mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2025-12-20 10:19:08 +00:00
Add pinned messages receive/send flags
This commit is contained in:
@@ -25,6 +25,10 @@ const log = createLogger('RemoteConfig');
|
||||
const SemverKeys = [
|
||||
'desktop.callQualitySurvey.beta',
|
||||
'desktop.callQualitySurvey.prod',
|
||||
'desktop.pinnedMessages.receive.beta',
|
||||
'desktop.pinnedMessages.receive.prod',
|
||||
'desktop.pinnedMessages.send.beta',
|
||||
'desktop.pinnedMessages.send.prod',
|
||||
'desktop.plaintextExport.beta',
|
||||
'desktop.plaintextExport.prod',
|
||||
] as const;
|
||||
|
||||
@@ -283,7 +283,7 @@ import {
|
||||
} from './types/Message2.preload.js';
|
||||
import { JobCancelReason } from './jobs/types.std.js';
|
||||
import { itemStorage } from './textsecure/Storage.preload.js';
|
||||
import { isPinnedMessagesReceiveEnabled } from './util/isPinnedMessagesEnabled.std.js';
|
||||
import { isPinnedMessagesReceiveEnabled } from './util/isPinnedMessagesEnabled.dom.js';
|
||||
|
||||
const { isNumber, throttle } = lodash;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import React, { type ReactNode } from 'react';
|
||||
import type { LocalizerType } from '../../types/I18N.std.js';
|
||||
import { AxoMenuBuilder } from '../../axo/AxoMenuBuilder.dom.js';
|
||||
import { isPinnedMessagesReceiveEnabled } from '../../util/isPinnedMessagesEnabled.std.js';
|
||||
|
||||
type MessageContextMenuProps = Readonly<{
|
||||
i18n: LocalizerType;
|
||||
@@ -101,12 +100,12 @@ export function MessageContextMenu({
|
||||
{i18n('icu:copy')}
|
||||
</AxoMenuBuilder.Item>
|
||||
)}
|
||||
{isPinnedMessagesReceiveEnabled() && onPinMessage && (
|
||||
{onPinMessage && (
|
||||
<AxoMenuBuilder.Item symbol="pin" onSelect={onPinMessage}>
|
||||
{i18n('icu:MessageContextMenu__PinMessage')}
|
||||
</AxoMenuBuilder.Item>
|
||||
)}
|
||||
{isPinnedMessagesReceiveEnabled() && onUnpinMessage && (
|
||||
{onUnpinMessage && (
|
||||
<AxoMenuBuilder.Item symbol="pin-slash" onSelect={onUnpinMessage}>
|
||||
{i18n('icu:MessageContextMenu__UnpinMessage')}
|
||||
</AxoMenuBuilder.Item>
|
||||
|
||||
@@ -84,6 +84,7 @@ function Template(props: {
|
||||
onPinGoTo={action('onPinGoTo')}
|
||||
onPinRemove={action('onPinRemove')}
|
||||
onPinsShowAll={action('onPinsShowAll')}
|
||||
canPinMessages
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ export type PinnedMessagesBarProps = Readonly<{
|
||||
onPinGoTo: (messageId: string) => void;
|
||||
onPinRemove: (messageId: string) => void;
|
||||
onPinsShowAll: () => void;
|
||||
canPinMessages: boolean;
|
||||
}>;
|
||||
|
||||
export const PinnedMessagesBar = memo(function PinnedMessagesBar(
|
||||
@@ -97,6 +98,7 @@ export const PinnedMessagesBar = memo(function PinnedMessagesBar(
|
||||
onPinGoTo={props.onPinGoTo}
|
||||
onPinRemove={props.onPinRemove}
|
||||
onPinsShowAll={props.onPinsShowAll}
|
||||
canPinMessages={props.canPinMessages}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
@@ -131,6 +133,7 @@ export const PinnedMessagesBar = memo(function PinnedMessagesBar(
|
||||
onPinGoTo={props.onPinGoTo}
|
||||
onPinRemove={props.onPinRemove}
|
||||
onPinsShowAll={props.onPinsShowAll}
|
||||
canPinMessages={props.canPinMessages}
|
||||
/>
|
||||
</Tabs.Content>
|
||||
);
|
||||
@@ -236,6 +239,7 @@ const Content = forwardRef(function Content(
|
||||
onPinGoTo: (messageId: string) => void;
|
||||
onPinRemove: (messageId: string) => void;
|
||||
onPinsShowAll: () => void;
|
||||
canPinMessages: boolean;
|
||||
},
|
||||
ref: ForwardedRef<HTMLDivElement>
|
||||
): JSX.Element {
|
||||
@@ -298,9 +302,14 @@ const Content = forwardRef(function Content(
|
||||
/>
|
||||
</AxoDropdownMenu.Trigger>
|
||||
<AxoDropdownMenu.Content>
|
||||
<AxoDropdownMenu.Item symbol="pin-slash" onSelect={handlePinRemove}>
|
||||
{props.canPinMessages && (
|
||||
<AxoDropdownMenu.Item
|
||||
symbol="pin-slash"
|
||||
onSelect={handlePinRemove}
|
||||
>
|
||||
{i18n('icu:PinnedMessagesBar__ActionsMenu__UnpinMessage')}
|
||||
</AxoDropdownMenu.Item>
|
||||
)}
|
||||
<AxoDropdownMenu.Item
|
||||
symbol="message-arrow"
|
||||
onSelect={handlePinGoTo}
|
||||
|
||||
@@ -31,6 +31,7 @@ export type PinnedMessagesPanelProps = Readonly<{
|
||||
i18n: LocalizerType;
|
||||
conversation: ConversationType;
|
||||
pinnedMessages: ReadonlyArray<PinnedMessageRenderData>;
|
||||
canPinMessages: boolean;
|
||||
renderTimelineItem: (props: SmartTimelineItemProps) => JSX.Element;
|
||||
}>;
|
||||
|
||||
@@ -78,11 +79,13 @@ export const PinnedMessagesPanel = memo(function PinnedMessagesPanel(
|
||||
);
|
||||
})}
|
||||
</ScrollArea>
|
||||
{props.canPinMessages && (
|
||||
<div className={tw('flex items-center justify-center p-2.5')}>
|
||||
<AxoButton.Root variant="borderless-primary" size="lg">
|
||||
{i18n('icu:PinnedMessagesPanel__UnpinAllMessages')}
|
||||
</AxoButton.Root>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -169,6 +169,7 @@ import type { MessageRequestResponseNotificationData } from '../../components/co
|
||||
import type { PinnedMessageNotificationData } from '../../components/conversation/pinned-messages/PinnedMessageNotification.dom.js';
|
||||
import { canEditGroupInfo } from '../../util/canEditGroupInfo.preload.js';
|
||||
import { getPinnedMessagesMessageIds } from './pinnedMessages.dom.js';
|
||||
import { isPinnedMessagesSendEnabled } from '../../util/isPinnedMessagesEnabled.dom.js';
|
||||
|
||||
const { groupBy, isEmpty, isNumber, isObject, map } = lodash;
|
||||
|
||||
@@ -2403,6 +2404,9 @@ export function canForward(message: ReadonlyMessageAttributesType): boolean {
|
||||
}
|
||||
|
||||
export function canPinMessages(conversation: ConversationType): boolean {
|
||||
if (!isPinnedMessagesSendEnabled()) {
|
||||
return false;
|
||||
}
|
||||
return conversation.type === 'direct' || canEditGroupInfo(conversation);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,10 @@ import React, { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { getIntl } from '../selectors/user.std.js';
|
||||
import { getSelectedConversationId } from '../selectors/conversations.dom.js';
|
||||
import {
|
||||
getConversationSelector,
|
||||
getSelectedConversationId,
|
||||
} from '../selectors/conversations.dom.js';
|
||||
import { strictAssert } from '../../util/assert.std.js';
|
||||
import { useConversationsActions } from '../ducks/conversations.preload.js';
|
||||
import type {
|
||||
@@ -20,6 +23,7 @@ import { PinnedMessagesBar } from '../../components/conversation/pinned-messages
|
||||
import { PanelType } from '../../types/Panels.std.js';
|
||||
import type { PinnedMessageId } from '../../types/PinnedMessage.std.js';
|
||||
import {
|
||||
canPinMessages as getCanPinMessages,
|
||||
getMessagePropsSelector,
|
||||
type MessagePropsType,
|
||||
} from '../selectors/message.preload.js';
|
||||
@@ -129,13 +133,18 @@ const selectPins: StateSelector<ReadonlyArray<Pin>> = createSelector(
|
||||
export const SmartPinnedMessagesBar = memo(function SmartPinnedMessagesBar() {
|
||||
const i18n = useSelector(getIntl);
|
||||
const conversationId = useSelector(getSelectedConversationId);
|
||||
const pins = useSelector(selectPins);
|
||||
|
||||
strictAssert(
|
||||
conversationId != null,
|
||||
'PinnedMessagesBar should only be rendered in selected conversation'
|
||||
);
|
||||
|
||||
const conversationSelector = useSelector(getConversationSelector);
|
||||
const conversation = conversationSelector(conversationId);
|
||||
strictAssert(conversation != null, 'Missing conversation');
|
||||
|
||||
const pins = useSelector(selectPins);
|
||||
const canPinMessages = getCanPinMessages(conversation);
|
||||
|
||||
const { pushPanelForConversation, scrollToMessage } =
|
||||
useConversationsActions();
|
||||
const { onPinnedMessageRemove } = usePinnedMessagesActions();
|
||||
@@ -203,6 +212,7 @@ export const SmartPinnedMessagesBar = memo(function SmartPinnedMessagesBar() {
|
||||
onPinGoTo={handlePinGoTo}
|
||||
onPinRemove={handlePinRemove}
|
||||
onPinsShowAll={handlePinsShowAll}
|
||||
canPinMessages={canPinMessages}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ import { PinnedMessagesPanel } from '../../components/conversation/pinned-messag
|
||||
import type { SmartTimelineItemProps } from './TimelineItem.preload.js';
|
||||
import { SmartTimelineItem } from './TimelineItem.preload.js';
|
||||
import { getPinnedMessages } from '../selectors/pinnedMessages.dom.js';
|
||||
import { canPinMessages as getCanPinMessages } from '../selectors/message.preload.js';
|
||||
|
||||
export type SmartPinnedMessagesPanelProps = Readonly<{
|
||||
conversationId: string;
|
||||
@@ -32,6 +33,7 @@ export const SmartPinnedMessagesPanel = memo(function SmartPinnedMessagesPanel(
|
||||
);
|
||||
|
||||
const pinnedMessages = useSelector(getPinnedMessages);
|
||||
const canPinMessages = getCanPinMessages(conversation);
|
||||
|
||||
return (
|
||||
<PinnedMessagesPanel
|
||||
@@ -39,6 +41,7 @@ export const SmartPinnedMessagesPanel = memo(function SmartPinnedMessagesPanel(
|
||||
conversation={conversation}
|
||||
pinnedMessages={pinnedMessages}
|
||||
renderTimelineItem={renderTimelineItem}
|
||||
canPinMessages={canPinMessages}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
18
ts/util/isPinnedMessagesEnabled.dom.ts
Normal file
18
ts/util/isPinnedMessagesEnabled.dom.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2025 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { isFeaturedEnabledNoRedux } from './isFeatureEnabled.dom.js';
|
||||
|
||||
export function isPinnedMessagesReceiveEnabled(): boolean {
|
||||
return isFeaturedEnabledNoRedux({
|
||||
betaKey: 'desktop.pinnedMessages.receive.beta',
|
||||
prodKey: 'desktop.pinnedMessages.receive.prod',
|
||||
});
|
||||
}
|
||||
|
||||
export function isPinnedMessagesSendEnabled(): boolean {
|
||||
return isFeaturedEnabledNoRedux({
|
||||
betaKey: 'desktop.pinnedMessages.send.beta',
|
||||
prodKey: 'desktop.pinnedMessages.send.prod',
|
||||
});
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright 2025 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Copyright 2025 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
import {
|
||||
Environment,
|
||||
getEnvironment,
|
||||
isMockEnvironment,
|
||||
} from '../environment.std.js';
|
||||
|
||||
function isDevEnv(): boolean {
|
||||
const env = getEnvironment();
|
||||
|
||||
if (
|
||||
env === Environment.Development ||
|
||||
env === Environment.Test ||
|
||||
isMockEnvironment()
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isPinnedMessagesReceiveEnabled(): boolean {
|
||||
return isDevEnv();
|
||||
}
|
||||
|
||||
export function isPinnedMessagesSendEnabled(): boolean {
|
||||
return isDevEnv();
|
||||
}
|
||||
Reference in New Issue
Block a user