Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F33394421
D7380.1768975954.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
7 KB
Referenced Files
None
Subscribers
None
D7380.1768975954.diff
View Options
diff --git a/web/chat/thread-top-bar.react.js b/web/chat/thread-top-bar.react.js
--- a/web/chat/thread-top-bar.react.js
+++ b/web/chat/thread-top-bar.react.js
@@ -3,6 +3,7 @@
import * as React from 'react';
import { ChevronRight } from 'react-feather';
+import { useModalContext } from 'lib/components/modal-provider.react.js';
import { threadIsPending } from 'lib/shared/thread-utils.js';
import type { ThreadInfo } from 'lib/types/thread-types.js';
import { useResolvedThreadInfo } from 'lib/utils/entity-helpers.js';
@@ -10,6 +11,8 @@
import ThreadMenu from './thread-menu.react.js';
import css from './thread-top-bar.css';
import ThreadAvatar from '../components/thread-avatar.react.js';
+import { InputStateContext } from '../input/input-state.js';
+import ThreadPinnedMessagesModal from '../modals/chat/thread-pinned-messages-modal.react.js';
import { shouldRenderAvatars } from '../utils/avatar-utils.js';
type ThreadTopBarProps = {
@@ -17,6 +20,7 @@
};
function ThreadTopBar(props: ThreadTopBarProps): React.Node {
const { threadInfo } = props;
+ const { pushModal } = useModalContext();
const threadBackgroundColorStyle = React.useMemo(
() => ({
background: `#${threadInfo.color}`,
@@ -42,6 +46,18 @@
return `${threadInfo.pinnedCount} pinned ${messageNoun}`;
}, [threadInfo.pinnedCount]);
+ const inputState = React.useContext(InputStateContext);
+ const pushThreadPinsModal = React.useCallback(() => {
+ pushModal(
+ <InputStateContext.Provider value={inputState}>
+ <ThreadPinnedMessagesModal
+ threadInfo={threadInfo}
+ modalName={bannerText}
+ />
+ </InputStateContext.Provider>,
+ );
+ }, [pushModal, inputState, threadInfo, bannerText]);
+
const pinnedCountBanner = React.useMemo(() => {
if (!bannerText) {
return null;
@@ -49,13 +65,13 @@
return (
<div className={css.pinnedCountBanner}>
- <a className={css.pinnedCountText}>
+ <a className={css.pinnedCountText} onClick={pushThreadPinsModal}>
{bannerText}
<ChevronRight size={14} className={css.chevronRight} />
</a>
</div>
);
- }, [bannerText]);
+ }, [bannerText, pushThreadPinsModal]);
const { uiName } = useResolvedThreadInfo(threadInfo);
diff --git a/web/modals/chat/thread-pinned-messages-modal.css b/web/modals/chat/thread-pinned-messages-modal.css
new file mode 100644
--- /dev/null
+++ b/web/modals/chat/thread-pinned-messages-modal.css
@@ -0,0 +1,12 @@
+hr.separator {
+ border: 0;
+ margin: 20px 0;
+ width: 100%;
+ height: 2px;
+ border: none;
+ border-top: var(--shades-black-70) solid 1px;
+}
+
+.pinnedMessagesContainer {
+ overflow-y: scroll;
+}
diff --git a/web/modals/chat/thread-pinned-messages-modal.react.js b/web/modals/chat/thread-pinned-messages-modal.react.js
new file mode 100644
--- /dev/null
+++ b/web/modals/chat/thread-pinned-messages-modal.react.js
@@ -0,0 +1,137 @@
+// @flow
+
+import invariant from 'invariant';
+import * as React from 'react';
+
+import { fetchPinnedMessages } from 'lib/actions/message-actions.js';
+import { useModalContext } from 'lib/components/modal-provider.react.js';
+import { messageListData } from 'lib/selectors/chat-selectors.js';
+import { createMessageInfo } from 'lib/shared/message-utils.js';
+import { type ThreadInfo } from 'lib/types/thread-types.js';
+import { useServerCall } from 'lib/utils/action-utils.js';
+
+import css from './thread-pinned-messages-modal.css';
+import PinnedMessage from '../../components/pinned-message.react.js';
+import { useSelector } from '../../redux/redux-utils.js';
+import Modal from '../modal.react.js';
+
+type ThreadPinnedMessagesModalProps = {
+ +threadInfo: ThreadInfo,
+ +modalName: string,
+};
+
+function ThreadPinnedMessagesModal(
+ props: ThreadPinnedMessagesModalProps,
+): React.Node {
+ const { threadInfo, modalName } = props;
+ const { id: threadID } = threadInfo;
+ const { popModal } = useModalContext();
+ const [rawPinnedMessages, setRawPinnedMessages] = React.useState([]);
+ const callFetchPinnedMessages = useServerCall(fetchPinnedMessages);
+
+ const userInfos = useSelector(state => state.userStore.userInfos);
+
+ React.useEffect(() => {
+ (async () => {
+ const result = await callFetchPinnedMessages({ threadID });
+ setRawPinnedMessages(result.pinnedMessages);
+ })();
+ }, [callFetchPinnedMessages, threadID]);
+
+ const translatedPinnedMessageInfos = React.useMemo(() => {
+ const threadInfos = { [threadID]: threadInfo };
+
+ return rawPinnedMessages
+ .map(messageInfo =>
+ createMessageInfo(messageInfo, null, userInfos, threadInfos),
+ )
+ .filter(Boolean);
+ }, [rawPinnedMessages, userInfos, threadID, threadInfo]);
+
+ const chatMessageInfos = useSelector(
+ messageListData(threadInfo.id, translatedPinnedMessageInfos),
+ );
+
+ const sortedUniqueChatMessageInfoItems = React.useMemo(() => {
+ if (!chatMessageInfos) {
+ return [];
+ }
+
+ const chatMessageInfoItems = chatMessageInfos.filter(
+ item => item.itemType === 'message' && item.isPinned,
+ );
+
+ // By the nature of using messageListData and passing in
+ // the desired translatedPinnedMessageInfos as additional
+ // messages, we will have duplicate ChatMessageInfoItems.
+ const uniqueChatMessageInfoItemsMap = new Map();
+ chatMessageInfoItems.map(
+ item =>
+ item.messageInfo &&
+ item.messageInfo.id &&
+ uniqueChatMessageInfoItemsMap.set(item.messageInfo.id, item),
+ );
+
+ // Sort uniqueChatMessageInfoItems based on the order of
+ // their appearance in rawPinnedMessages (since the messages from the server
+ // are already sorted by their pin_time in descending order).
+ const sortedChatMessageInfoItems = [];
+ for (let i = 0; i < rawPinnedMessages.length; i++) {
+ sortedChatMessageInfoItems.push(
+ uniqueChatMessageInfoItemsMap.get(rawPinnedMessages[i].id),
+ );
+ }
+
+ return sortedChatMessageInfoItems;
+ }, [chatMessageInfos, rawPinnedMessages]);
+
+ const modifiedItems = React.useMemo(
+ () =>
+ sortedUniqueChatMessageInfoItems.map(item => {
+ invariant(item, 'item should not be null');
+
+ // We need to modify the item to make sure that the message does
+ // not render with the date header and that the creator
+ // is not considered the viewer.
+ let modifiedItem = item;
+ if (item.messageInfoType === 'composable') {
+ modifiedItem = {
+ ...item,
+ startsConversation: false,
+ messageInfo: {
+ ...item.messageInfo,
+ creator: {
+ ...item.messageInfo.creator,
+ isViewer: false,
+ },
+ },
+ };
+ }
+ return modifiedItem;
+ }),
+ [sortedUniqueChatMessageInfoItems],
+ );
+
+ const pinnedMessagesToDisplay = React.useMemo(
+ () =>
+ modifiedItems.map(item => (
+ <PinnedMessage
+ key={item.messageInfo.id}
+ item={item}
+ threadInfo={threadInfo}
+ />
+ )),
+ [modifiedItems, threadInfo],
+ );
+
+ return (
+ <Modal name={modalName} onClose={popModal} size="large">
+ <hr className={css.separator} />
+ <div className={css.pinnedMessagesContainer}>
+ {pinnedMessagesToDisplay}
+ </div>
+ </Modal>
+ );
+}
+
+export default ThreadPinnedMessagesModal;
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Jan 21, 6:12 AM (14 h, 45 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5966981
Default Alt Text
D7380.1768975954.diff (7 KB)
Attached To
Mode
D7380: [web] Display the pinned messages for the thread when the banner is clicked
Attached
Detach File
Event Timeline
Log In to Comment