Add pinned message notifications

This commit is contained in:
Jamie
2025-12-16 07:47:46 -08:00
committed by GitHub
parent 5ec3f763cd
commit 0a12e1ec17
6 changed files with 59 additions and 18 deletions

View File

@@ -3408,6 +3408,14 @@
"messageformat": "You donated for {recipient}",
"description": "Shown to label a donation badge you've sent in notifications and the left pane"
},
"icu:message--pinned--preview--received": {
"messageformat": "{sender} pinned a message",
"description": "Shown to label pinned message notification in notifications and the left pane."
},
"icu:message--pinned--preview--sent": {
"messageformat": "You pinned a message",
"description": "Shown to label pinned message notification in notifications and the left pane."
},
"icu:message--donation": {
"messageformat": "Donation",
"description": "Shown to label the donation badge you've redeemed on another device."

View File

@@ -232,26 +232,27 @@ function TabTrigger(props: {
);
}
type ContentProps = Readonly<{
i18n: LocalizerType;
pin: Pin;
onPinGoTo: (messageId: string) => void;
onPinRemove: (messageId: string) => void;
onPinsShowAll: () => void;
canPinMessages: boolean;
}>;
const Content = forwardRef(function Content(
props: {
i18n: LocalizerType;
pin: Pin;
onPinGoTo: (messageId: string) => void;
onPinRemove: (messageId: string) => void;
onPinsShowAll: () => void;
canPinMessages: boolean;
},
ref: ForwardedRef<HTMLDivElement>
): JSX.Element {
const {
{
i18n,
pin,
onPinGoTo,
onPinRemove,
onPinsShowAll,
canPinMessages,
...forwardedProps
} = props;
}: ContentProps,
ref: ForwardedRef<HTMLDivElement>
): JSX.Element {
const handlePinGoTo = useCallback(() => {
onPinGoTo(pin.message.id);
}, [onPinGoTo, pin.message.id]);
@@ -277,10 +278,10 @@ const Content = forwardRef(function Content(
{thumbnailUrl != null && <ImageThumbnail url={thumbnailUrl} />}
<div className={tw('min-w-0 flex-1')}>
<h1 className={tw('type-body-small font-semibold text-label-primary')}>
<UserText text={props.pin.sender.title} />
<UserText text={pin.sender.title} />
</h1>
<p className={tw('me-2 truncate type-body-medium text-label-primary')}>
<MessagePreview i18n={i18n} message={props.pin.message} />
<MessagePreview i18n={i18n} message={pin.message} />
</p>
<AriaClickable.HiddenTrigger
aria-label={i18n(
@@ -302,7 +303,7 @@ const Content = forwardRef(function Content(
/>
</AxoDropdownMenu.Trigger>
<AxoDropdownMenu.Content>
{props.canPinMessages && (
{canPinMessages && (
<AxoDropdownMenu.Item
symbol="pin-slash"
onSelect={handlePinRemove}

View File

@@ -93,6 +93,13 @@ export async function onPinnedMessageAdd(
}
}
if (result.change?.inserted) {
await targetConversation.addNotification('pinned-message-notification', {
pinnedMessageId: targetMessage.id,
sourceServiceId: props.pinnedByAci,
});
}
window.reduxActions.pinnedMessages.onPinnedMessagesChanged(
targetConversation.id
);

View File

@@ -1933,7 +1933,8 @@ export class BackupExportStream extends Readable {
}
if (isPinnedMessageNotification(message)) {
throw new Error('unimplemented');
// TODO(jamie): Implement backups for pinned messages
return { kind: NonBubbleResultKind.Drop };
}
if (isProfileChange(message)) {

View File

@@ -134,6 +134,11 @@ function onPinnedMessageAdd(
): StateThunk {
return async dispatch => {
const target = await getPinnedMessageTarget(targetMessageId);
const targetConversation = window.ConversationController.get(
target.conversationId
);
strictAssert(targetConversation != null, 'Missing target conversation');
await conversationJobQueue.add({
type: conversationQueueJobEnum.enum.PinMessage,
...target,
@@ -152,6 +157,11 @@ function onPinnedMessageAdd(
pinnedAt,
});
await targetConversation.addNotification('pinned-message-notification', {
pinnedMessageId: targetMessageId,
sourceServiceId: itemStorage.user.getCheckedAci(),
});
dispatch(onPinnedMessagesChanged(target.conversationId));
};
}

View File

@@ -512,7 +512,21 @@ export function getNotificationDataForMessage(
}
if (isPinnedMessageNotification(attributes)) {
throw new Error('unimplemented');
const fromContact = getAuthor(attributes);
const ourAci = itemStorage.user.getCheckedAci();
let text: string;
if (fromContact?.getAci() === ourAci) {
text = i18n('icu:message--pinned--preview--sent');
} else {
const sender = fromContact?.getTitle() ?? i18n('icu:unknownContact');
text = i18n('icu:message--pinned--preview--received', { sender });
}
return {
emoji: '📌',
text,
};
}
const { poll } = attributes;