Notify story creator for replies

Co-authored-by: Jamie Kyle <113370520+jamiebuilds-signal@users.noreply.github.com>
This commit is contained in:
automated-signal 2022-10-11 13:48:39 -07:00 committed by GitHub
parent 8da29d1f96
commit ed9fd7e931
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 250 additions and 135 deletions

View File

@ -154,6 +154,7 @@ import { SeenStatus } from './MessageSeenStatus';
import MessageSender from './textsecure/SendMessage';
import type AccountManager from './textsecure/AccountManager';
import { onStoryRecipientUpdate } from './util/onStoryRecipientUpdate';
import { StoryViewModeType, StoryViewTargetType } from './types/Stories';
const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000;
@ -1879,10 +1880,19 @@ export async function startApp(): Promise<void> {
activeWindowService.registerForActive(() => notificationService.clear());
window.addEventListener('unload', () => notificationService.fastClear());
notificationService.on('click', (id, messageId) => {
notificationService.on('click', (id, messageId, storyId) => {
window.showWindow();
if (id) {
window.Whisper.events.trigger('showConversation', id, messageId);
if (storyId) {
window.reduxActions.stories.viewStory({
storyId,
storyViewMode: StoryViewModeType.Single,
viewTarget: StoryViewTargetType.Replies,
});
} else {
window.Whisper.events.trigger('showConversation', id, messageId);
}
} else {
window.reduxActions.app.openInbox();
}

View File

@ -3,11 +3,12 @@
import React, { useState } from 'react';
import type { MyStoryType, StoryViewType } from '../types/Stories';
import { StoryViewTargetType, StoryViewModeType } from '../types/Stories';
import type { LocalizerType } from '../types/Util';
import type { ViewStoryActionCreatorType } from '../state/ducks/stories';
import { ConfirmationDialog } from './ConfirmationDialog';
import { ContextMenu } from './ContextMenu';
import { StoryViewModeType } from '../types/Stories';
import { MessageTimestamp } from './conversation/MessageTimestamp';
import { StoryDistributionListName } from './StoryDistributionListName';
import { StoryImage } from './StoryImage';
@ -151,7 +152,7 @@ export const MyStories = ({
viewStory({
storyId: story.messageId,
storyViewMode: StoryViewModeType.User,
shouldShowDetailsModal: true,
viewTarget: StoryViewTargetType.Details,
});
},
},

View File

@ -5,13 +5,14 @@ import React, { useState } from 'react';
import classNames from 'classnames';
import type { ConversationType } from '../state/ducks/conversations';
import type { ConversationStoryType, StoryViewType } from '../types/Stories';
import { StoryViewTargetType, HasStories } from '../types/Stories';
import type { LocalizerType } from '../types/Util';
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
import type { ViewUserStoriesActionCreatorType } from '../state/ducks/stories';
import { Avatar, AvatarSize } from './Avatar';
import { ConfirmationDialog } from './ConfirmationDialog';
import { ContextMenu } from './ContextMenu';
import { HasStories } from '../types/Stories';
import { MessageTimestamp } from './conversation/MessageTimestamp';
import { StoryImage } from './StoryImage';
import { ThemeType } from '../types/Util';
@ -134,7 +135,10 @@ export const StoryListItem = ({
icon: 'StoryListItem__icon--info',
label: i18n('StoryListItem__info'),
onClick: () =>
viewUserStories({ conversationId, shouldShowDetailsModal: true }),
viewUserStories({
conversationId,
viewTarget: StoryViewTargetType.Details,
}),
},
{
icon: 'StoryListItem__icon--chat',

View File

@ -31,7 +31,11 @@ import { SendStatus } from '../messages/MessageSendState';
import { StoryDetailsModal } from './StoryDetailsModal';
import { StoryDistributionListName } from './StoryDistributionListName';
import { StoryImage } from './StoryImage';
import { StoryViewDirectionType, StoryViewModeType } from '../types/Stories';
import {
StoryViewDirectionType,
StoryViewModeType,
StoryViewTargetType,
} from '../types/Stories';
import { StoryViewsNRepliesModal } from './StoryViewsNRepliesModal';
import { Theme } from '../util/theme';
import { ToastType } from '../state/ducks/toast';
@ -83,7 +87,7 @@ export type PropsType = {
recentEmojis?: Array<string>;
renderEmojiPicker: (props: RenderEmojiPickerProps) => JSX.Element;
replyState?: ReplyStateType;
shouldShowDetailsModal?: boolean;
viewTarget?: StoryViewTargetType;
showToast: ShowToastActionCreatorType;
skinTone?: number;
story: StoryViewType;
@ -128,7 +132,7 @@ export const StoryViewer = ({
recentEmojis,
renderEmojiPicker,
replyState,
shouldShowDetailsModal,
viewTarget,
showToast,
skinTone,
story,
@ -167,12 +171,14 @@ export const StoryViewer = ({
const conversationId = group?.id || story.sender.id;
const [hasStoryViewsNRepliesModal, setHasStoryViewsNRepliesModal] =
useState(false);
const [hasStoryDetailsModal, setHasStoryDetailsModal] = useState(
Boolean(shouldShowDetailsModal)
const [currentViewTarget, setCurrentViewTarget] = useState(
viewTarget ?? null
);
useEffect(() => {
setCurrentViewTarget(viewTarget ?? null);
}, [viewTarget]);
const onClose = useCallback(() => {
viewStory({
closeViewer: true,
@ -180,12 +186,12 @@ export const StoryViewer = ({
}, [viewStory]);
const onEscape = useCallback(() => {
if (hasStoryViewsNRepliesModal) {
setHasStoryViewsNRepliesModal(false);
if (currentViewTarget != null) {
setCurrentViewTarget(null);
} else {
onClose();
}
}, [hasStoryViewsNRepliesModal, onClose]);
}, [currentViewTarget, onClose]);
useEscapeHandling(onEscape);
@ -314,8 +320,7 @@ export const StoryViewer = ({
hasActiveCall ||
hasConfirmHideStory ||
hasExpandedCaption ||
hasStoryDetailsModal ||
hasStoryViewsNRepliesModal ||
currentViewTarget != null ||
isShowingContextMenu ||
pauseStory ||
Boolean(reactionEmoji);
@ -351,7 +356,7 @@ export const StoryViewer = ({
(ev: KeyboardEvent) => {
// the replies modal can consume arrow keys
// we don't want to navigate while someone is typing a reply
if (hasStoryViewsNRepliesModal) {
if (currentViewTarget != null) {
return;
}
@ -374,7 +379,7 @@ export const StoryViewer = ({
}
},
[
hasStoryViewsNRepliesModal,
currentViewTarget,
canNavigateLeft,
canNavigateRight,
story.messageId,
@ -466,7 +471,7 @@ export const StoryViewer = ({
{
icon: 'StoryListItem__icon--info',
label: i18n('StoryListItem__info'),
onClick: () => setHasStoryDetailsModal(true),
onClick: () => setCurrentViewTarget(StoryViewTargetType.Details),
},
{
icon: 'StoryListItem__icon--delete',
@ -478,7 +483,7 @@ export const StoryViewer = ({
{
icon: 'StoryListItem__icon--info',
label: i18n('StoryListItem__info'),
onClick: () => setHasStoryDetailsModal(true),
onClick: () => setCurrentViewTarget(StoryViewTargetType.Details),
},
{
icon: 'StoryListItem__icon--hide',
@ -726,7 +731,9 @@ export const StoryViewer = ({
{(canReply || isSent) && (
<button
className="StoryViewer__reply"
onClick={() => setHasStoryViewsNRepliesModal(true)}
onClick={() =>
setCurrentViewTarget(StoryViewTargetType.Replies)
}
tabIndex={0}
type="button"
>
@ -788,11 +795,11 @@ export const StoryViewer = ({
type="button"
/>
</div>
{hasStoryDetailsModal && (
{currentViewTarget === StoryViewTargetType.Details && (
<StoryDetailsModal
getPreferredBadge={getPreferredBadge}
i18n={i18n}
onClose={() => setHasStoryDetailsModal(false)}
onClose={() => setCurrentViewTarget(null)}
sender={story.sender}
sendState={sendState}
size={attachment?.size}
@ -800,7 +807,8 @@ export const StoryViewer = ({
expirationTimestamp={story.expirationTimestamp}
/>
)}
{hasStoryViewsNRepliesModal && (
{(currentViewTarget === StoryViewTargetType.Replies ||
currentViewTarget === StoryViewTargetType.Views) && (
<StoryViewsNRepliesModal
authorTitle={firstName || title}
canReply={Boolean(canReply)}
@ -809,18 +817,18 @@ export const StoryViewer = ({
hasViewsCapability={isSent}
i18n={i18n}
isGroupStory={isGroupStory}
onClose={() => setHasStoryViewsNRepliesModal(false)}
onClose={() => setCurrentViewTarget(null)}
onReact={emoji => {
onReactToStory(emoji, story);
if (!isGroupStory) {
setHasStoryViewsNRepliesModal(false);
setCurrentViewTarget(null);
showToast(ToastType.StoryReact);
}
setReactionEmoji(emoji);
}}
onReply={(message, mentions, replyTimestamp) => {
if (!isGroupStory) {
setHasStoryViewsNRepliesModal(false);
setCurrentViewTarget(null);
showToast(ToastType.StoryReply);
}
onReplyToStory(message, mentions, replyTimestamp, story);
@ -836,6 +844,8 @@ export const StoryViewer = ({
sortedGroupMembers={group?.sortedGroupMembers}
storyPreviewAttachment={attachment}
views={views}
viewTarget={currentViewTarget}
onChangeViewTarget={setCurrentViewTarget}
/>
)}
{hasConfirmHideStory && (

View File

@ -4,6 +4,7 @@
import type { Meta, Story } from '@storybook/react';
import React from 'react';
import { useArgs } from '@storybook/addons';
import type { PropsType } from './StoryViewsNRepliesModal';
import * as durations from '../util/durations';
import enMessages from '../../_locales/en/messages.json';
@ -14,6 +15,7 @@ import { UUID } from '../types/UUID';
import { fakeAttachment } from '../test-both/helpers/fakeAttachment';
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
import { setupI18n } from '../util/setupI18n';
import { StoryViewTargetType } from '../types/Stories';
const i18n = setupI18n('en', enMessages);
@ -64,6 +66,12 @@ export default {
views: {
defaultValue: [],
},
viewTarget: {
defaultValue: StoryViewTargetType.Views,
},
onChangeViewTarget: {
action: true,
},
},
} as Meta;
@ -161,9 +169,21 @@ function getViewsAndReplies() {
};
}
const Template: Story<PropsType> = args => (
<StoryViewsNRepliesModal {...args} />
);
const Template: Story<PropsType> = args => {
const [, updateArgs] = useArgs();
function onChangeViewTarget(viewTarget: StoryViewTargetType) {
args.onChangeViewTarget(viewTarget);
updateArgs({ viewTarget });
}
return (
<StoryViewsNRepliesModal
{...args}
onChangeViewTarget={onChangeViewTarget}
/>
);
};
export const CanReply = Template.bind({});
CanReply.args = {};

View File

@ -1,7 +1,13 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useCallback, useEffect, useRef, useState } from 'react';
import React, {
useCallback,
useLayoutEffect,
useMemo,
useRef,
useState,
} from 'react';
import classNames from 'classnames';
import { usePopper } from 'react-popper';
import type { AttachmentType } from '../types/Attachment';
@ -12,6 +18,7 @@ import type { InputApi } from './CompositionInput';
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
import type { RenderEmojiPickerProps } from './conversation/ReactionPicker';
import type { ReplyType, StorySendStateType } from '../types/Stories';
import { StoryViewTargetType } from '../types/Stories';
import { Avatar, AvatarSize } from './Avatar';
import { CompositionInput } from './CompositionInput';
import { ContactName } from './conversation/ContactName';
@ -78,7 +85,7 @@ const MESSAGE_DEFAULT_PROPS = {
viewStory: shouldNeverBeCalled,
};
enum Tab {
export enum StoryViewsNRepliesTab {
Replies = 'Replies',
Views = 'Views',
}
@ -109,6 +116,8 @@ export type PropsType = {
sortedGroupMembers?: Array<ConversationType>;
storyPreviewAttachment?: AttachmentType;
views: Array<StorySendStateType>;
viewTarget: StoryViewTargetType;
onChangeViewTarget: (target: StoryViewTargetType) => unknown;
};
export const StoryViewsNRepliesModal = ({
@ -133,14 +142,30 @@ export const StoryViewsNRepliesModal = ({
sortedGroupMembers,
storyPreviewAttachment,
views,
viewTarget,
onChangeViewTarget,
}: PropsType): JSX.Element | null => {
const containerElementRef = useRef<HTMLDivElement | null>(null);
const inputApiRef = useRef<InputApi | undefined>();
const shouldScrollToBottomRef = useRef(false);
const [bottom, setBottom] = useState<HTMLDivElement | null>(null);
const shouldScrollToBottomRef = useRef(true);
const bottomRef = useRef<HTMLDivElement>(null);
const [messageBodyText, setMessageBodyText] = useState('');
const [showReactionPicker, setShowReactionPicker] = useState(false);
const currentTab = useMemo<StoryViewsNRepliesTab>(() => {
return viewTarget === StoryViewTargetType.Replies
? StoryViewsNRepliesTab.Replies
: StoryViewsNRepliesTab.Views;
}, [viewTarget]);
const onTabChange = (tab: string) => {
onChangeViewTarget(
tab === StoryViewsNRepliesTab.Replies
? StoryViewTargetType.Replies
: StoryViewTargetType.Views
);
};
const focusComposer = useCallback(() => {
if (inputApiRef.current) {
inputApiRef.current.focus();
@ -170,12 +195,16 @@ export const StoryViewsNRepliesModal = ({
let composerElement: JSX.Element | undefined;
useEffect(() => {
if (replies.length && shouldScrollToBottomRef.current) {
bottom?.scrollIntoView({ behavior: 'smooth' });
useLayoutEffect(() => {
if (
currentTab === StoryViewsNRepliesTab.Replies &&
replies.length &&
shouldScrollToBottomRef.current
) {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
shouldScrollToBottomRef.current = false;
}
}, [bottom, replies.length]);
}, [currentTab, replies.length]);
if (canReply) {
composerElement = (
@ -348,7 +377,7 @@ export const StoryViewsNRepliesModal = ({
</div>
)
)}
<div ref={setBottom} />
<div ref={bottomRef} />
</div>
);
} else if (isGroupStory) {
@ -414,23 +443,24 @@ export const StoryViewsNRepliesModal = ({
const tabsElement =
viewsElement && repliesElement ? (
<Tabs
initialSelectedTab={Tab.Views}
selectedTab={currentTab}
onTabChange={onTabChange}
moduleClassName="StoryViewsNRepliesModal__tabs"
tabs={[
{
id: Tab.Views,
id: StoryViewsNRepliesTab.Views,
label: i18n('StoryViewsNRepliesModal__tab--views'),
},
{
id: Tab.Replies,
id: StoryViewsNRepliesTab.Replies,
label: i18n('StoryViewsNRepliesModal__tab--replies'),
},
]}
>
{({ selectedTab }) => (
<>
{selectedTab === Tab.Views && viewsElement}
{selectedTab === Tab.Replies && (
{selectedTab === StoryViewsNRepliesTab.Views && viewsElement}
{selectedTab === StoryViewsNRepliesTab.Replies && (
<>
{repliesElement}
{composerElement}

View File

@ -11,19 +11,9 @@ type PropsType = {
children: (renderProps: { selectedTab: string }) => ReactNode;
} & TabsOptionsType;
export const Tabs = ({
children,
initialSelectedTab,
moduleClassName,
onTabChange,
tabs,
}: PropsType): JSX.Element => {
const { selectedTab, tabsHeaderElement } = useTabs({
initialSelectedTab,
moduleClassName,
onTabChange,
tabs,
});
export const Tabs = (props: PropsType): JSX.Element => {
const { children, ...options } = props;
const { selectedTab, tabsHeaderElement } = useTabs(options);
return (
<>

View File

@ -12,33 +12,59 @@ type Tab = {
label: string;
};
export type TabsOptionsType = {
initialSelectedTab?: string;
export type BaseTabsOptionsType = {
moduleClassName?: string;
onTabChange?: (selectedTab: string) => unknown;
tabs: Array<Tab>;
};
export function useTabs({
initialSelectedTab,
moduleClassName,
onTabChange,
tabs,
}: TabsOptionsType): {
export type ControlledTabsOptionsType = BaseTabsOptionsType & {
selectedTab: string;
onTabChange: (selectedTab: string) => unknown;
};
export type UncontrolledTabsOptionsType = BaseTabsOptionsType & {
initialSelectedTab?: string;
onTabChange?: (selectedTab: string) => unknown;
};
export type TabsOptionsType =
| ControlledTabsOptionsType
| UncontrolledTabsOptionsType;
type TabsProps = {
selectedTab: string;
tabsHeaderElement: JSX.Element;
} {
assertDev(tabs.length, 'Tabs needs more than 1 tab present');
};
const getClassName = getClassNamesFor('Tabs', moduleClassName);
export function useTabs(options: TabsOptionsType): TabsProps {
assertDev(options.tabs.length, 'Tabs needs more than 1 tab present');
const [selectedTab, setSelectedTab] = useState<string>(
initialSelectedTab || tabs[0].id
);
const getClassName = getClassNamesFor('Tabs', options.moduleClassName);
let selectedTab: string;
let onChange: (selectedTab: string) => void;
if ('selectedTab' in options) {
selectedTab = options.selectedTab;
onChange = options.onTabChange;
} else {
// useTabs should always be either controlled or uncontrolled.
// This is enforced by the type system.
// eslint-disable-next-line react-hooks/rules-of-hooks
const [tabState, setTabState] = useState<string>(
options.initialSelectedTab || options.tabs[0].id
);
selectedTab = tabState;
onChange = (newTab: string) => {
setTabState(newTab);
options.onTabChange?.(newTab);
};
}
const tabsHeaderElement = (
<div className={getClassName('')}>
{tabs.map(({ id, label }) => (
{options.tabs.map(({ id, label }) => (
<div
className={classNames(
getClassName('__tab'),
@ -46,12 +72,11 @@ export function useTabs({
)}
key={id}
onClick={() => {
setSelectedTab(id);
onTabChange?.(id);
onChange(id);
}}
onKeyUp={(e: KeyboardEvent) => {
if (e.target === e.currentTarget && e.keyCode === 13) {
setSelectedTab(id);
onChange(id);
e.preventDefault();
e.stopPropagation();
}

View File

@ -5296,6 +5296,7 @@ export class ConversationModel extends window.Backbone
notificationService.add({
senderTitle,
conversationId,
storyId: message.get('storyId'),
notificationIconUrl,
isExpiringMessage,
message: message.getNotificationText(),

View File

@ -2853,10 +2853,34 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
const isFirstRun = false;
await this.modifyTargetMessage(conversation, isFirstRun);
const storyId = this.get('storyId');
const isGroupStoryReply =
isGroup(conversation.attributes) && this.get('storyId');
isGroup(conversation.attributes) && storyId != null;
if (isMessageUnread(this.attributes) && !isGroupStoryReply) {
let shouldNotify = true;
if (!isMessageUnread(this.attributes)) {
shouldNotify = false;
} else if (isGroupStoryReply) {
const match = window.reduxStore.getState().stories.stories.find(story => {
return story.messageId === storyId;
});
const sourceUuid = match?.sourceUuid;
const userUuid = window.textsecure.storage.user.getUuid();
const weAreSource =
sourceUuid != null &&
userUuid != null &&
sourceUuid === userUuid.toString();
// TODO: Check if we're someone else who has replied/reacted to this story
if (!weAreSource) {
shouldNotify = false;
}
}
if (shouldNotify) {
await conversation.notify(this);
}

View File

@ -18,12 +18,13 @@ import type { LocalizerType } from '../types/Util';
type NotificationDataType = Readonly<{
conversationId: string;
storyId?: string;
messageId: string;
senderTitle: string;
message: string;
notificationIconUrl?: undefined | string;
isExpiringMessage: boolean;
reaction: {
reaction?: {
emoji: string;
targetAuthorUuid: string;
targetTimestamp: number;
@ -268,6 +269,7 @@ class NotificationService extends EventEmitter {
const {
conversationId,
storyId,
messageId,
senderTitle,
message,
@ -340,7 +342,7 @@ class NotificationService extends EventEmitter {
message: notificationMessage,
silent: !shouldPlayNotificationSound,
onNotificationClick: () => {
this.emit('click', conversationId, messageId);
this.emit('click', conversationId, messageId, storyId);
},
});
}

View File

@ -14,7 +14,7 @@ import type {
} from './conversations';
import type { NoopActionType } from './noop';
import type { StateType as RootStateType } from '../reducer';
import type { StoryViewType } from '../../types/Stories';
import type { StoryViewTargetType, StoryViewType } from '../../types/Stories';
import type { SyncType } from '../../jobs/helpers/syncHelpers';
import type { UUIDStringType } from '../../types/UUID';
import * as log from '../../logging/log';
@ -86,7 +86,7 @@ export type SelectedStoryDataType = {
currentIndex: number;
messageId: string;
numStories: number;
shouldShowDetailsModal: boolean;
viewTarget?: StoryViewTargetType;
storyViewMode: StoryViewModeType;
};
@ -815,13 +815,13 @@ const getSelectedStoryDataForConversationId = (
export type ViewUserStoriesActionCreatorType = (opts: {
conversationId: string;
shouldShowDetailsModal?: boolean;
viewTarget?: StoryViewTargetType;
storyViewMode?: StoryViewModeType;
}) => unknown;
const viewUserStories: ViewUserStoriesActionCreatorType = ({
conversationId,
shouldShowDetailsModal = false,
viewTarget,
storyViewMode,
}): ThunkAction<void, RootStateType, unknown, ViewStoryActionType> => {
return (dispatch, getState) => {
@ -851,7 +851,7 @@ const viewUserStories: ViewUserStoriesActionCreatorType = ({
currentIndex,
messageId: story.messageId,
numStories,
shouldShowDetailsModal,
viewTarget,
storyViewMode: inferredStoryViewMode,
},
});
@ -866,7 +866,7 @@ type ViewStoryOptionsType =
storyId: string;
storyViewMode: StoryViewModeType;
viewDirection?: StoryViewDirectionType;
shouldShowDetailsModal?: boolean;
viewTarget?: StoryViewTargetType;
};
export type ViewStoryActionCreatorType = (
@ -889,12 +889,7 @@ const viewStory: ViewStoryActionCreatorType = (
return;
}
const {
shouldShowDetailsModal = false,
storyId,
storyViewMode,
viewDirection,
} = opts;
const { viewTarget, storyId, storyViewMode, viewDirection } = opts;
const state = getState();
const { stories } = state.stories;
@ -934,7 +929,7 @@ const viewStory: ViewStoryActionCreatorType = (
currentIndex,
messageId: storyId,
numStories,
shouldShowDetailsModal,
viewTarget,
storyViewMode,
},
});
@ -955,7 +950,6 @@ const viewStory: ViewStoryActionCreatorType = (
currentIndex: nextIndex,
messageId: nextStory.messageId,
numStories,
shouldShowDetailsModal: false,
storyViewMode,
},
});
@ -973,7 +967,6 @@ const viewStory: ViewStoryActionCreatorType = (
currentIndex: nextIndex,
messageId: nextStory.messageId,
numStories,
shouldShowDetailsModal: false,
storyViewMode,
},
});
@ -1022,7 +1015,6 @@ const viewStory: ViewStoryActionCreatorType = (
nextSelectedStoryData.currentIndex
].messageId,
numStories: nextSelectedStoryData.numStories,
shouldShowDetailsModal: false,
storyViewMode,
},
});
@ -1080,7 +1072,6 @@ const viewStory: ViewStoryActionCreatorType = (
currentIndex: 0,
messageId: nextSelectedStoryData.storiesByConversationId[0].messageId,
numStories: nextSelectedStoryData.numStories,
shouldShowDetailsModal: false,
storyViewMode,
},
});
@ -1115,7 +1106,6 @@ const viewStory: ViewStoryActionCreatorType = (
currentIndex: 0,
messageId: nextSelectedStoryData.storiesByConversationId[0].messageId,
numStories: nextSelectedStoryData.numStories,
shouldShowDetailsModal: false,
storyViewMode,
},
});

View File

@ -117,7 +117,7 @@ export function SmartStoryViewer(): JSX.Element | null {
recentEmojis={recentEmojis}
renderEmojiPicker={renderEmojiPicker}
replyState={replyState}
shouldShowDetailsModal={selectedStoryData.shouldShowDetailsModal}
viewTarget={selectedStoryData.viewTarget}
showToast={showToast}
skinTone={skinTone}
story={storyView}

View File

@ -148,7 +148,7 @@ describe('both/state/ducks/stories', () => {
currentIndex: 0,
messageId: storyId,
numStories: 1,
shouldShowDetailsModal: false,
viewTarget: undefined,
storyViewMode: StoryViewModeType.All,
},
});
@ -179,7 +179,6 @@ describe('both/state/ducks/stories', () => {
currentIndex: 1,
messageId: storyId2,
numStories: 3,
shouldShowDetailsModal: false,
storyViewMode: StoryViewModeType.User,
},
});
@ -209,7 +208,6 @@ describe('both/state/ducks/stories', () => {
currentIndex: 0,
messageId: storyId1,
numStories: 3,
shouldShowDetailsModal: false,
storyViewMode: StoryViewModeType.User,
},
});
@ -282,7 +280,6 @@ describe('both/state/ducks/stories', () => {
currentIndex: 0,
messageId: storyId3,
numStories: 1,
shouldShowDetailsModal: false,
storyViewMode: StoryViewModeType.Unread,
},
});
@ -440,7 +437,6 @@ describe('both/state/ducks/stories', () => {
currentIndex: 0,
messageId: storyId2,
numStories: 2,
shouldShowDetailsModal: false,
storyViewMode: StoryViewModeType.All,
},
});
@ -477,7 +473,6 @@ describe('both/state/ducks/stories', () => {
currentIndex: 0,
messageId: storyId1,
numStories: 2,
shouldShowDetailsModal: false,
storyViewMode: StoryViewModeType.All,
},
});

View File

@ -112,6 +112,12 @@ export enum StoryViewDirectionType {
Previous = 'Previous',
}
export enum StoryViewTargetType {
Details = 'Details',
Views = 'Views',
Replies = 'Replies',
}
// Type of stories to view before closing the viewer
// All = All the stories in order
// Single = A single story. Like when clicking on a qouted story

View File

@ -15,27 +15,6 @@
"updated": "2018-09-18T19:19:27.699Z",
"reasonDetail": "Part of runtime library for C++ transpiled code"
},
{
"rule": "React-useRef",
"path": "ts/components/AddCaptionModal.tsx",
"line": " const scrollerRef = React.useRef<HTMLDivElement>(null);",
"reasonCategory": "usageTrusted",
"updated": "2022-10-03T16:06:12.837Z"
},
{
"rule": "React-useRef",
"path": "ts/components/CompositionInput.tsx",
"line": " const scrollerRefInner = React.useRef<HTMLDivElement>(null);",
"reasonCategory": "usageTrusted",
"updated": "2022-10-03T16:06:12.837Z"
},
{
"rule": "React-useRef",
"path": "ts/components/CompositionTextArea.tsx",
"line": " const inputApiRef = React.useRef<InputApi | undefined>();",
"reasonCategory": "usageTrusted",
"updated": "2022-10-03T16:06:12.837Z"
},
{
"rule": "jQuery-append(",
"path": "components/mp3lameencoder/lib/Mp3LameEncoder.js",
@ -8858,6 +8837,13 @@
"reasonCategory": "falseMatch",
"updated": "2021-05-05T23:11:22.692Z"
},
{
"rule": "React-useRef",
"path": "ts/components/AddCaptionModal.tsx",
"line": " const scrollerRef = React.useRef<HTMLDivElement>(null);",
"reasonCategory": "usageTrusted",
"updated": "2022-10-03T16:06:12.837Z"
},
{
"rule": "React-useRef",
"path": "ts/components/AvatarTextEditor.tsx",
@ -9029,6 +9015,20 @@
"updated": "2022-06-25T00:06:19.860Z",
"reasonDetail": "Not used for DOM manipulation"
},
{
"rule": "React-useRef",
"path": "ts/components/CompositionInput.tsx",
"line": " const scrollerRefInner = React.useRef<HTMLDivElement>(null);",
"reasonCategory": "usageTrusted",
"updated": "2022-10-03T16:06:12.837Z"
},
{
"rule": "React-useRef",
"path": "ts/components/CompositionTextArea.tsx",
"line": " const inputApiRef = React.useRef<InputApi | undefined>();",
"reasonCategory": "usageTrusted",
"updated": "2022-10-03T16:06:12.837Z"
},
{
"rule": "React-useRef",
"path": "ts/components/ContactPills.tsx",
@ -9316,13 +9316,6 @@
"reasonCategory": "usageTrusted",
"updated": "2022-08-04T00:52:01.080Z"
},
{
"rule": "React-useRef",
"path": "ts/components/StoryViewsNRepliesModal.tsx",
"line": " const shouldScrollToBottomRef = useRef(false);",
"reasonCategory": "usageTrusted",
"updated": "2022-09-22T03:07:22.153Z"
},
{
"rule": "React-useRef",
"path": "ts/components/TextAttachment.tsx",
@ -9690,5 +9683,19 @@
"line": " message.innerHTML = window.SignalContext.i18n('optimizingApplication');",
"reasonCategory": "usageTrusted",
"updated": "2021-09-17T21:02:59.414Z"
},
{
"rule": "React-useRef",
"path": "ts/components/StoryViewsNRepliesModal.tsx",
"line": " const shouldScrollToBottomRef = useRef(true);",
"reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted",
"updated": "2022-10-05T18:51:56.411Z"
},
{
"rule": "React-useRef",
"path": "ts/components/StoryViewsNRepliesModal.tsx",
"line": " const bottomRef = useRef<HTMLDivElement>(null);",
"reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted",
"updated": "2022-10-05T18:51:56.411Z"
}
]