diff --git a/web/components/pinned-message.css b/web/components/pinned-message.css
new file mode 100644
--- /dev/null
+++ b/web/components/pinned-message.css
@@ -0,0 +1,28 @@
+.messageContainer {
+ overflow-y: scroll;
+ border: 1px solid var(--pin-message-modal-border-color);
+ border-radius: 7px;
+ max-height: 400px;
+ margin: 16px 32px 16px 32px;
+}
+
+.messageDate {
+ color: var(--chat-timestamp-color);
+ font-size: var(--xs-font-12);
+ padding: 6px 0;
+ line-height: var(--line-height-text);
+ text-align: left;
+ margin-left: 16px;
+}
+
+.creator {
+ font-size: small;
+ color: #777777;
+ font-size: 14px;
+ padding: 4px 24px;
+ text-align: left;
+}
+
+.messageContent {
+ margin-bottom: -8px;
+}
diff --git a/web/components/pinned-message.react.js b/web/components/pinned-message.react.js
new file mode 100644
--- /dev/null
+++ b/web/components/pinned-message.react.js
@@ -0,0 +1,57 @@
+// @flow
+
+import * as React from 'react';
+
+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 { useStringForUser } from 'lib/hooks/ens-cache.js';
+
+import { useTextMessageRulesFunc } from '../markdown/rules.react.js';
+import { MessageListContext } from '../chat/message-list-types.js';
+import Message from '../chat/message.react.js';
+import css from './pinned-message.css';
+
+type PinnedMessageProps = {
+ +item: ChatMessageInfoItem,
+ +threadInfo: ThreadInfo,
+};
+
+function PinnedMessage(props: PinnedMessageProps): React.Node {
+ const { item, threadInfo } = 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,
+ );
+
+ return (
+
+
+
{username}
+
+
+
+
+
+
+ {longAbsoluteDate(item.messageInfo.time)}
+
+
+
+ );
+}
+
+export default PinnedMessage;
diff --git a/web/modals/chat/toggle-pin-modal.css b/web/modals/chat/toggle-pin-modal.css
--- a/web/modals/chat/toggle-pin-modal.css
+++ b/web/modals/chat/toggle-pin-modal.css
@@ -0,0 +1,26 @@
+.confirmationText {
+ color: var(--pin-message-information-text-color);
+ padding: 16px 32px 0 32px;
+ font-size: small;
+}
+
+.buttonContainer {
+ width: 87%;
+ display: flex;
+ flex-direction: column;
+ align-self: center;
+ align-items: stretch;
+ margin-bottom: 16px;
+}
+
+.cancelButton {
+ color: white;
+ display: flex;
+ justify-content: center;
+ margin-top: 16px;
+}
+
+.cancelButton:hover {
+ cursor: pointer;
+ text-decoration: underline;
+}
diff --git a/web/modals/chat/toggle-pin-modal.react.js b/web/modals/chat/toggle-pin-modal.react.js
--- a/web/modals/chat/toggle-pin-modal.react.js
+++ b/web/modals/chat/toggle-pin-modal.react.js
@@ -1,18 +1,107 @@
// @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 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 PinnedMessage from '../../components/pinned-message.react.js';
+import Modal from '../modal.react.js';
type TogglePinModalProps = {
+item: ChatMessageInfoItem,
+threadInfo: ThreadInfo,
};
-// eslint-disable-next-line no-unused-vars
function TogglePinModal(props: TogglePinModalProps): React.Node {
- return <>>;
+ const { item, threadInfo } = props;
+ const { messageInfo, isPinned } = item;
+ const { popModal } = useModalContext();
+
+ const callToggleMessagePin = useServerCall(toggleMessagePin);
+ const dispatchActionPromise = useDispatchActionPromise();
+
+ const modalName = isPinned ? 'Remove Pinned Message' : 'Pin Message';
+ const action = isPinned ? 'unpin' : 'pin';
+ const confirmationText = isPinned
+ ? `Are you sure you want to remove this pinned message?`
+ : `You may pin this message to the channel you are currently viewing.
+ To unpin a message, select the pinned messages icon in the channel. `;
+ const buttonText = isPinned ? 'Remove Pinned Message' : 'Pin Message';
+ const buttonColor = isPinned ? buttonThemes.danger : buttonThemes.standard;
+
+ // 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,
+ threadCreatedFromMessage: undefined,
+ reactions: {},
+ startsConversation: false,
+ messageInfo: {
+ ...item.messageInfo,
+ creator: {
+ ...item.messageInfo.creator,
+ isViewer: false,
+ },
+ },
+ };
+ } else {
+ return item;
+ }
+ }, [item]);
+
+ const onClick = React.useCallback(() => {
+ const toggleMessagePinPromise = (async () => {
+ invariant(messageInfo.id, 'messageInfo.id should be defined');
+ const result = await callToggleMessagePin({
+ messageID: messageInfo.id,
+ action,
+ });
+ return {
+ newMessageInfos: result.newMessageInfos,
+ threadID: result.threadID,
+ };
+ })();
+
+ dispatchActionPromise(toggleMessagePinActionTypes, toggleMessagePinPromise);
+ popModal();
+ }, [
+ action,
+ callToggleMessagePin,
+ dispatchActionPromise,
+ messageInfo.id,
+ popModal,
+ ]);
+
+ return (
+
+ {confirmationText}
+
+
+
+
+ Cancel
+
+
+
+ );
}
export default TogglePinModal;
diff --git a/web/theme.css b/web/theme.css
--- a/web/theme.css
+++ b/web/theme.css
@@ -212,4 +212,6 @@
--topbar-button-fg: var(--shades-white-60);
--message-label-color: var(--shades-black-60);
--topbar-lines: rgba(255, 255, 255, 0.08);
+ --pin-message-information-text-color: var(--shades-white-60);
+ --pin-message-modal-border-color: var(--shades-black-80);
}
diff --git a/web/utils/tooltip-utils.js b/web/utils/tooltip-utils.js
--- a/web/utils/tooltip-utils.js
+++ b/web/utils/tooltip-utils.js
@@ -495,6 +495,8 @@
isComposableMessageType(messageInfo.type) &&
threadHasPermission(threadInfo, threadPermissions.MANAGE_PINS);
+ const inputState = React.useContext(InputStateContext);
+
return React.useMemo(() => {
if (!canTogglePin) {
return null;
@@ -505,7 +507,11 @@
const buttonContent = ;
const onClickTogglePin = () => {
- pushModal();
+ pushModal(
+
+
+ ,
+ );
};
return {
@@ -513,7 +519,7 @@
onClick: onClickTogglePin,
label: isPinned ? 'Unpin' : 'Pin',
};
- }, [canTogglePin, isPinned, pushModal, item, threadInfo]);
+ }, [canTogglePin, inputState, isPinned, pushModal, item, threadInfo]);
}
function useMessageTooltipActions(