Allow unpinning all pinned messages

This commit is contained in:
Jamie
2025-12-16 07:39:50 -08:00
committed by GitHub
parent 7df788814c
commit 377d272841
3 changed files with 79 additions and 2 deletions

View File

@@ -1750,6 +1750,22 @@
"messageformat": "Unpin all messages", "messageformat": "Unpin all messages",
"description": "Conversation > Pinned messages panel (view all) > Unpin all messages button" "description": "Conversation > Pinned messages panel (view all) > Unpin all messages button"
}, },
"icu:PinnedMessagesPanel__UnpinAllMessages__ConfirmDialog__Title": {
"messageformat": "Unpin all messages?",
"description": "Conversation > Pinned messages panel (view all) > Unpin all messages button > Confirm Dialog > Title"
},
"icu:PinnedMessagesPanel__UnpinAllMessages__ConfirmDialog__Description--Group": {
"messageformat": "Messages will be unpinned for all group members.",
"description": "Conversation > Pinned messages panel (view all) > Unpin all messages button > Confirm Dialog > Description (in group)"
},
"icu:PinnedMessagesPanel__UnpinAllMessages__ConfirmDialog__Cancel": {
"messageformat": "Cancel",
"description": "Conversation > Pinned messages panel (view all) > Unpin all messages button > Confirm Dialog > Cancel"
},
"icu:PinnedMessagesPanel__UnpinAllMessages__ConfirmDialog__Unpin": {
"messageformat": "Unpin",
"description": "Conversation > Pinned messages panel (view all) > Unpin all messages button > Confirm Dialog > Unpin"
},
"icu:sessionEnded": { "icu:sessionEnded": {
"messageformat": "Secure session reset", "messageformat": "Secure session reset",
"description": "This is a past tense, informational message. In other words, your secure session has been reset." "description": "This is a past tense, informational message. In other words, your secure session has been reset."

View File

@@ -5,6 +5,7 @@ import React, {
forwardRef, forwardRef,
Fragment, Fragment,
memo, memo,
useCallback,
useMemo, useMemo,
useRef, useRef,
useState, useState,
@@ -26,12 +27,14 @@ import { useSizeObserver } from '../../../hooks/useSizeObserver.dom.js';
import { MessageInteractivity } from '../Message.dom.js'; import { MessageInteractivity } from '../Message.dom.js';
import { tw } from '../../../axo/tw.dom.js'; import { tw } from '../../../axo/tw.dom.js';
import { AxoButton } from '../../../axo/AxoButton.dom.js'; import { AxoButton } from '../../../axo/AxoButton.dom.js';
import { AxoAlertDialog } from '../../../axo/AxoAlertDialog.dom.js';
export type PinnedMessagesPanelProps = Readonly<{ export type PinnedMessagesPanelProps = Readonly<{
i18n: LocalizerType; i18n: LocalizerType;
conversation: ConversationType; conversation: ConversationType;
pinnedMessages: ReadonlyArray<PinnedMessageRenderData>; pinnedMessages: ReadonlyArray<PinnedMessageRenderData>;
canPinMessages: boolean; canPinMessages: boolean;
onPinnedMessageRemoveAll: () => void;
renderTimelineItem: (props: SmartTimelineItemProps) => JSX.Element; renderTimelineItem: (props: SmartTimelineItemProps) => JSX.Element;
}>; }>;
@@ -44,6 +47,13 @@ export const PinnedMessagesPanel = memo(function PinnedMessagesPanel(
WidthBreakpoint.Wide WidthBreakpoint.Wide
); );
const [confirmUnpinAllDialogOpen, setConfirmUnpinAllDialogOpen] =
useState(false);
const handleClickUnpinAll = useCallback(() => {
setConfirmUnpinAllDialogOpen(true);
}, []);
useLayoutEffect(() => { useLayoutEffect(() => {
strictAssert(containerElementRef.current, 'Missing container ref'); strictAssert(containerElementRef.current, 'Missing container ref');
const container = containerElementRef.current; const container = containerElementRef.current;
@@ -81,11 +91,49 @@ export const PinnedMessagesPanel = memo(function PinnedMessagesPanel(
</ScrollArea> </ScrollArea>
{props.canPinMessages && ( {props.canPinMessages && (
<div className={tw('flex items-center justify-center p-2.5')}> <div className={tw('flex items-center justify-center p-2.5')}>
<AxoButton.Root variant="borderless-primary" size="lg"> <AxoButton.Root
variant="borderless-primary"
size="lg"
onClick={handleClickUnpinAll}
>
{i18n('icu:PinnedMessagesPanel__UnpinAllMessages')} {i18n('icu:PinnedMessagesPanel__UnpinAllMessages')}
</AxoButton.Root> </AxoButton.Root>
</div> </div>
)} )}
<AxoAlertDialog.Root
open={confirmUnpinAllDialogOpen}
onOpenChange={setConfirmUnpinAllDialogOpen}
>
<AxoAlertDialog.Content escape="cancel-is-noop">
<AxoAlertDialog.Body>
<AxoAlertDialog.Title>
{i18n(
'icu:PinnedMessagesPanel__UnpinAllMessages__ConfirmDialog__Title'
)}
</AxoAlertDialog.Title>
<AxoAlertDialog.Description>
{i18n(
'icu:PinnedMessagesPanel__UnpinAllMessages__ConfirmDialog__Description--Group'
)}
</AxoAlertDialog.Description>
</AxoAlertDialog.Body>
<AxoAlertDialog.Footer>
<AxoAlertDialog.Cancel>
{i18n(
'icu:PinnedMessagesPanel__UnpinAllMessages__ConfirmDialog__Cancel'
)}
</AxoAlertDialog.Cancel>
<AxoAlertDialog.Action
variant="primary"
onClick={props.onPinnedMessageRemoveAll}
>
{i18n(
'icu:PinnedMessagesPanel__UnpinAllMessages__ConfirmDialog__Unpin'
)}
</AxoAlertDialog.Action>
</AxoAlertDialog.Footer>
</AxoAlertDialog.Content>
</AxoAlertDialog.Root>
</div> </div>
); );
}); });

View File

@@ -1,7 +1,7 @@
// Copyright 2025 Signal Messenger, LLC // Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React, { memo } from 'react'; import React, { memo, useCallback } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { getIntl } from '../selectors/user.std.js'; import { getIntl } from '../selectors/user.std.js';
import { getConversationByIdSelector } from '../selectors/conversations.dom.js'; import { getConversationByIdSelector } from '../selectors/conversations.dom.js';
@@ -11,6 +11,8 @@ import type { SmartTimelineItemProps } from './TimelineItem.preload.js';
import { SmartTimelineItem } from './TimelineItem.preload.js'; import { SmartTimelineItem } from './TimelineItem.preload.js';
import { getPinnedMessages } from '../selectors/pinnedMessages.dom.js'; import { getPinnedMessages } from '../selectors/pinnedMessages.dom.js';
import { canPinMessages as getCanPinMessages } from '../selectors/message.preload.js'; import { canPinMessages as getCanPinMessages } from '../selectors/message.preload.js';
import { usePinnedMessagesActions } from '../ducks/pinnedMessages.preload.js';
import { useConversationsActions } from '../ducks/conversations.preload.js';
export type SmartPinnedMessagesPanelProps = Readonly<{ export type SmartPinnedMessagesPanelProps = Readonly<{
conversationId: string; conversationId: string;
@@ -26,6 +28,7 @@ export const SmartPinnedMessagesPanel = memo(function SmartPinnedMessagesPanel(
const i18n = useSelector(getIntl); const i18n = useSelector(getIntl);
const conversationSelector = useSelector(getConversationByIdSelector); const conversationSelector = useSelector(getConversationByIdSelector);
const conversation = conversationSelector(props.conversationId); const conversation = conversationSelector(props.conversationId);
const { popPanelForConversation } = useConversationsActions();
strictAssert( strictAssert(
conversation, conversation,
@@ -35,6 +38,15 @@ export const SmartPinnedMessagesPanel = memo(function SmartPinnedMessagesPanel(
const pinnedMessages = useSelector(getPinnedMessages); const pinnedMessages = useSelector(getPinnedMessages);
const canPinMessages = getCanPinMessages(conversation); const canPinMessages = getCanPinMessages(conversation);
const { onPinnedMessageRemove } = usePinnedMessagesActions();
const handlePinnedMessageRemoveAll = useCallback(() => {
popPanelForConversation();
for (const { pinnedMessage } of pinnedMessages) {
onPinnedMessageRemove(pinnedMessage.messageId);
}
}, [popPanelForConversation, pinnedMessages, onPinnedMessageRemove]);
return ( return (
<PinnedMessagesPanel <PinnedMessagesPanel
i18n={i18n} i18n={i18n}
@@ -42,6 +54,7 @@ export const SmartPinnedMessagesPanel = memo(function SmartPinnedMessagesPanel(
pinnedMessages={pinnedMessages} pinnedMessages={pinnedMessages}
renderTimelineItem={renderTimelineItem} renderTimelineItem={renderTimelineItem}
canPinMessages={canPinMessages} canPinMessages={canPinMessages}
onPinnedMessageRemoveAll={handlePinnedMessageRemoveAll}
/> />
); );
}); });