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(