diff --git a/web/chat/thread-top-bar.react.js b/web/chat/thread-top-bar.react.js
index e0034de90..a5fdfe29f 100644
--- a/web/chat/thread-top-bar.react.js
+++ b/web/chat/thread-top-bar.react.js
@@ -1,96 +1,95 @@
// @flow
import * as React from 'react';
import { ChevronRight } from 'react-feather';
import { useModalContext } from 'lib/components/modal-provider.react.js';
import SWMansionIcon from 'lib/components/SWMansionIcon.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';
import { pinnedMessageCountText } from 'lib/utils/message-pinning-utils.js';
import ThreadMenu from './thread-menu.react.js';
import css from './thread-top-bar.css';
import ThreadAvatar from '../avatars/thread-avatar.react.js';
import Button from '../components/button.react.js';
import { InputStateContext } from '../input/input-state.js';
import PinnedMessagesModal from '../modals/chat/pinned-messages-modal.react.js';
import MessageSearchModal from '../modals/search/message-search-modal.react.js';
type ThreadTopBarProps = {
+threadInfo: ThreadInfo,
};
function ThreadTopBar(props: ThreadTopBarProps): React.Node {
const { threadInfo } = props;
const { pushModal } = useModalContext();
let threadMenu = null;
if (!threadIsPending(threadInfo.id)) {
threadMenu = ;
}
- const bannerText = threadInfo.pinnedCount
- ? pinnedMessageCountText(threadInfo.pinnedCount)
- : '';
+ const bannerText =
+ !!threadInfo.pinnedCount && pinnedMessageCountText(threadInfo.pinnedCount);
const inputState = React.useContext(InputStateContext);
const pushThreadPinsModal = React.useCallback(() => {
pushModal(
-
+
,
);
- }, [pushModal, inputState, threadInfo, bannerText]);
+ }, [pushModal, inputState, threadInfo]);
const pinnedCountBanner = React.useMemo(() => {
if (!bannerText) {
return null;
}
return (
);
}, [bannerText, pushThreadPinsModal]);
const onClickSearch = React.useCallback(
() =>
pushModal(
,
),
[inputState, pushModal, threadInfo],
);
const { uiName } = useResolvedThreadInfo(threadInfo);
return (
<>
{pinnedCountBanner}
>
);
}
export default ThreadTopBar;
diff --git a/web/modals/chat/pinned-messages-modal.css b/web/modals/chat/pinned-messages-modal.css
index e0613625a..a02cd5bc4 100644
--- a/web/modals/chat/pinned-messages-modal.css
+++ b/web/modals/chat/pinned-messages-modal.css
@@ -1,28 +1,35 @@
hr.separator {
border: 0;
margin: 20px 0 0 0;
width: 100%;
height: 2px;
border: none;
border-top: var(--shades-black-60) solid 1px;
}
.messageResultsContainer {
overflow-y: scroll;
padding: 0 32px 8px 32px;
}
.messageResultsContainer > * {
margin-bottom: 16px;
}
.loadingIndicator {
text-align: center;
}
.topSpace {
height: 48px;
align-items: center;
justify-content: center;
display: flex;
}
+
+.noPinnedMessages {
+ color: var(--text-background-tertiary-default);
+ display: flex;
+ flex: 1;
+ justify-content: center;
+}
diff --git a/web/modals/chat/pinned-messages-modal.react.js b/web/modals/chat/pinned-messages-modal.react.js
index 31d45f706..5d4799032 100644
--- a/web/modals/chat/pinned-messages-modal.react.js
+++ b/web/modals/chat/pinned-messages-modal.react.js
@@ -1,162 +1,172 @@
// @flow
import * as React from 'react';
import {
fetchPinnedMessageActionTypes,
useFetchPinnedMessages,
} from 'lib/actions/message-actions.js';
import { useModalContext } from 'lib/components/modal-provider.react.js';
import {
messageListData,
type ChatMessageInfoItem,
} from 'lib/selectors/chat-selectors.js';
import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js';
import {
createMessageInfo,
isInvalidPinSourceForThread,
modifyItemForResultScreen,
} from 'lib/shared/message-utils.js';
import type { RawMessageInfo } from 'lib/types/message-types.js';
import { type ThreadInfo } from 'lib/types/thread-types.js';
import { useDispatchActionPromise } from 'lib/utils/action-utils.js';
+import { pinnedMessageCountText } from 'lib/utils/message-pinning-utils.js';
import css from './pinned-messages-modal.css';
import MessageResult from '../../components/message-result.react.js';
import LoadingIndicator from '../../loading-indicator.react.js';
import { useSelector } from '../../redux/redux-utils.js';
import Modal from '../modal.react.js';
type Props = {
+threadInfo: ThreadInfo,
- +modalName: string,
};
const loadingStatusSelector = createLoadingStatusSelector(
fetchPinnedMessageActionTypes,
);
function PinnedMessagesModal(props: Props): React.Node {
- const { threadInfo, modalName } = props;
+ const { threadInfo } = props;
const { id: threadID } = threadInfo;
const { popModal } = useModalContext();
const [rawMessageResults, setRawMessageResults] = React.useState<
$ReadOnlyArray,
>([]);
const callFetchPinnedMessages = useFetchPinnedMessages();
const dispatchActionPromise = useDispatchActionPromise();
const userInfos = useSelector(state => state.userStore.userInfos);
const loadingStatus = useSelector(loadingStatusSelector);
React.useEffect(() => {
void dispatchActionPromise(
fetchPinnedMessageActionTypes,
(async () => {
const result = await callFetchPinnedMessages({ threadID });
setRawMessageResults(result.pinnedMessages);
})(),
);
}, [dispatchActionPromise, callFetchPinnedMessages, threadID]);
const translatedMessageResults = React.useMemo(() => {
const threadInfos = { [threadID]: threadInfo };
return rawMessageResults
.map(messageInfo =>
createMessageInfo(messageInfo, null, userInfos, threadInfos),
)
.filter(Boolean);
}, [rawMessageResults, userInfos, threadID, threadInfo]);
const chatMessageInfos = useSelector(
messageListData(threadInfo.id, translatedMessageResults),
);
const sortedUniqueChatMessageInfoItems = React.useMemo(() => {
if (!chatMessageInfos) {
return ([]: ChatMessageInfoItem[]);
}
const chatMessageInfoItems = chatMessageInfos.filter(
item =>
item.itemType === 'message' &&
item.isPinned &&
!isInvalidPinSourceForThread(item.messageInfo, threadInfo),
);
// By the nature of using messageListData and passing in
// the desired translatedMessageResults as additional
// messages, we will have duplicate ChatMessageInfoItems.
const uniqueChatMessageInfoItemsMap = new Map<
string,
ChatMessageInfoItem,
>();
chatMessageInfoItems.forEach(
item =>
item.messageInfo &&
item.messageInfo.id &&
uniqueChatMessageInfoItemsMap.set(item.messageInfo.id, item),
);
// Push the items in the order they appear in the rawMessageResults
// since the messages fetched from the server are already sorted
// in the order of pin_time (newest first).
const sortedChatMessageInfoItems = [];
for (let i = 0; i < rawMessageResults.length; i++) {
const rawMessageID = rawMessageResults[i].id;
if (!rawMessageID) {
continue;
}
sortedChatMessageInfoItems.push(
uniqueChatMessageInfoItemsMap.get(rawMessageID),
);
}
return sortedChatMessageInfoItems;
}, [chatMessageInfos, rawMessageResults, threadInfo]);
const modifiedItems = React.useMemo(
() =>
sortedUniqueChatMessageInfoItems
.filter(Boolean)
.map(item => modifyItemForResultScreen(item)),
[sortedUniqueChatMessageInfoItems],
);
const messageResultsToDisplay = React.useMemo(() => {
+ if (modifiedItems.length === 0) {
+ return (
+
+ No pinned messages in this thread.
+
+ );
+ }
+
const items = modifiedItems.map(item => (
));
return <>{items}>;
}, [modifiedItems, threadInfo]);
const loadingIndicator = React.useMemo(() => {
if (loadingStatus === 'loading') {
return (
);
}
return null;
}, [loadingStatus]);
+ const modalName = pinnedMessageCountText(modifiedItems.length);
+
return (
{loadingIndicator}
{messageResultsToDisplay}
);
}
export default PinnedMessagesModal;