diff --git a/CHANGELOG.md b/CHANGELOG.md index f6e5ec86b..5b4e3c00f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Add confirmation on comment removal _community pr!_ ([#6563](https://github.com/lbryio/lbry-desktop/pull/6563)) - Show on content page if a file is part of a playlist already _community pr!_([#6393](https://github.com/lbryio/lbry-desktop/pull/6393)) - Add filtering to playlists ([#6905](https://github.com/lbryio/lbry-desktop/pull/6905)) +- Added direct replying to notifications _community pr!_ ([#6935](https://github.com/lbryio/lbry-desktop/pull/6935)) ### Changed - Use Canonical Url for copy link ([#6500](https://github.com/lbryio/lbry-desktop/pull/6500)) diff --git a/ui/component/comment/view.jsx b/ui/component/comment/view.jsx index bc8222db7..3233d3fb6 100644 --- a/ui/component/comment/view.jsx +++ b/ui/component/comment/view.jsx @@ -64,6 +64,9 @@ type Props = { isModerator: boolean, isGlobalMod: boolean, isFiat: boolean, + supportDisabled: boolean, + setQuickReply: (any) => void, + quickReply: any, }; const LENGTH_TO_COLLAPSE = 300; @@ -100,6 +103,9 @@ function Comment(props: Props) { isModerator, isGlobalMod, isFiat, + supportDisabled, + setQuickReply, + quickReply, } = props; const { @@ -185,6 +191,7 @@ function Comment(props: Props) { function handleSubmit() { updateComment(commentId, editedMessage); + if (setQuickReply) setQuickReply({ ...quickReply, comment_id: commentId, comment: editedMessage }); setEditing(false); } @@ -294,6 +301,7 @@ function Comment(props: Props) { commentIsMine={commentIsMine} handleEditComment={handleEditComment} supportAmount={supportAmount} + setQuickReply={setQuickReply} /> @@ -403,6 +411,7 @@ function Comment(props: Props) { onCancelReplying={() => { setReplying(false); }} + supportDisabled={supportDisabled} /> )} diff --git a/ui/component/commentCreate/index.js b/ui/component/commentCreate/index.js index 66bb84f01..2f6d25b47 100644 --- a/ui/component/commentCreate/index.js +++ b/ui/component/commentCreate/index.js @@ -7,7 +7,7 @@ import { doSendTip, } from 'lbry-redux'; import { doOpenModal, doSetActiveChannel } from 'redux/actions/app'; -import { doCommentCreate, doFetchCreatorSettings } from 'redux/actions/comments'; +import { doCommentCreate, doFetchCreatorSettings, doCommentById } from 'redux/actions/comments'; import { selectUserVerifiedEmail } from 'redux/selectors/user'; import { selectActiveChannelClaim } from 'redux/selectors/app'; import { selectSettingsByChannelId } from 'redux/selectors/comments'; @@ -43,6 +43,7 @@ const perform = (dispatch, ownProps) => ({ sendTip: (params, callback, errorCallback) => dispatch(doSendTip(params, false, callback, errorCallback, false)), doToast: (options) => dispatch(doToast(options)), doFetchCreatorSettings: (channelClaimId) => dispatch(doFetchCreatorSettings(channelClaimId)), + fetchComment: (commentId) => dispatch(doCommentById(commentId, false)), }); export default connect(select, perform)(CommentCreate); diff --git a/ui/component/commentCreate/view.jsx b/ui/component/commentCreate/view.jsx index 26c9093e1..33360f3d0 100644 --- a/ui/component/commentCreate/view.jsx +++ b/ui/component/commentCreate/view.jsx @@ -47,8 +47,12 @@ type Props = { claimIsMine: boolean, sendTip: ({}, (any) => void, (any) => void) => void, doToast: ({ message: string }) => void, + supportDisabled: boolean, doFetchCreatorSettings: (channelId: string) => Promise, settingsByChannelId: { [channelId: string]: PerChannelSettings }, + setQuickReply: (any) => void, + fetchComment: (commentId: string) => Promise, + shouldFetchComment: boolean, }; export function CommentCreate(props: Props) { @@ -71,6 +75,10 @@ export function CommentCreate(props: Props) { doToast, doFetchCreatorSettings, settingsByChannelId, + supportDisabled, + setQuickReply, + fetchComment, + shouldFetchComment, } = props; const buttonRef: ElementRef = React.useRef(); const { @@ -80,7 +88,7 @@ export function CommentCreate(props: Props) { const [isSubmitting, setIsSubmitting] = React.useState(false); const [commentFailure, setCommentFailure] = React.useState(false); const [successTip, setSuccessTip] = React.useState({ txid: undefined, tipAmount: undefined }); - const { claim_id: claimId } = claim; + const claimId = claim && claim.claim_id; const [isSupportComment, setIsSupportComment] = React.useState(); const [isReviewingSupportComment, setIsReviewingSupportComment] = React.useState(); const [tipAmount, setTipAmount] = React.useState(1); @@ -90,7 +98,8 @@ export function CommentCreate(props: Props) { const charCount = commentValue.length; const [activeTab, setActiveTab] = React.useState(''); const [tipError, setTipError] = React.useState(); - const disabled = isSubmitting || isFetchingChannels || !commentValue.length; + const [deletedComment, setDeletedComment] = React.useState(false); + const disabled = deletedComment || isSubmitting || isFetchingChannels || !commentValue.length; const [shouldDisableReviewButton, setShouldDisableReviewButton] = React.useState(); const channelId = getChannelIdFromClaim(claim); const channelSettings = channelId ? settingsByChannelId[channelId] : undefined; @@ -99,6 +108,15 @@ export function CommentCreate(props: Props) { const minAmount = minTip || minSuper || 0; const minAmountMet = minAmount === 0 || tipAmount >= minAmount; + // Fetch top-level comments to identify if it has been deleted and can reply to it + React.useEffect(() => { + if (shouldFetchComment && fetchComment) { + fetchComment(parentId).then((result) => { + setDeletedComment(String(result).includes('Error')); + }); + } + }, [fetchComment, shouldFetchComment, parentId]); + const minAmountRef = React.useRef(minAmount); minAmountRef.current = minAmount; @@ -322,6 +340,7 @@ export function CommentCreate(props: Props) { createComment(commentValue, claimId, parentId, txid, payment_intent_id, environment) .then((res) => { setIsSubmitting(false); + if (setQuickReply) setQuickReply(res); if (res && res.signature) { setCommentValue(''); @@ -522,32 +541,34 @@ export function CommentCreate(props: Props) { requiresAuth={IS_WEB} /> )} - {!claimIsMine && ( - )} diff --git a/ui/component/commentsReplies/view.jsx b/ui/component/commentsReplies/view.jsx index 42d0e451a..83cec4219 100644 --- a/ui/component/commentsReplies/view.jsx +++ b/ui/component/commentsReplies/view.jsx @@ -18,6 +18,7 @@ type Props = { isFetchingByParentId: { [string]: boolean }, onShowMore?: () => void, hasMore: boolean, + supportDisabled: boolean, }; function CommentsReplies(props: Props) { @@ -34,6 +35,7 @@ function CommentsReplies(props: Props) { isFetchingByParentId, onShowMore, hasMore, + supportDisabled, } = props; const [isExpanded, setExpanded] = React.useState(true); @@ -98,6 +100,7 @@ function CommentsReplies(props: Props) { numDirectReplies={comment.replies} isModerator={comment.is_moderator} isGlobalMod={comment.is_global_mod} + supportDisabled={supportDisabled} /> ); })} diff --git a/ui/component/notification/view.jsx b/ui/component/notification/view.jsx index 3a0d70811..e51fe4ebe 100644 --- a/ui/component/notification/view.jsx +++ b/ui/component/notification/view.jsx @@ -18,6 +18,9 @@ import NotificationContentChannelMenu from 'component/notificationContentChannel import LbcMessage from 'component/common/lbc-message'; import UriIndicator from 'component/uriIndicator'; import { NavLink } from 'react-router-dom'; +import CommentReactions from 'component/commentReactions'; +import CommentCreate from 'component/commentCreate'; +import CommentsReplies from 'component/commentsReplies'; type Props = { notification: WebNotification, @@ -31,6 +34,8 @@ export default function Notification(props: Props) { const { notification, menuButton = false, doReadNotifications, doDeleteNotification } = props; const { push } = useHistory(); const { notification_rule, notification_parameters, is_read, id } = notification; + const [isReplying, setReplying] = React.useState(false); + const [quickReply, setQuickReply] = React.useState(); const isCommentNotification = notification_rule === RULE.COMMENT || @@ -119,7 +124,8 @@ export default function Notification(props: Props) { fullTitle.push(message); if (index === titleSplit.length - 1) { - return {fullTitle.join(' ')}; + const result = fullTitle.join(' '); + return {result}; } } }); @@ -147,11 +153,6 @@ export default function Notification(props: Props) { } } - function handleReadNotification(e) { - e.stopPropagation(); - doReadNotifications([id]); - } - const Wrapper = menuButton ? (props: { children: any }) => ( @@ -160,10 +161,8 @@ export default function Notification(props: Props) { ) : notificationLink ? (props: { children: any }) => ( - - - {props.children} - + + {props.children} ) : (props: { children: any }) => ( @@ -176,12 +175,12 @@ export default function Notification(props: Props) { ); return ( - -
+
+
{icon}
@@ -192,7 +191,7 @@ export default function Notification(props: Props) { {isCommentNotification && commentText ? ( <>
{title}
-
+
{commentText}
@@ -220,7 +219,15 @@ export default function Notification(props: Props) {
- {!is_read &&
-
-
+ + + {isCommentNotification && ( +
+
+
+ + {isReplying && ( + setReplying(false)} + onCancelReplying={() => setReplying(false)} + setQuickReply={setQuickReply} + supportDisabled + shouldFetchComment + /> + )} + + {quickReply && ( + + )} +
+ )} +
); } diff --git a/ui/modal/modalRemoveComment/view.jsx b/ui/modal/modalRemoveComment/view.jsx index f595be838..72a9c2ac7 100644 --- a/ui/modal/modalRemoveComment/view.jsx +++ b/ui/modal/modalRemoveComment/view.jsx @@ -11,10 +11,19 @@ type Props = { closeModal: () => void, deleteComment: (string, ?string) => void, supportAmount?: any, + setQuickReply: (any) => void, }; function ModalRemoveComment(props: Props) { - const { commentId, commentIsMine, contentChannelPermanentUrl, closeModal, deleteComment, supportAmount } = props; + const { + commentId, + commentIsMine, + contentChannelPermanentUrl, + closeModal, + deleteComment, + supportAmount, + setQuickReply, + } = props; return ( @@ -24,7 +33,9 @@ function ModalRemoveComment(props: Props) {

{__('Are you sure you want to remove this comment?')}

{Boolean(supportAmount) && ( -

{__('This comment has a tip associated with it which cannot be reverted.')}

+

+ {__('This comment has a tip associated with it which cannot be reverted.')} +

)}
} @@ -35,8 +46,9 @@ function ModalRemoveComment(props: Props) { button="primary" label={__('Remove')} onClick={() => { - deleteComment(commentId, commentIsMine ? undefined : contentChannelPermanentUrl); closeModal(); + deleteComment(commentId, commentIsMine ? undefined : contentChannelPermanentUrl); + if (setQuickReply) setQuickReply(undefined); }} />