Toast on delete bot directive usage (#620)
* Toast on successful delete bot directive * refactor duplicate code into a reusable function * restore empty spacing lines to clean up the diff * perf optimization, only query for deleteScheduledAt for your own items * Issue a warning toast if the delete bot was mentioned but the item was not scheduled for deletion * use bs-secondary color for warning --------- Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
This commit is contained in:
parent
d84c46df81
commit
4596681fbc
|
@ -994,6 +994,15 @@ export default {
|
||||||
}
|
}
|
||||||
const parent = await models.item.findUnique({ where: { id: item.parentId } })
|
const parent = await models.item.findUnique({ where: { id: item.parentId } })
|
||||||
return parent.otsHash
|
return parent.otsHash
|
||||||
|
},
|
||||||
|
deleteScheduledAt: async (item, args, { me, models }) => {
|
||||||
|
const meId = me?.id ?? ANON_USER_ID
|
||||||
|
if (meId !== item.userId) {
|
||||||
|
// Only query for deleteScheduledAt for your own items to keep DB queries minimized
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const deleteJobs = await models.$queryRawUnsafe(`SELECT startafter FROM pgboss.job WHERE name = 'deleteItem' AND data->>'id' = '${item.id}'`)
|
||||||
|
return deleteJobs[0]?.startafter ?? null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ export default gql`
|
||||||
createdAt: Date!
|
createdAt: Date!
|
||||||
updatedAt: Date!
|
updatedAt: Date!
|
||||||
deletedAt: Date
|
deletedAt: Date
|
||||||
|
deleteScheduledAt: Date
|
||||||
title: String
|
title: String
|
||||||
searchTitle: String
|
searchTitle: String
|
||||||
url: String
|
url: String
|
||||||
|
|
|
@ -7,9 +7,10 @@ import InputGroup from 'react-bootstrap/InputGroup'
|
||||||
import { bountySchema } from '../lib/validate'
|
import { bountySchema } from '../lib/validate'
|
||||||
import { SubSelectInitial } from './sub-select-form'
|
import { SubSelectInitial } from './sub-select-form'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { normalizeForwards } from '../lib/form'
|
import { normalizeForwards, toastDeleteScheduled } from '../lib/form'
|
||||||
import { MAX_TITLE_LENGTH } from '../lib/constants'
|
import { MAX_TITLE_LENGTH } from '../lib/constants'
|
||||||
import { useMe } from './me'
|
import { useMe } from './me'
|
||||||
|
import { useToast } from './toast'
|
||||||
import { ItemButtonBar } from './post'
|
import { ItemButtonBar } from './post'
|
||||||
|
|
||||||
export function BountyForm ({
|
export function BountyForm ({
|
||||||
|
@ -25,6 +26,7 @@ export function BountyForm ({
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const client = useApolloClient()
|
const client = useApolloClient()
|
||||||
const me = useMe()
|
const me = useMe()
|
||||||
|
const toaster = useToast()
|
||||||
const schema = bountySchema({ client, me, existingBoost: item?.boost })
|
const schema = bountySchema({ client, me, existingBoost: item?.boost })
|
||||||
const [upsertBounty] = useMutation(
|
const [upsertBounty] = useMutation(
|
||||||
gql`
|
gql`
|
||||||
|
@ -51,6 +53,7 @@ export function BountyForm ({
|
||||||
hmac: $hmac
|
hmac: $hmac
|
||||||
) {
|
) {
|
||||||
id
|
id
|
||||||
|
deleteScheduledAt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
@ -58,7 +61,7 @@ export function BountyForm ({
|
||||||
|
|
||||||
const onSubmit = useCallback(
|
const onSubmit = useCallback(
|
||||||
async ({ boost, bounty, ...values }) => {
|
async ({ boost, bounty, ...values }) => {
|
||||||
const { error } = await upsertBounty({
|
const { data, error } = await upsertBounty({
|
||||||
variables: {
|
variables: {
|
||||||
sub: item?.subName || sub?.name,
|
sub: item?.subName || sub?.name,
|
||||||
id: item?.id,
|
id: item?.id,
|
||||||
|
@ -78,6 +81,7 @@ export function BountyForm ({
|
||||||
const prefix = sub?.name ? `/~${sub.name}` : ''
|
const prefix = sub?.name ? `/~${sub.name}` : ''
|
||||||
await router.push(prefix + '/recent')
|
await router.push(prefix + '/recent')
|
||||||
}
|
}
|
||||||
|
toastDeleteScheduled(toaster, data, !!item, values.text)
|
||||||
}, [upsertBounty, router]
|
}, [upsertBounty, router]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,19 @@ import { Form, MarkdownInput } from '../components/form'
|
||||||
import { gql, useMutation } from '@apollo/client'
|
import { gql, useMutation } from '@apollo/client'
|
||||||
import styles from './reply.module.css'
|
import styles from './reply.module.css'
|
||||||
import { commentSchema } from '../lib/validate'
|
import { commentSchema } from '../lib/validate'
|
||||||
|
import { useToast } from './toast'
|
||||||
|
import { toastDeleteScheduled } from '../lib/form'
|
||||||
import { FeeButtonProvider } from './fee-button'
|
import { FeeButtonProvider } from './fee-button'
|
||||||
import { ItemButtonBar } from './post'
|
import { ItemButtonBar } from './post'
|
||||||
|
|
||||||
export default function CommentEdit ({ comment, editThreshold, onSuccess, onCancel }) {
|
export default function CommentEdit ({ comment, editThreshold, onSuccess, onCancel }) {
|
||||||
|
const toaster = useToast()
|
||||||
const [upsertComment] = useMutation(
|
const [upsertComment] = useMutation(
|
||||||
gql`
|
gql`
|
||||||
mutation upsertComment($id: ID! $text: String!) {
|
mutation upsertComment($id: ID! $text: String!) {
|
||||||
upsertComment(id: $id, text: $text) {
|
upsertComment(id: $id, text: $text) {
|
||||||
text
|
text
|
||||||
|
deleteScheduledAt
|
||||||
}
|
}
|
||||||
}`, {
|
}`, {
|
||||||
update (cache, { data: { upsertComment } }) {
|
update (cache, { data: { upsertComment } }) {
|
||||||
|
@ -35,10 +39,11 @@ export default function CommentEdit ({ comment, editThreshold, onSuccess, onCanc
|
||||||
}}
|
}}
|
||||||
schema={commentSchema}
|
schema={commentSchema}
|
||||||
onSubmit={async (values, { resetForm }) => {
|
onSubmit={async (values, { resetForm }) => {
|
||||||
const { error } = await upsertComment({ variables: { ...values, id: comment.id } })
|
const { data, error } = await upsertComment({ variables: { ...values, id: comment.id } })
|
||||||
if (error) {
|
if (error) {
|
||||||
throw new Error({ message: error.toString() })
|
throw new Error({ message: error.toString() })
|
||||||
}
|
}
|
||||||
|
toastDeleteScheduled(toaster, data, true, values.text)
|
||||||
if (onSuccess) {
|
if (onSuccess) {
|
||||||
onSuccess()
|
onSuccess()
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,11 @@ import Item from './item'
|
||||||
import { discussionSchema } from '../lib/validate'
|
import { discussionSchema } from '../lib/validate'
|
||||||
import { SubSelectInitial } from './sub-select-form'
|
import { SubSelectInitial } from './sub-select-form'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { normalizeForwards } from '../lib/form'
|
import { normalizeForwards, toastDeleteScheduled } from '../lib/form'
|
||||||
import { MAX_TITLE_LENGTH } from '../lib/constants'
|
import { MAX_TITLE_LENGTH } from '../lib/constants'
|
||||||
import { useMe } from './me'
|
import { useMe } from './me'
|
||||||
import useCrossposter from './use-crossposter'
|
import useCrossposter from './use-crossposter'
|
||||||
|
import { useToast } from './toast'
|
||||||
import { ItemButtonBar } from './post'
|
import { ItemButtonBar } from './post'
|
||||||
|
|
||||||
export function DiscussionForm ({
|
export function DiscussionForm ({
|
||||||
|
@ -27,12 +28,14 @@ export function DiscussionForm ({
|
||||||
// if Web Share Target API was used
|
// if Web Share Target API was used
|
||||||
const shareTitle = router.query.title
|
const shareTitle = router.query.title
|
||||||
const crossposter = useCrossposter()
|
const crossposter = useCrossposter()
|
||||||
|
const toaster = useToast()
|
||||||
|
|
||||||
const [upsertDiscussion] = useMutation(
|
const [upsertDiscussion] = useMutation(
|
||||||
gql`
|
gql`
|
||||||
mutation upsertDiscussion($sub: String, $id: ID, $title: String!, $text: String, $boost: Int, $forward: [ItemForwardInput], $hash: String, $hmac: String) {
|
mutation upsertDiscussion($sub: String, $id: ID, $title: String!, $text: String, $boost: Int, $forward: [ItemForwardInput], $hash: String, $hmac: String) {
|
||||||
upsertDiscussion(sub: $sub, id: $id, title: $title, text: $text, boost: $boost, forward: $forward, hash: $hash, hmac: $hmac) {
|
upsertDiscussion(sub: $sub, id: $id, title: $title, text: $text, boost: $boost, forward: $forward, hash: $hash, hmac: $hmac) {
|
||||||
id
|
id
|
||||||
|
deleteScheduledAt
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
|
@ -75,6 +78,7 @@ export function DiscussionForm ({
|
||||||
const prefix = sub?.name ? `/~${sub.name}` : ''
|
const prefix = sub?.name ? `/~${sub.name}` : ''
|
||||||
await router.push(prefix + '/recent')
|
await router.push(prefix + '/recent')
|
||||||
}
|
}
|
||||||
|
toastDeleteScheduled(toaster, data, !!item, values.text)
|
||||||
}, [upsertDiscussion, router, item, sub, crossposter]
|
}, [upsertDiscussion, router, item, sub, crossposter]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,8 @@ import { usePrice } from './price'
|
||||||
import Avatar from './avatar'
|
import Avatar from './avatar'
|
||||||
import { jobSchema } from '../lib/validate'
|
import { jobSchema } from '../lib/validate'
|
||||||
import { MAX_TITLE_LENGTH } from '../lib/constants'
|
import { MAX_TITLE_LENGTH } from '../lib/constants'
|
||||||
|
import { useToast } from './toast'
|
||||||
|
import { toastDeleteScheduled } from '../lib/form'
|
||||||
import { ItemButtonBar } from './post'
|
import { ItemButtonBar } from './post'
|
||||||
|
|
||||||
function satsMin2Mo (minute) {
|
function satsMin2Mo (minute) {
|
||||||
|
@ -38,6 +40,7 @@ function PriceHint ({ monthly }) {
|
||||||
export default function JobForm ({ item, sub }) {
|
export default function JobForm ({ item, sub }) {
|
||||||
const storageKeyPrefix = item ? undefined : `${sub.name}-job`
|
const storageKeyPrefix = item ? undefined : `${sub.name}-job`
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const toaster = useToast()
|
||||||
const [logoId, setLogoId] = useState(item?.uploadId)
|
const [logoId, setLogoId] = useState(item?.uploadId)
|
||||||
const [upsertJob] = useMutation(gql`
|
const [upsertJob] = useMutation(gql`
|
||||||
mutation upsertJob($sub: String!, $id: ID, $title: String!, $company: String!, $location: String,
|
mutation upsertJob($sub: String!, $id: ID, $title: String!, $company: String!, $location: String,
|
||||||
|
@ -46,6 +49,7 @@ export default function JobForm ({ item, sub }) {
|
||||||
location: $location, remote: $remote, text: $text,
|
location: $location, remote: $remote, text: $text,
|
||||||
url: $url, maxBid: $maxBid, status: $status, logo: $logo, hash: $hash, hmac: $hmac) {
|
url: $url, maxBid: $maxBid, status: $status, logo: $logo, hash: $hash, hmac: $hmac) {
|
||||||
id
|
id
|
||||||
|
deleteScheduledAt
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
|
@ -59,7 +63,7 @@ export default function JobForm ({ item, sub }) {
|
||||||
status = 'STOPPED'
|
status = 'STOPPED'
|
||||||
}
|
}
|
||||||
|
|
||||||
const { error } = await upsertJob({
|
const { data, error } = await upsertJob({
|
||||||
variables: {
|
variables: {
|
||||||
id: item?.id,
|
id: item?.id,
|
||||||
sub: item?.subName || sub?.name,
|
sub: item?.subName || sub?.name,
|
||||||
|
@ -78,6 +82,7 @@ export default function JobForm ({ item, sub }) {
|
||||||
} else {
|
} else {
|
||||||
await router.push(`/~${sub.name}/recent`)
|
await router.push(`/~${sub.name}/recent`)
|
||||||
}
|
}
|
||||||
|
toastDeleteScheduled(toaster, data, !!item, values.text)
|
||||||
}, [upsertJob, router, logoId]
|
}, [upsertJob, router, logoId]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,8 @@ import AccordianItem from './accordian-item'
|
||||||
import { linkSchema } from '../lib/validate'
|
import { linkSchema } from '../lib/validate'
|
||||||
import Moon from '../svgs/moon-fill.svg'
|
import Moon from '../svgs/moon-fill.svg'
|
||||||
import { SubSelectInitial } from './sub-select-form'
|
import { SubSelectInitial } from './sub-select-form'
|
||||||
import { normalizeForwards } from '../lib/form'
|
import { normalizeForwards, toastDeleteScheduled } from '../lib/form'
|
||||||
|
import { useToast } from './toast'
|
||||||
import { MAX_TITLE_LENGTH } from '../lib/constants'
|
import { MAX_TITLE_LENGTH } from '../lib/constants'
|
||||||
import { useMe } from './me'
|
import { useMe } from './me'
|
||||||
import { ItemButtonBar } from './post'
|
import { ItemButtonBar } from './post'
|
||||||
|
@ -19,6 +20,7 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const client = useApolloClient()
|
const client = useApolloClient()
|
||||||
const me = useMe()
|
const me = useMe()
|
||||||
|
const toaster = useToast()
|
||||||
const schema = linkSchema({ client, me, existingBoost: item?.boost })
|
const schema = linkSchema({ client, me, existingBoost: item?.boost })
|
||||||
// if Web Share Target API was used
|
// if Web Share Target API was used
|
||||||
const shareUrl = router.query.url
|
const shareUrl = router.query.url
|
||||||
|
@ -70,13 +72,14 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
|
||||||
mutation upsertLink($sub: String, $id: ID, $title: String!, $url: String!, $text: String, $boost: Int, $forward: [ItemForwardInput], $hash: String, $hmac: String) {
|
mutation upsertLink($sub: String, $id: ID, $title: String!, $url: String!, $text: String, $boost: Int, $forward: [ItemForwardInput], $hash: String, $hmac: String) {
|
||||||
upsertLink(sub: $sub, id: $id, title: $title, url: $url, text: $text, boost: $boost, forward: $forward, hash: $hash, hmac: $hmac) {
|
upsertLink(sub: $sub, id: $id, title: $title, url: $url, text: $text, boost: $boost, forward: $forward, hash: $hash, hmac: $hmac) {
|
||||||
id
|
id
|
||||||
|
deleteScheduledAt
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
|
|
||||||
const onSubmit = useCallback(
|
const onSubmit = useCallback(
|
||||||
async ({ boost, title, ...values }) => {
|
async ({ boost, title, ...values }) => {
|
||||||
const { error } = await upsertLink({
|
const { data, error } = await upsertLink({
|
||||||
variables: {
|
variables: {
|
||||||
sub: item?.subName || sub?.name,
|
sub: item?.subName || sub?.name,
|
||||||
id: item?.id,
|
id: item?.id,
|
||||||
|
@ -95,6 +98,7 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
|
||||||
const prefix = sub?.name ? `/~${sub.name}` : ''
|
const prefix = sub?.name ? `/~${sub.name}` : ''
|
||||||
await router.push(prefix + '/recent')
|
await router.push(prefix + '/recent')
|
||||||
}
|
}
|
||||||
|
toastDeleteScheduled(toaster, data, !!item, values.text)
|
||||||
}, [upsertLink, router]
|
}, [upsertLink, router]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -7,14 +7,16 @@ import { MAX_POLL_CHOICE_LENGTH, MAX_POLL_NUM_CHOICES, MAX_TITLE_LENGTH } from '
|
||||||
import { pollSchema } from '../lib/validate'
|
import { pollSchema } from '../lib/validate'
|
||||||
import { SubSelectInitial } from './sub-select-form'
|
import { SubSelectInitial } from './sub-select-form'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { normalizeForwards } from '../lib/form'
|
import { normalizeForwards, toastDeleteScheduled } from '../lib/form'
|
||||||
import { useMe } from './me'
|
import { useMe } from './me'
|
||||||
|
import { useToast } from './toast'
|
||||||
import { ItemButtonBar } from './post'
|
import { ItemButtonBar } from './post'
|
||||||
|
|
||||||
export function PollForm ({ item, sub, editThreshold, children }) {
|
export function PollForm ({ item, sub, editThreshold, children }) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const client = useApolloClient()
|
const client = useApolloClient()
|
||||||
const me = useMe()
|
const me = useMe()
|
||||||
|
const toaster = useToast()
|
||||||
const schema = pollSchema({ client, me, existingBoost: item?.boost })
|
const schema = pollSchema({ client, me, existingBoost: item?.boost })
|
||||||
|
|
||||||
const [upsertPoll] = useMutation(
|
const [upsertPoll] = useMutation(
|
||||||
|
@ -24,6 +26,7 @@ export function PollForm ({ item, sub, editThreshold, children }) {
|
||||||
upsertPoll(sub: $sub, id: $id, title: $title, text: $text,
|
upsertPoll(sub: $sub, id: $id, title: $title, text: $text,
|
||||||
options: $options, boost: $boost, forward: $forward, hash: $hash, hmac: $hmac) {
|
options: $options, boost: $boost, forward: $forward, hash: $hash, hmac: $hmac) {
|
||||||
id
|
id
|
||||||
|
deleteScheduledAt
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
|
@ -31,7 +34,7 @@ export function PollForm ({ item, sub, editThreshold, children }) {
|
||||||
const onSubmit = useCallback(
|
const onSubmit = useCallback(
|
||||||
async ({ boost, title, options, ...values }) => {
|
async ({ boost, title, options, ...values }) => {
|
||||||
const optionsFiltered = options.slice(initialOptions?.length).filter(word => word.trim().length > 0)
|
const optionsFiltered = options.slice(initialOptions?.length).filter(word => word.trim().length > 0)
|
||||||
const { error } = await upsertPoll({
|
const { data, error } = await upsertPoll({
|
||||||
variables: {
|
variables: {
|
||||||
id: item?.id,
|
id: item?.id,
|
||||||
sub: item?.subName || sub?.name,
|
sub: item?.subName || sub?.name,
|
||||||
|
@ -51,6 +54,7 @@ export function PollForm ({ item, sub, editThreshold, children }) {
|
||||||
const prefix = sub?.name ? `/~${sub.name}` : ''
|
const prefix = sub?.name ? `/~${sub.name}` : ''
|
||||||
await router.push(prefix + '/recent')
|
await router.push(prefix + '/recent')
|
||||||
}
|
}
|
||||||
|
toastDeleteScheduled(toaster, data, !!item, values.text)
|
||||||
}, [upsertPoll, router]
|
}, [upsertPoll, router]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ import { commentsViewedAfterComment } from '../lib/new-comments'
|
||||||
import { commentSchema } from '../lib/validate'
|
import { commentSchema } from '../lib/validate'
|
||||||
import { quote } from '../lib/md'
|
import { quote } from '../lib/md'
|
||||||
import { COMMENT_DEPTH_LIMIT } from '../lib/constants'
|
import { COMMENT_DEPTH_LIMIT } from '../lib/constants'
|
||||||
|
import { useToast } from './toast'
|
||||||
|
import { toastDeleteScheduled } from '../lib/form'
|
||||||
import { ItemButtonBar } from './post'
|
import { ItemButtonBar } from './post'
|
||||||
|
|
||||||
export function ReplyOnAnotherPage ({ item }) {
|
export function ReplyOnAnotherPage ({ item }) {
|
||||||
|
@ -34,6 +36,7 @@ export default forwardRef(function Reply ({ item, onSuccess, replyOpen, children
|
||||||
const parentId = item.id
|
const parentId = item.id
|
||||||
const replyInput = useRef(null)
|
const replyInput = useRef(null)
|
||||||
const formInnerRef = useRef()
|
const formInnerRef = useRef()
|
||||||
|
const toaster = useToast()
|
||||||
|
|
||||||
// Start block to handle iOS Safari's weird selection clearing behavior
|
// Start block to handle iOS Safari's weird selection clearing behavior
|
||||||
const savedRange = useRef()
|
const savedRange = useRef()
|
||||||
|
@ -139,7 +142,8 @@ export default forwardRef(function Reply ({ item, onSuccess, replyOpen, children
|
||||||
)
|
)
|
||||||
|
|
||||||
const onSubmit = useCallback(async ({ amount, hash, hmac, ...values }, { resetForm }) => {
|
const onSubmit = useCallback(async ({ amount, hash, hmac, ...values }, { resetForm }) => {
|
||||||
await upsertComment({ variables: { parentId, hash, hmac, ...values } })
|
const { data } = await upsertComment({ variables: { parentId, hash, hmac, ...values } })
|
||||||
|
toastDeleteScheduled(toaster, data, false, values.text)
|
||||||
resetForm({ text: '' })
|
resetForm({ text: '' })
|
||||||
setReply(replyOpen || false)
|
setReply(replyOpen || false)
|
||||||
}, [upsertComment, setReply, parentId])
|
}, [upsertComment, setReply, parentId])
|
||||||
|
|
|
@ -25,12 +25,20 @@ export const ToastProvider = ({ children }) => {
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const toaster = useMemo(() => ({
|
const toaster = useMemo(() => ({
|
||||||
success: body => {
|
success: (body, delay = 5000) => {
|
||||||
dispatchToast({
|
dispatchToast({
|
||||||
body,
|
body,
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
autohide: true,
|
autohide: true,
|
||||||
delay: 5000
|
delay
|
||||||
|
})
|
||||||
|
},
|
||||||
|
warning: (body, delay = 5000) => {
|
||||||
|
dispatchToast({
|
||||||
|
body,
|
||||||
|
variant: 'warning',
|
||||||
|
autohide: true,
|
||||||
|
delay
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
danger: (body, onCloseCallback) => {
|
danger: (body, onCloseCallback) => {
|
||||||
|
@ -64,7 +72,7 @@ export const ToastProvider = ({ children }) => {
|
||||||
{toasts.map(toast => (
|
{toasts.map(toast => (
|
||||||
<Toast
|
<Toast
|
||||||
key={toast.id} bg={toast.variant} show autohide={toast.autohide}
|
key={toast.id} bg={toast.variant} show autohide={toast.autohide}
|
||||||
delay={toast.delay} className={`${styles.toast} ${styles[toast.variant]}`} onClose={() => removeToast(toast.id)}
|
delay={toast.delay} className={`${styles.toast} ${styles[toast.variant]} ${toast.variant === 'warning' ? 'text-dark' : ''}`} onClose={() => removeToast(toast.id)}
|
||||||
>
|
>
|
||||||
<ToastBody>
|
<ToastBody>
|
||||||
<div className='d-flex align-items-center'>
|
<div className='d-flex align-items-center'>
|
||||||
|
@ -77,7 +85,7 @@ export const ToastProvider = ({ children }) => {
|
||||||
if (toast.onCloseCallback) toast.onCloseCallback()
|
if (toast.onCloseCallback) toast.onCloseCallback()
|
||||||
removeToast(toast.id)
|
removeToast(toast.id)
|
||||||
}}
|
}}
|
||||||
><div className={styles.toastClose}>X</div>
|
><div className={`${styles.toastClose} ${toast.variant === 'warning' ? 'text-dark' : ''}`}>X</div>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</ToastBody>
|
</ToastBody>
|
||||||
|
|
|
@ -17,6 +17,10 @@
|
||||||
border-color: var(--bs-danger-border-subtle);
|
border-color: var(--bs-danger-border-subtle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
border-color: var(--bs-warning-border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
.toastClose {
|
.toastClose {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-family: "lightning";
|
font-family: "lightning";
|
||||||
|
|
|
@ -6,6 +6,7 @@ export const COMMENT_FIELDS = gql`
|
||||||
parentId
|
parentId
|
||||||
createdAt
|
createdAt
|
||||||
deletedAt
|
deletedAt
|
||||||
|
deleteScheduledAt
|
||||||
text
|
text
|
||||||
user {
|
user {
|
||||||
id
|
id
|
||||||
|
|
|
@ -55,6 +55,7 @@ export const ITEM_FULL_FIELDS = gql`
|
||||||
fragment ItemFullFields on Item {
|
fragment ItemFullFields on Item {
|
||||||
...ItemFields
|
...ItemFields
|
||||||
text
|
text
|
||||||
|
deleteScheduledAt
|
||||||
root {
|
root {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
|
|
27
lib/form.js
27
lib/form.js
|
@ -1,3 +1,5 @@
|
||||||
|
import { hasDeleteMention } from './item'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize an array of forwards by converting the pct from a string to a number
|
* Normalize an array of forwards by converting the pct from a string to a number
|
||||||
* Also extracts nym from nested user object, if necessary
|
* Also extracts nym from nested user object, if necessary
|
||||||
|
@ -10,3 +12,28 @@ export const normalizeForwards = (forward) => {
|
||||||
}
|
}
|
||||||
return forward.filter(fwd => fwd.nym || fwd.user?.name).map(fwd => ({ nym: fwd.nym ?? fwd.user?.name, pct: Number(fwd.pct) }))
|
return forward.filter(fwd => fwd.nym || fwd.user?.name).map(fwd => ({ nym: fwd.nym ?? fwd.user?.name, pct: Number(fwd.pct) }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const toastDeleteScheduled = (toaster, upsertResponseData, isEdit, itemText) => {
|
||||||
|
const keys = Object.keys(upsertResponseData)
|
||||||
|
const data = upsertResponseData[keys[0]]
|
||||||
|
if (!data) return
|
||||||
|
const deleteScheduledAt = data.deleteScheduledAt ? new Date(data.deleteScheduledAt) : undefined
|
||||||
|
if (deleteScheduledAt) {
|
||||||
|
const itemType = {
|
||||||
|
upsertDiscussion: 'discussion post',
|
||||||
|
upsertLink: 'link post',
|
||||||
|
upsertPoll: 'poll',
|
||||||
|
upsertBounty: 'bounty',
|
||||||
|
upsertJob: 'job',
|
||||||
|
upsertComment: 'comment'
|
||||||
|
}[keys[0]] ?? 'item'
|
||||||
|
|
||||||
|
const message = `${itemType === 'comment' ? 'your comment' : isEdit ? `this ${itemType}` : `your new ${itemType}`} will be deleted at ${deleteScheduledAt.toLocaleString()}`
|
||||||
|
toaster.success(message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (hasDeleteMention(itemText)) {
|
||||||
|
// There's a delete mention but the deletion wasn't scheduled
|
||||||
|
toaster.warning('it looks like you tried to use the delete bot but it didn\'t work. make sure you use the correct format: "@delete in n units" e.g. "@delete in 2 hours"', 10000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,10 @@ export const isJob = item => typeof item.maxBid !== 'undefined'
|
||||||
// a delete directive preceded by a non word character that isn't a backtick
|
// a delete directive preceded by a non word character that isn't a backtick
|
||||||
const deletePattern = /\B(?<!`)@delete\s+in\s+(\d+)\s+(second|minute|hour|day|week|month|year)s?/gi
|
const deletePattern = /\B(?<!`)@delete\s+in\s+(\d+)\s+(second|minute|hour|day|week|month|year)s?/gi
|
||||||
|
|
||||||
|
const deleteMentionPattern = /\B(?<!`)@delete/i
|
||||||
|
|
||||||
|
export const hasDeleteMention = (text) => deleteMentionPattern.test(text ?? '')
|
||||||
|
|
||||||
export const getDeleteCommand = (text) => {
|
export const getDeleteCommand = (text) => {
|
||||||
if (!text) return false
|
if (!text) return false
|
||||||
const matches = [...text.matchAll(deletePattern)]
|
const matches = [...text.matchAll(deletePattern)]
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
$primary: #FADA5E;
|
$primary: #FADA5E;
|
||||||
$secondary: #F6911D;
|
$secondary: #F6911D;
|
||||||
$danger: #c03221;
|
$danger: #c03221;
|
||||||
|
$warning: $secondary;
|
||||||
$info: #007cbe;
|
$info: #007cbe;
|
||||||
$success: #5c8001;
|
$success: #5c8001;
|
||||||
$twitter: #1da1f2;
|
$twitter: #1da1f2;
|
||||||
|
@ -13,6 +14,7 @@ $theme-colors: (
|
||||||
"primary" : #FADA5E,
|
"primary" : #FADA5E,
|
||||||
"secondary" : #F6911D,
|
"secondary" : #F6911D,
|
||||||
"danger" : #c03221,
|
"danger" : #c03221,
|
||||||
|
"warning" : $secondary,
|
||||||
"info" : #007cbe,
|
"info" : #007cbe,
|
||||||
"success" : #5c8001,
|
"success" : #5c8001,
|
||||||
"twitter" : #1da1f2,
|
"twitter" : #1da1f2,
|
||||||
|
|
Loading…
Reference in New Issue