diff --git a/web/components/message-result.css b/web/components/message-result.css
index c655a60ab..785c9fa94 100644
--- a/web/components/message-result.css
+++ b/web/components/message-result.css
@@ -1,28 +1,31 @@
.messageContainer {
- overflow-y: scroll;
border: 1px solid var(--pin-message-modal-border-color);
border-radius: 7px;
- max-height: 400px;
margin: 0 32px 16px 32px;
}
+.messageContainerOverflow {
+ overflow-y: scroll;
+ max-height: 400px;
+}
+
.messageDate {
color: var(--chat-timestamp-color);
font-size: var(--xs-font-12);
padding: 0px 0px 6px 0px;
line-height: var(--line-height-text);
text-align: left;
margin-left: 16px;
}
.creator {
font-size: small;
color: var(--shades-white-60);
font-size: var(--s-font-14);
padding: 4px 24px;
text-align: left;
}
.messageContent {
margin-bottom: 1px;
}
diff --git a/web/components/message-result.react.js b/web/components/message-result.react.js
index 0858eae41..ae0ecd4cf 100644
--- a/web/components/message-result.react.js
+++ b/web/components/message-result.react.js
@@ -1,58 +1,65 @@
// @flow
+import classNames from 'classnames';
import * as React from 'react';
import { useStringForUser } from 'lib/hooks/ens-cache.js';
import type { ChatMessageInfoItem } from 'lib/selectors/chat-selectors.js';
import type { ThreadInfo } from 'lib/types/thread-types.js';
import { longAbsoluteDate } from 'lib/utils/date-utils.js';
import css from './message-result.css';
import { MessageListContext } from '../chat/message-list-types.js';
import Message from '../chat/message.react.js';
import { useTextMessageRulesFunc } from '../markdown/rules.react.js';
type MessageResultProps = {
+item: ChatMessageInfoItem,
+threadInfo: ThreadInfo,
+ +scrollable: boolean,
};
function MessageResult(props: MessageResultProps): React.Node {
- const { item, threadInfo } = props;
+ const { item, threadInfo, scrollable } = props;
const getTextMessageMarkdownRules = useTextMessageRulesFunc(threadInfo);
const messageListContext = React.useMemo(() => {
if (!getTextMessageMarkdownRules) {
return undefined;
}
return { getTextMessageMarkdownRules };
}, [getTextMessageMarkdownRules]);
const shouldShowUsername = !item.startsConversation && !item.startsCluster;
const username = useStringForUser(
shouldShowUsername ? item.messageInfo.creator : null,
);
+ const messageContainerClassNames = classNames({
+ [css.messageContainer]: true,
+ [css.messageContainerOverflow]: scrollable,
+ });
+
return (
-
+
{username}
{longAbsoluteDate(item.messageInfo.time)}
);
}
export default MessageResult;
diff --git a/web/modals/chat/message-results-modal.react.js b/web/modals/chat/message-results-modal.react.js
index a079ef967..bea2be3c5 100644
--- a/web/modals/chat/message-results-modal.react.js
+++ b/web/modals/chat/message-results-modal.react.js
@@ -1,152 +1,153 @@
// @flow
import * as React from 'react';
import {
fetchPinnedMessages,
fetchPinnedMessageActionTypes,
} from 'lib/actions/message-actions.js';
import { useModalContext } from 'lib/components/modal-provider.react.js';
import { messageListData } from 'lib/selectors/chat-selectors.js';
import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js';
import {
createMessageInfo,
modifyItemForResultScreen,
} from 'lib/shared/message-utils.js';
import { type ThreadInfo } from 'lib/types/thread-types.js';
import {
useServerCall,
useDispatchActionPromise,
} from 'lib/utils/action-utils.js';
import css from './message-results-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 MessageResultsModalProps = {
+threadInfo: ThreadInfo,
+modalName: string,
};
const loadingStatusSelector = createLoadingStatusSelector(
fetchPinnedMessageActionTypes,
);
function MessageResultsModal(props: MessageResultsModalProps): React.Node {
const { threadInfo, modalName } = props;
const { id: threadID } = threadInfo;
const { popModal } = useModalContext();
const [rawMessageResults, setRawMessageResults] = React.useState([]);
const callFetchPinnedMessages = useServerCall(fetchPinnedMessages);
const dispatchActionPromise = useDispatchActionPromise();
const userInfos = useSelector(state => state.userStore.userInfos);
const loadingStatus = useSelector(loadingStatusSelector);
React.useEffect(() => {
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 [];
}
const pinnedMessageIDs = new Set();
translatedMessageResults.forEach(item => pinnedMessageIDs.add(item.id));
const chatMessageInfoItems = chatMessageInfos.filter(
item =>
item.itemType === 'message' &&
pinnedMessageIDs.has(item.messageInfo.id),
);
// By the nature of using messageListData and passing in
// the desired translatedMessageResults as additional
// messages, we will have duplicate ChatMessageInfoItems.
const uniqueChatMessageInfoItemsMap = new Map();
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++) {
sortedChatMessageInfoItems.push(
uniqueChatMessageInfoItemsMap.get(rawMessageResults[i].id),
);
}
return sortedChatMessageInfoItems;
}, [translatedMessageResults, chatMessageInfos, rawMessageResults]);
const modifiedItems = React.useMemo(
() =>
sortedUniqueChatMessageInfoItems
.filter(Boolean)
.map(item => modifyItemForResultScreen(item)),
[sortedUniqueChatMessageInfoItems],
);
const messageResultsToDisplay = React.useMemo(() => {
const items = modifiedItems.map(item => (
));
return <>{items}>;
}, [modifiedItems, threadInfo]);
const loadingIndicator = React.useMemo(() => {
if (loadingStatus === 'loading') {
return (
);
}
return null;
}, [loadingStatus]);
return (
{loadingIndicator}
{messageResultsToDisplay}
);
}
export default MessageResultsModal;
diff --git a/web/modals/chat/toggle-pin-modal.react.js b/web/modals/chat/toggle-pin-modal.react.js
index a645ba7af..4816746eb 100644
--- a/web/modals/chat/toggle-pin-modal.react.js
+++ b/web/modals/chat/toggle-pin-modal.react.js
@@ -1,123 +1,127 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import {
toggleMessagePin,
toggleMessagePinActionTypes,
} from 'lib/actions/thread-actions.js';
import { useModalContext } from 'lib/components/modal-provider.react.js';
import type { ChatMessageInfoItem } from 'lib/selectors/chat-selectors.js';
import { modifyItemForResultScreen } from 'lib/shared/message-utils.js';
import type { ThreadInfo } from 'lib/types/thread-types.js';
import {
useServerCall,
useDispatchActionPromise,
} from 'lib/utils/action-utils.js';
import css from './toggle-pin-modal.css';
import Button, { buttonThemes } from '../../components/button.react.js';
import MessageResult from '../../components/message-result.react.js';
import Modal from '../modal.react.js';
type TogglePinModalProps = {
+item: ChatMessageInfoItem,
+threadInfo: ThreadInfo,
};
function TogglePinModal(props: TogglePinModalProps): React.Node {
const { item, threadInfo } = props;
const { messageInfo, isPinned } = item;
const { popModal } = useModalContext();
const callToggleMessagePin = useServerCall(toggleMessagePin);
const dispatchActionPromise = useDispatchActionPromise();
const modalInfo = React.useMemo(() => {
if (isPinned) {
return {
name: 'Remove Pinned Message',
action: 'unpin',
confirmationText:
'Are you sure you want to remove this pinned message?',
buttonText: 'Remove Pinned Message',
buttonColor: buttonThemes.danger,
};
}
return {
name: 'Pin Message',
action: 'pin',
confirmationText: `You may pin this message to the channel
you are currently viewing. To unpin a message, select the pinned
messages icon in the channel.`,
buttonText: 'Pin Message',
buttonColor: buttonThemes.standard,
};
}, [isPinned]);
// We want to remove inline engagement (threadCreatedFromMessage / reactions)
// and the message header (startsConversation). We also want to set isViewer
// to false so that the message is left-aligned and uncolored.
const modifiedItem = React.useMemo(() => {
if (item.messageInfoType !== 'composable') {
return item;
}
const strippedItem = {
...item,
threadCreatedFromMessage: undefined,
reactions: {},
};
return modifyItemForResultScreen(strippedItem);
}, [item]);
const onClick = React.useCallback(() => {
const createToggleMessagePinPromise = async () => {
invariant(messageInfo.id, 'messageInfo.id should be defined');
const result = await callToggleMessagePin({
messageID: messageInfo.id,
action: modalInfo.action,
});
return {
newMessageInfos: result.newMessageInfos,
threadID: result.threadID,
};
};
dispatchActionPromise(
toggleMessagePinActionTypes,
createToggleMessagePinPromise(),
);
popModal();
}, [
modalInfo,
callToggleMessagePin,
dispatchActionPromise,
messageInfo.id,
popModal,
]);
return (
{modalInfo.confirmationText}
-
+
Cancel
);
}
export default TogglePinModal;