Do not allow replies to self story

This commit is contained in:
Josh Perez 2022-09-21 15:19:16 -04:00 committed by GitHub
parent d221895b3a
commit b04fbb6d8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 70 additions and 17 deletions

View File

@ -5591,6 +5591,10 @@
"message": "No replies yet", "message": "No replies yet",
"description": "Placeholder text for when there are no replies" "description": "Placeholder text for when there are no replies"
}, },
"StoryViewsNRepliesModal__no-views": {
"message": "No views yet",
"description": "Placeholder text for when there are no views"
},
"StoryViewsNRepliesModal__tab--views": { "StoryViewsNRepliesModal__tab--views": {
"message": "Views", "message": "Views",
"description": "Title for views tab" "description": "Title for views tab"

View File

@ -23,6 +23,7 @@
display: flex; display: flex;
flex: 1; flex: 1;
justify-content: center; justify-content: center;
padding: 80px 0;
user-select: none; user-select: none;
} }
} }

View File

@ -424,8 +424,10 @@ export const StoryViewer = ({
muteClassName = 'StoryViewer__soundless'; muteClassName = 'StoryViewer__soundless';
} }
const isSent = Boolean(sendState);
const contextMenuOptions: ReadonlyArray<ContextMenuOptionType<unknown>> = const contextMenuOptions: ReadonlyArray<ContextMenuOptionType<unknown>> =
sendState isSent
? [ ? [
{ {
icon: 'StoryListItem__icon--info', icon: 'StoryListItem__icon--info',
@ -654,7 +656,7 @@ export const StoryViewer = ({
))} ))}
</div> </div>
<div className="StoryViewer__actions"> <div className="StoryViewer__actions">
{(canReply || sendState) && ( {(canReply || isSent) && (
<button <button
className="StoryViewer__reply" className="StoryViewer__reply"
onClick={() => setHasStoryViewsNRepliesModal(true)} onClick={() => setHasStoryViewsNRepliesModal(true)}
@ -662,12 +664,12 @@ export const StoryViewer = ({
type="button" type="button"
> >
<> <>
{sendState || replyCount > 0 ? ( {isSent || replyCount > 0 ? (
<span className="StoryViewer__reply__chevron"> <span className="StoryViewer__reply__chevron">
{sendState && !hasReadReceiptSetting && !replyCount && ( {isSent && !hasReadReceiptSetting && !replyCount && (
<>{i18n('StoryViewer__views-off')}</> <>{i18n('StoryViewer__views-off')}</>
)} )}
{sendState && {isSent &&
hasReadReceiptSetting && hasReadReceiptSetting &&
(viewCount === 1 ? ( (viewCount === 1 ? (
<Intl <Intl
@ -682,7 +684,7 @@ export const StoryViewer = ({
components={[<strong>{viewCount}</strong>]} components={[<strong>{viewCount}</strong>]}
/> />
))} ))}
{(sendState || viewCount > 0) && replyCount > 0 && ' '} {(isSent || viewCount > 0) && replyCount > 0 && ' '}
{replyCount > 0 && {replyCount > 0 &&
(replyCount === 1 ? ( (replyCount === 1 ? (
<Intl <Intl
@ -699,7 +701,7 @@ export const StoryViewer = ({
))} ))}
</span> </span>
) : null} ) : null}
{!sendState && !replyCount && ( {!isSent && !replyCount && (
<span className="StoryViewer__reply__arrow"> <span className="StoryViewer__reply__arrow">
{isGroupStory {isGroupStory
? i18n('StoryViewer__reply-group') ? i18n('StoryViewer__reply-group')
@ -758,6 +760,7 @@ export const StoryViewer = ({
canReply={Boolean(canReply)} canReply={Boolean(canReply)}
getPreferredBadge={getPreferredBadge} getPreferredBadge={getPreferredBadge}
hasReadReceiptSetting={hasReadReceiptSetting} hasReadReceiptSetting={hasReadReceiptSetting}
hasViewsCapability={isSent}
i18n={i18n} i18n={i18n}
isGroupStory={isGroupStory} isGroupStory={isGroupStory}
onClose={() => setHasStoryViewsNRepliesModal(false)} onClose={() => setHasStoryViewsNRepliesModal(false)}

View File

@ -31,6 +31,9 @@ export default {
hasReadReceiptSetting: { hasReadReceiptSetting: {
defaultValue: true, defaultValue: true,
}, },
hasViewsCapability: {
defaultValue: false,
},
i18n: { i18n: {
defaultValue: i18n, defaultValue: i18n,
}, },
@ -168,10 +171,20 @@ CanReply.storyName = 'Can reply';
export const ViewsOnly = Template.bind({}); export const ViewsOnly = Template.bind({});
ViewsOnly.args = { ViewsOnly.args = {
canReply: false,
hasViewsCapability: true,
views: getViewsAndReplies().views, views: getViewsAndReplies().views,
}; };
ViewsOnly.storyName = 'Views only'; ViewsOnly.storyName = 'Views only';
export const NoViews = Template.bind({});
NoViews.args = {
canReply: false,
hasViewsCapability: true,
views: [],
};
NoViews.storyName = 'No views';
export const InAGroupNoReplies = Template.bind({}); export const InAGroupNoReplies = Template.bind({});
InAGroupNoReplies.args = { InAGroupNoReplies.args = {
isGroupStory: true, isGroupStory: true,
@ -182,6 +195,7 @@ export const InAGroup = Template.bind({});
{ {
const { views, replies } = getViewsAndReplies(); const { views, replies } = getViewsAndReplies();
InAGroup.args = { InAGroup.args = {
hasViewsCapability: true,
isGroupStory: true, isGroupStory: true,
replies, replies,
views, views,
@ -210,6 +224,7 @@ export const ReadReceiptsTurnedOff = Template.bind({});
ReadReceiptsTurnedOff.args = { ReadReceiptsTurnedOff.args = {
canReply: false, canReply: false,
hasReadReceiptSetting: false, hasReadReceiptSetting: false,
hasViewsCapability: true,
views: getViewsAndReplies().views, views: getViewsAndReplies().views,
}; };
ReadReceiptsTurnedOff.storyName = 'Read receipts turned off'; ReadReceiptsTurnedOff.storyName = 'Read receipts turned off';
@ -219,6 +234,7 @@ export const GroupReadReceiptsOff = Template.bind({});
const { views, replies } = getViewsAndReplies(); const { views, replies } = getViewsAndReplies();
GroupReadReceiptsOff.args = { GroupReadReceiptsOff.args = {
hasReadReceiptSetting: false, hasReadReceiptSetting: false,
hasViewsCapability: true,
isGroupStory: true, isGroupStory: true,
replies, replies,
views, views,

View File

@ -88,6 +88,7 @@ export type PropsType = {
canReply: boolean; canReply: boolean;
getPreferredBadge: PreferredBadgeSelectorType; getPreferredBadge: PreferredBadgeSelectorType;
hasReadReceiptSetting: boolean; hasReadReceiptSetting: boolean;
hasViewsCapability: boolean;
i18n: LocalizerType; i18n: LocalizerType;
isGroupStory?: boolean; isGroupStory?: boolean;
onClose: () => unknown; onClose: () => unknown;
@ -115,6 +116,7 @@ export const StoryViewsNRepliesModal = ({
canReply, canReply,
getPreferredBadge, getPreferredBadge,
hasReadReceiptSetting, hasReadReceiptSetting,
hasViewsCapability,
i18n, i18n,
isGroupStory, isGroupStory,
onClose, onClose,
@ -353,7 +355,7 @@ export const StoryViewsNRepliesModal = ({
} }
let viewsElement: JSX.Element | undefined; let viewsElement: JSX.Element | undefined;
if (!hasReadReceiptSetting) { if (hasViewsCapability && !hasReadReceiptSetting) {
viewsElement = ( viewsElement = (
<div className="StoryViewsNRepliesModal__read-receipts-off"> <div className="StoryViewsNRepliesModal__read-receipts-off">
{i18n('StoryViewsNRepliesModal__read-receipts-off')} {i18n('StoryViewsNRepliesModal__read-receipts-off')}
@ -397,10 +399,16 @@ export const StoryViewsNRepliesModal = ({
))} ))}
</div> </div>
); );
} else if (hasViewsCapability) {
viewsElement = (
<div className="StoryViewsNRepliesModal__replies--none">
{i18n('StoryViewsNRepliesModal__no-views')}
</div>
);
} }
const tabsElement = const tabsElement =
views.length && replies.length ? ( viewsElement && repliesElement ? (
<Tabs <Tabs
initialSelectedTab={Tab.Views} initialSelectedTab={Tab.Views}
moduleClassName="StoryViewsNRepliesModal__tabs" moduleClassName="StoryViewsNRepliesModal__tabs"

View File

@ -1667,7 +1667,9 @@ function canReplyOrReact(
} }
if (isStory(message)) { if (isStory(message)) {
return Boolean(message.canReplyToStory); return (
Boolean(message.canReplyToStory) && conversation.id !== ourConversationId
);
} }
// Fail safe. // Fail safe.

View File

@ -29,6 +29,7 @@ import {
getConversationSelector, getConversationSelector,
getMe, getMe,
} from './conversations'; } from './conversations';
import { getUserConversationId } from './user';
import { getDistributionListSelector } from './storyDistributionLists'; import { getDistributionListSelector } from './storyDistributionLists';
import { getStoriesEnabled } from './items'; import { getStoriesEnabled } from './items';
import { calculateExpirationTimestamp } from '../../util/expirationTimer'; import { calculateExpirationTimestamp } from '../../util/expirationTimer';
@ -126,6 +127,7 @@ function getAvatarData(
export function getStoryView( export function getStoryView(
conversationSelector: GetConversationByIdType, conversationSelector: GetConversationByIdType,
ourConversationId: string | undefined,
story: StoryDataType story: StoryDataType
): StoryViewType { ): StoryViewType {
const sender = pick(conversationSelector(story.sourceUuid || story.source), [ const sender = pick(conversationSelector(story.sourceUuid || story.source), [
@ -176,7 +178,7 @@ export function getStoryView(
return { return {
attachment, attachment,
canReply: canReply(story, undefined, conversationSelector), canReply: canReply(story, ourConversationId, conversationSelector),
isHidden: Boolean(sender.hideStory), isHidden: Boolean(sender.hideStory),
isUnread: story.readStatus === ReadStatus.Unread, isUnread: story.readStatus === ReadStatus.Unread,
messageId: story.messageId, messageId: story.messageId,
@ -193,6 +195,7 @@ export function getStoryView(
export function getConversationStory( export function getConversationStory(
conversationSelector: GetConversationByIdType, conversationSelector: GetConversationByIdType,
ourConversationId: string | undefined,
story: StoryDataType story: StoryDataType
): ConversationStoryType { ): ConversationStoryType {
const sender = pick(conversationSelector(story.sourceUuid || story.source), [ const sender = pick(conversationSelector(story.sourceUuid || story.source), [
@ -212,7 +215,11 @@ export function getConversationStory(
'title', 'title',
]); ]);
const storyView = getStoryView(conversationSelector, story); const storyView = getStoryView(
conversationSelector,
ourConversationId,
story
);
return { return {
conversationId: conversation.id, conversationId: conversation.id,
@ -292,10 +299,12 @@ export const getStories = createSelector(
getConversationSelector, getConversationSelector,
getDistributionListSelector, getDistributionListSelector,
getStoriesState, getStoriesState,
getUserConversationId,
( (
conversationSelector, conversationSelector,
distributionListSelector, distributionListSelector,
{ stories }: Readonly<StoriesStateType> { stories }: Readonly<StoriesStateType>,
ourConversationId
): { ): {
hiddenStories: Array<ConversationStoryType>; hiddenStories: Array<ConversationStoryType>;
myStories: Array<MyStoryType>; myStories: Array<MyStoryType>;
@ -312,6 +321,7 @@ export const getStories = createSelector(
const conversationStory = getConversationStory( const conversationStory = getConversationStory(
conversationSelector, conversationSelector,
ourConversationId,
story story
); );
@ -339,7 +349,11 @@ export const getStories = createSelector(
return; return;
} }
const storyView = getStoryView(conversationSelector, story); const storyView = getStoryView(
conversationSelector,
ourConversationId,
story
);
const existingMyStory = myStoriesById.get(sentId) || { stories: [] }; const existingMyStory = myStoriesById.get(sentId) || { stories: [] };
@ -427,7 +441,8 @@ export const getHasStoriesSelector = createSelector(
export const getStoryByIdSelector = createSelector( export const getStoryByIdSelector = createSelector(
getStoriesState, getStoriesState,
({ stories }) => getUserConversationId,
({ stories }, ourConversationId) =>
( (
conversationSelector: GetConversationByIdType, conversationSelector: GetConversationByIdType,
messageId: string messageId: string
@ -441,8 +456,12 @@ export const getStoryByIdSelector = createSelector(
} }
return { return {
conversationStory: getConversationStory(conversationSelector, story), conversationStory: getConversationStory(
storyView: getStoryView(conversationSelector, story), conversationSelector,
ourConversationId,
story
),
storyView: getStoryView(conversationSelector, ourConversationId, story),
}; };
} }
); );