Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 0 additions & 32 deletions src/components/comments/container/commentsContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import React, { useState, useEffect } from 'react';
import { Platform } from 'react-native';
import { connect } from 'react-redux';
import { injectIntl } from 'react-intl';
import get from 'lodash/get';

import { postBodySummary } from '@ecency/render-helper';
import { useNavigation } from '@react-navigation/native';
import { SheetManager } from 'react-native-actions-sheet';
import { getDiscussionsQueryOptions, useDeleteComment } from '@ecency/sdk';
import { useQueryClient } from '@tanstack/react-query';
// Services and Actions
import { writeToClipboard } from '../../../utils/clipboard';
import { stripCategoryFromPostPath } from '../../../utils/post';
import { toastNotification } from '../../../redux/actions/uiAction';

// Constants
Expand All @@ -34,7 +30,6 @@ const CommentsContainer = ({
currentAccount,
comments,
dispatch,
intl,
commentCount,
isLoggedIn,
commentNumber,
Expand Down Expand Up @@ -274,32 +269,6 @@ const CommentsContainer = ({
});
};

const _handleOnPressCommentMenu = (index, selectedComment) => {
const _showCopiedToast = () => {
dispatch(
toastNotification(
intl.formatMessage({
id: 'alert.copied',
}),
),
);
};

if (index === 0) {
// Normalize legacy `/<category>/@author/permlink…` to the canonical
// `/@author/permlink…` form that ecency.com now treats as canonical
// (the legacy form is 302'd to this one).
const _commentPath = stripCategoryFromPostPath(get(selectedComment, 'url'));
writeToClipboard(`https://ecency.com${_commentPath}`).then(_showCopiedToast);
}
if (index === 1) {
const body = postBodySummary(selectedComment.markdownBody, null, Platform.OS);
writeToClipboard(body).then(_showCopiedToast);
} else if (index === 2) {
_openReplyThread(selectedComment);
}
};

return (
<CommentsView
key={selectedFilter}
Expand All @@ -318,7 +287,6 @@ const CommentsContainer = ({
isLoggedIn={isLoggedIn}
fetchPost={fetchPost}
handleDeleteComment={_handleDeleteComment}
handleOnPressCommentMenu={_handleOnPressCommentMenu}
handleOnOptionsPress={handleOnOptionsPress}
handleOnUserPress={_handleOnUserPress}
isOwnProfile={isOwnProfile}
Expand Down
54 changes: 27 additions & 27 deletions src/components/comments/view/commentsView.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import React, { useState, Fragment, useRef } from 'react';
import React, { Fragment, useRef } from 'react';
import { Text } from 'react-native';
import get from 'lodash/get';
import { useIntl } from 'react-intl';
import EStyleSheet from 'react-native-extended-stylesheet';

// Components
import { FlashList } from '@shopify/flash-list';
import { Comment, TextButton, UpvotePopover } from '../..';
import { OptionsModal } from '../../atoms';
import { Comment, PostOptionsModal, TextButton, UpvotePopover } from '../..';
import { PostHtmlInteractionHandler } from '../../postHtmlRenderer';

// Styles
Expand All @@ -24,7 +23,6 @@ const CommentsView = ({
fetchPost,
handleDeleteComment,
handleOnEditPress,
handleOnPressCommentMenu,
handleOnReplyPress,
handleOnUserPress,
handleOnVotersPress,
Expand All @@ -46,21 +44,36 @@ const CommentsView = ({
onTagPress,
onAuthorPress,
}) => {
const [selectedComment, setSelectedComment] = useState(null);
const intl = useIntl();
const commentMenu = useRef<any>();
// Surfaces that pass `handleOnOptionsPress` (waves) route to their own sheet.
// Everywhere else used to fall back to a four-item menu with no delete, edit,
// report or moderation action; it now gets the same sheet the post detail
// screen uses.
const postOptionsModalRef = useRef<any>(null);
const upvotePopoverRef = useRef();
Comment thread
coderabbitai[bot] marked this conversation as resolved.
const postInteractionRef = useRef(null);

const _openCommentMenu = (item) => {
if (handleOnOptionsPress) {
handleOnOptionsPress(item);
} else if (commentMenu.current) {
setSelectedComment(item);
commentMenu.current.show();
} else if (postOptionsModalRef.current) {
postOptionsModalRef.current.show(item);
}
};

// Without this the sheet falls back to its own delete, which calls
// navigation.goBack() and would pop the profile or bot-comments screen the
// list is embedded in. It would also skip the in-place list removal and, on
// waves, the container's wave-specific delete path.
const _handleDeleteFromMenu = (item) =>
handleDeleteComment(
item.permlink,
item.parent_permlink,
item.parent_author,
item.root_author,
item.root_permlink,
);

const _openReplyThread = (item) => {
if (item && openReplyThread) {
openReplyThread(item);
Expand All @@ -73,11 +86,6 @@ const CommentsView = ({
}
};

const _onMenuItemPress = (index) => {
handleOnPressCommentMenu(index, selectedComment);
setSelectedComment(null);
};

const _onUpvotePress = ({ content, sourceRef, showPayoutDetails, onVotingStart }) => {
if (upvotePopoverRef.current) {
const postType = isWavesHost(content.parent_author) ? PostTypes.WAVE : PostTypes.COMMENT;
Expand All @@ -92,13 +100,6 @@ const CommentsView = ({
}
};

const menuItems = [
intl.formatMessage({ id: 'post.copy_link' }),
intl.formatMessage({ id: 'post.copy_text' }),
intl.formatMessage({ id: 'post.open_thread' }),
intl.formatMessage({ id: 'alert.cancel' }),
];

if (!hideManyCommentsButton && hasManyComments) {
return (
<TextButton
Expand Down Expand Up @@ -186,12 +187,11 @@ const CommentsView = ({
{...flatListProps}
/>
{!handleOnOptionsPress && (
<OptionsModal
ref={commentMenu}
options={menuItems}
title={get(selectedComment, 'summary')}
cancelButtonIndex={3}
onPress={_onMenuItemPress}
<PostOptionsModal
ref={postOptionsModalRef}
isVisibleTranslateModal={true}
onOpenThread={_openReplyThread}
Comment on lines +190 to +193

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Route comment deletion through the list handler

When a user selects Delete for an owned, deletable comment on any newly converted list surface, this modal receives no onDelete, so PostOptionsModal falls through to its default deletion path, which calls navigation.goBack() instead of the existing handleDeleteComment. This unexpectedly closes the profile/bot-comments screen and leaves the deleted item in the list state; on the profile Waves tab it also bypasses wavesQuery.deleteWave and therefore its infinite-query cache update. Pass a deletion callback that delegates to the existing comment/wave handler rather than using the modal default.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in a371e95. Both of you flagged this independently and it was the real defect in the PR.

Traced it: _deletePost only delegates when onDelete is present (postOptionsModal.tsx:451); otherwise it runs its own mutation and calls navigation.goBack() at :478. On a profile tab or bot-comments screen that pops the screen the list is embedded in. It also skips the container's in-place list removal, and on waves it bypasses the PostTypes.WAVE branch of _handleDeleteComment that routes through handleCommentDelete.

The list already had the right handler; the sheet just was not given it. commentsView now passes onDelete forwarding to handleDeleteComment with the same five arguments the inline delete button uses (commentView.tsx:317-323), so permlink, parent and root all arrive as they did before:

const _handleDeleteFromMenu = (item) =>
  handleDeleteComment(
    item.permlink, item.parent_permlink, item.parent_author,
    item.root_author, item.root_permlink,
  );

Worth noting this is exactly the failure mode the existing comment on _deletePost warns about for waves. The comment was there; I did not apply it when adding a new consumer.

Comment thread
coderabbitai[bot] marked this conversation as resolved.
onDelete={_handleDeleteFromMenu}
/>
Comment thread
greptile-apps[bot] marked this conversation as resolved.
)}
<UpvotePopover ref={upvotePopoverRef} />
Expand Down
49 changes: 47 additions & 2 deletions src/components/postOptionsModal/container/postOptionsModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect, useState, useRef, forwardRef, useImperativeHandle } from 'react';
import { Alert, Share, Text, TouchableHighlight } from 'react-native';
import { Alert, Platform, Share, Text, TouchableHighlight } from 'react-native';
import { useIntl } from 'react-intl';
import get from 'lodash/get';
import EStyleSheet from 'react-native-extended-stylesheet';
Expand All @@ -16,6 +16,7 @@ import {
parseProfileMetadata,
useDeleteComment,
} from '@ecency/sdk';
import { postBodySummary } from '@ecency/render-helper';
import { useAuthContext } from '../../../providers/sdk';
import {
useReblogMutation,
Expand Down Expand Up @@ -74,9 +75,18 @@ interface Props {
* disappears from the feed.
*/
onDelete?: (content: any) => void | Promise<void>;
/**
* Optional thread handler. When provided, the "open-thread" action is offered
* and delegates here. Only comment surfaces can open a thread, so the option
* is hidden wherever this is absent.
*/
onOpenThread?: (content: any) => void;
}

const PostOptionsModal = ({ pageType, isWave, isVisibleTranslateModal, onDelete }: Props, ref) => {
const PostOptionsModal = (
{ pageType, isWave, isVisibleTranslateModal, onDelete, onOpenThread }: Props,
ref,
) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const navigation = useNavigation();
Expand Down Expand Up @@ -235,6 +245,14 @@ const PostOptionsModal = ({ pageType, isWave, isVisibleTranslateModal, onDelete
// offer "unmute" on posts no moderator ever muted.
const _isMutedInCommunity = !!content && !!content.stats?.gray;

// Carried over from the legacy comment menu, so both are scoped to comments
// rather than widening the post detail sheet. A comment is anything with a
// parent; copying the text also needs a body to copy, and opening a thread
// only means something where a handler was given.
const _isCommentContent = !!content && (content.depth > 0 || !!content.parent_author);
const _canCopyText = _isCommentContent && !!content?.markdownBody;
const _canOpenThread = _isCommentContent && !!onOpenThread;

// check if post can be deleted
// Hive's on-chain rule is: no children AND no net positive rshares. Using
// `active_votes.length` was stricter than the chain (a self-vote or a
Expand Down Expand Up @@ -301,6 +319,10 @@ const PostOptionsModal = ({ pageType, isWave, isVisibleTranslateModal, onDelete
return _canMuteCommunityPost && !_isMutedInCommunity;
case 'unmute-post':
return _canMuteCommunityPost && _isMutedInCommunity;
case 'copy-text':
return _canCopyText;
case 'open-thread':
return _canOpenThread;
case 'translate':
return isVisibleTranslateModal;
case 'delete-post':
Expand Down Expand Up @@ -830,6 +852,29 @@ const PostOptionsModal = ({ pageType, isWave, isVisibleTranslateModal, onDelete
}, 300);
break;
}
case 'copy-text': {
// The legacy comment menu copied a plain-text summary rather than raw
// markdown, so links and images do not come through as syntax.
// writeToClipboard returns false for empty text, and the summary can be
// empty where the body is only an image or markup, so the success toast
// has to follow the result rather than the attempt.
const _body = postBodySummary(content.markdownBody, null, Platform.OS);
const _copied = await writeToClipboard(_body);
if (!_copied) {
break;
}
alertTimer.current = setTimeout(() => {
dispatch(toastNotification(intl.formatMessage({ id: 'alert.copied' })));
alertTimer.current = null;
}, 300);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
break;
}
case 'open-thread':
// Deferred like the other cases that leave this sheet, so the
// navigation is not swallowed by the sheet's own hide animation.
await delay(700);
onOpenThread?.(content);
break;

case 'reblog':
_reblog(false);
Expand Down
2 changes: 2 additions & 0 deletions src/config/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@
"cross-post": "Cross Post",
"pin-blog": "Pin to blog",
"copy": "copy link",
"copy-text": "copy text",
"open-thread": "open thread",
"unpin-community": "Unpin from community",
"mute-post": "Mute post",
"unmute-post": "Unmute post",
Expand Down
5 changes: 5 additions & 0 deletions src/constants/options/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ export default [
'cross-post',
'promote',
'copy',
// Comment surfaces only. Carried over from the legacy comment menu that
// PostOptionsModal replaced, which offered copy-link, copy-text and
// open-thread and nothing else.
'copy-text',
'open-thread',
'reply',
'translate',
'reblog',
Expand Down
Loading