Mark conversation as unread

Co-authored-by: Sidney Keese <sidney@carbonfive.com>
This commit is contained in:
Chris Svenningsen 2020-10-28 15:54:32 -07:00 committed by Evan Hahn
parent 184f7e1bf3
commit c408072576
20 changed files with 169 additions and 62 deletions

View file

@ -210,11 +210,15 @@
"description": "Shown at the top of the archived conversations list in the left pane"
},
"archiveConversation": {
"message": "Archive Conversation",
"message": "Archive",
"description": "Shown in menu for conversation, and moves conversation out of main conversation list"
},
"markUnread": {
"message": "Mark as unread",
"description": "Shown in menu for conversation, and marks conversation as unread"
},
"moveConversationToInbox": {
"message": "Move Conversation to Inbox",
"message": "Unarchive",
"description": "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list"
},
"pinConversation": {
@ -1039,7 +1043,7 @@
"description": "Shown on the drop-down menu for an individual message, deletes single message for everyone"
},
"deleteMessages": {
"message": "Delete messages",
"message": "Delete",
"description": "Menu item for deleting messages, title case."
},
"deleteConversationConfirmation": {
@ -2361,6 +2365,10 @@
"message": "Conversation returned to inbox",
"description": "A toast that shows up when the user unarchives a conversation"
},
"conversationMarkedUnread": {
"message": "Conversation marked unread",
"description": "A toast that shows up when user marks a conversation as unread"
},
"StickerCreator--title": {
"message": "Sticker pack creator",
"description": "The title of the Sticker Pack Creator window"

View file

@ -205,6 +205,8 @@
'private'
);
conversation.setMarkedUnread(false);
const { openConversationExternal } = window.reduxActions.conversations;
if (openConversationExternal) {
openConversationExternal(id, messageId);

View file

@ -73,20 +73,23 @@ message ContactRecord {
optional bool blocked = 9;
optional bool whitelisted = 10;
optional bool archived = 11;
optional bool markedUnread = 12;
}
message GroupV1Record {
optional bytes id = 1;
optional bool blocked = 2;
optional bool whitelisted = 3;
optional bool archived = 4;
optional bytes id = 1;
optional bool blocked = 2;
optional bool whitelisted = 3;
optional bool archived = 4;
optional bool markedUnread = 5;
}
message GroupV2Record {
optional bytes masterKey = 1;
optional bool blocked = 2;
optional bool whitelisted = 3;
optional bool archived = 4;
optional bytes masterKey = 1;
optional bool blocked = 2;
optional bool whitelisted = 3;
optional bool archived = 4;
optional bool markedUnread = 5;
}
message AccountRecord {
@ -112,7 +115,7 @@ message AccountRecord {
optional bool sealedSenderIndicators = 7;
optional bool typingIndicators = 8;
optional bool proxiedLinkPreviews = 9;
optional bool noteToSelfUnread = 10;
optional bool noteToSelfMarkedUnread = 10;
optional bool linkPreviews = 11;
repeated PinnedConversation pinnedConversations = 14;
}

View file

@ -37,6 +37,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
name: overrideProps.name || 'Some Person',
type: overrideProps.type || 'direct',
onClick: action('onClick'),
markedUnread: boolean('markedUnread', overrideProps.markedUnread || false),
lastMessage: overrideProps.lastMessage || {
text: text('lastMessage.text', 'Hi there!'),
status: select(
@ -137,18 +138,32 @@ story.add('Message Request', () => {
story.add('Unread', () => {
const counts = [4, 10, 250];
const defaultProps = createProps({
lastMessage: {
text: 'Hey there!',
status: 'delivered',
},
});
return counts.map(unreadCount => {
const props = createProps({
lastMessage: {
text: 'Hey there!',
status: 'delivered',
},
const items = counts.map(unreadCount => {
const props = {
...defaultProps,
unreadCount,
});
};
return <ConversationListItem key={unreadCount} {...props} />;
});
const markedUnreadProps = {
...defaultProps,
markedUnread: true,
};
const markedUnreadItem = [
<ConversationListItem key={5} {...markedUnreadProps} />,
];
return [...items, ...markedUnreadItem];
});
story.add('Selected', () => {

View file

@ -37,6 +37,7 @@ export type PropsData = {
lastUpdated: number;
unreadCount?: number;
markedUnread: boolean;
isSelected: boolean;
acceptedMessageRequest?: boolean;
@ -93,13 +94,19 @@ export class ConversationListItem extends React.PureComponent<Props> {
);
}
isUnread(): boolean {
const { markedUnread, unreadCount } = this.props;
return (isNumber(unreadCount) && unreadCount > 0) || markedUnread;
}
public renderUnread(): JSX.Element | null {
const { unreadCount } = this.props;
if (isNumber(unreadCount) && unreadCount > 0) {
if (this.isUnread()) {
return (
<div className="module-conversation-list-item__unread-count">
{unreadCount}
{unreadCount || ''}
</div>
);
}
@ -109,7 +116,6 @@ export class ConversationListItem extends React.PureComponent<Props> {
public renderHeader(): JSX.Element {
const {
unreadCount,
i18n,
isMe,
lastUpdated,
@ -119,14 +125,12 @@ export class ConversationListItem extends React.PureComponent<Props> {
title,
} = this.props;
const withUnread = isNumber(unreadCount) && unreadCount > 0;
return (
<div className="module-conversation-list-item__header">
<div
className={classNames(
'module-conversation-list-item__header__name',
withUnread
this.isUnread()
? 'module-conversation-list-item__header__name--with-unread'
: null
)}
@ -146,7 +150,7 @@ export class ConversationListItem extends React.PureComponent<Props> {
<div
className={classNames(
'module-conversation-list-item__header__date',
withUnread
this.isUnread()
? 'module-conversation-list-item__header__date--has-unread'
: null
)}
@ -155,7 +159,7 @@ export class ConversationListItem extends React.PureComponent<Props> {
timestamp={lastUpdated}
extended={false}
module="module-conversation-list-item__header__timestamp"
withUnread={withUnread}
withUnread={this.isUnread()}
i18n={i18n}
/>
</div>
@ -172,14 +176,12 @@ export class ConversationListItem extends React.PureComponent<Props> {
muteExpiresAt,
shouldShowDraft,
typingContact,
unreadCount,
} = this.props;
if (!lastMessage && !typingContact) {
return null;
}
const messageBody = lastMessage ? lastMessage.text : '';
const withUnread = isNumber(unreadCount) && unreadCount > 0;
const showingDraft = shouldShowDraft && draftPreview;
const deletedForEveryone = Boolean(
lastMessage && lastMessage.deletedForEveryone
@ -192,7 +194,7 @@ export class ConversationListItem extends React.PureComponent<Props> {
dir="auto"
className={classNames(
'module-conversation-list-item__message__text',
withUnread
this.isUnread()
? 'module-conversation-list-item__message__text--has-unread'
: null
)}
@ -249,8 +251,7 @@ export class ConversationListItem extends React.PureComponent<Props> {
/* eslint-enable no-nested-ternary */
public render(): JSX.Element {
const { unreadCount, onClick, id, isSelected, style } = this.props;
const withUnread = isNumber(unreadCount) && unreadCount > 0;
const { id, isSelected, onClick, style } = this.props;
return (
<button
@ -263,7 +264,7 @@ export class ConversationListItem extends React.PureComponent<Props> {
style={style}
className={classNames(
'module-conversation-list-item',
withUnread ? 'module-conversation-list-item--has-unread' : null,
this.isUnread() ? 'module-conversation-list-item--has-unread' : null,
isSelected ? 'module-conversation-list-item--is-selected' : null
)}
data-id={cleanId(id)}

View file

@ -18,6 +18,7 @@ const defaultConversations: Array<PropsData> = [
id: 'fred-convo',
isSelected: false,
lastUpdated: Date.now(),
markedUnread: false,
title: 'Fred Willard',
type: 'direct',
},
@ -25,6 +26,7 @@ const defaultConversations: Array<PropsData> = [
id: 'marc-convo',
isSelected: true,
lastUpdated: Date.now(),
markedUnread: false,
title: 'Marc Barraca',
type: 'direct',
},
@ -35,6 +37,7 @@ const defaultArchivedConversations: Array<PropsData> = [
id: 'michelle-archive-convo',
isSelected: false,
lastUpdated: Date.now(),
markedUnread: false,
title: 'Michelle Mercure',
type: 'direct',
},
@ -46,6 +49,7 @@ const pinnedConversations: Array<PropsData> = [
isPinned: true,
isSelected: false,
lastUpdated: Date.now(),
markedUnread: false,
title: 'Philip Glass',
type: 'direct',
},
@ -54,6 +58,7 @@ const pinnedConversations: Array<PropsData> = [
isPinned: true,
isSelected: false,
lastUpdated: Date.now(),
markedUnread: false,
title: 'Robert Moog',
type: 'direct',
},
@ -129,6 +134,7 @@ story.add('Search Results', () => {
id: 'fred-convo',
isSelected: false,
lastUpdated: Date.now(),
markedUnread: false,
title: 'People Named Fred',
type: 'group',
},
@ -147,6 +153,7 @@ story.add('Search Results', () => {
id: 'fred-contact',
isSelected: false,
lastUpdated: Date.now(),
markedUnread: false,
title: 'Fred Willard',
type: 'direct',
},

View file

@ -157,6 +157,7 @@ const conversations = [
text: 'The rabbit hopped silently in the night.',
status: SENT,
},
markedUnread: false,
},
},
{
@ -177,6 +178,7 @@ const conversations = [
text: "What's going on?",
status: SENT,
},
markedUnread: false,
},
},
];
@ -196,6 +198,7 @@ const contacts = [
lastUpdated: Date.now() - 10 * 60 * 1000,
unreadCount: 0,
isSelected: false,
markedUnread: false,
},
},
{
@ -211,6 +214,7 @@ const contacts = [
lastUpdated: Date.now() - 11 * 60 * 1000,
unreadCount: 0,
isSelected: false,
markedUnread: false,
},
},
];

View file

@ -44,6 +44,7 @@ const actionProps: PropsActionsType = {
onGoBack: action('onGoBack'),
onArchive: action('onArchive'),
onMarkUnread: action('onMarkUnread'),
onMoveToInbox: action('onMoveToInbox'),
onSetPin: action('onSetPin'),
};

View file

@ -36,6 +36,7 @@ export interface PropsDataType {
isMe?: boolean;
isArchived?: boolean;
isPinned?: boolean;
markedUnread?: boolean;
disableTimerChanges?: boolean;
expirationSettingName?: string;
@ -60,6 +61,7 @@ export interface PropsActionsType {
onGoBack: () => void;
onArchive: () => void;
onMarkUnread: () => void;
onMoveToInbox: () => void;
}
@ -310,6 +312,7 @@ export class ConversationHeader extends React.Component<PropsType> {
isPinned,
type,
isArchived,
markedUnread,
muteExpirationLabel,
onDeleteMessages,
onResetSession,
@ -319,6 +322,7 @@ export class ConversationHeader extends React.Component<PropsType> {
onShowGroupMembers,
onShowSafetyNumber,
onArchive,
onMarkUnread,
onSetPin,
onMoveToInbox,
timerOptions,
@ -350,28 +354,6 @@ export class ConversationHeader extends React.Component<PropsType> {
return (
<ContextMenu id={triggerId}>
<SubMenu title={muteTitle}>
{muteOptions.map(item => (
<MenuItem
key={item.name}
disabled={item.disabled}
onClick={() => {
onSetMuteNotifications(item.value);
}}
>
{item.name}
</MenuItem>
))}
</SubMenu>
{isPinned ? (
<MenuItem onClick={() => onSetPin(false)}>
{i18n('unpinConversation')}
</MenuItem>
) : (
<MenuItem onClick={() => onSetPin(true)}>
{i18n('pinConversation')}
</MenuItem>
)}
{disableTimerChanges ? null : (
<SubMenu title={disappearingTitle}>
{(timerOptions || []).map(item => (
@ -386,21 +368,37 @@ export class ConversationHeader extends React.Component<PropsType> {
))}
</SubMenu>
)}
<MenuItem onClick={onShowAllMedia}>{i18n('viewRecentMedia')}</MenuItem>
<SubMenu title={muteTitle}>
{muteOptions.map(item => (
<MenuItem
key={item.name}
disabled={item.disabled}
onClick={() => {
onSetMuteNotifications(item.value);
}}
>
{item.name}
</MenuItem>
))}
</SubMenu>
{isGroup ? (
<MenuItem onClick={onShowGroupMembers}>
{i18n('showMembers')}
</MenuItem>
) : null}
<MenuItem onClick={onShowAllMedia}>{i18n('viewRecentMedia')}</MenuItem>
{!isGroup && !isMe ? (
<MenuItem onClick={onShowSafetyNumber}>
{i18n('showSafetyNumber')}
</MenuItem>
) : null}
<MenuItem divider />
{!isGroup && acceptedMessageRequest ? (
<MenuItem onClick={onResetSession}>{i18n('resetSession')}</MenuItem>
) : null}
<MenuItem divider />
{!markedUnread ? (
<MenuItem onClick={onMarkUnread}>{i18n('markUnread')}</MenuItem>
) : null}
{isArchived ? (
<MenuItem onClick={onMoveToInbox}>
{i18n('moveConversationToInbox')}
@ -409,6 +407,15 @@ export class ConversationHeader extends React.Component<PropsType> {
<MenuItem onClick={onArchive}>{i18n('archiveConversation')}</MenuItem>
)}
<MenuItem onClick={onDeleteMessages}>{i18n('deleteMessages')}</MenuItem>
{isPinned ? (
<MenuItem onClick={() => onSetPin(false)}>
{i18n('unpinConversation')}
</MenuItem>
) : (
<MenuItem onClick={() => onSetPin(true)}>
{i18n('pinConversation')}
</MenuItem>
)}
</ContextMenu>
);
}

View file

@ -19,6 +19,7 @@ storiesOf('Components/Conversation/ProfileChangeNotification', module)
title: 'Mr. Fire 🔥',
name: 'Mr. Fire 🔥',
lastUpdated: Date.now(),
markedUnread: false,
}}
change={{
type: 'name',
@ -37,6 +38,7 @@ storiesOf('Components/Conversation/ProfileChangeNotification', module)
type: 'direct',
title: 'Mr. Fire 🔥',
lastUpdated: Date.now(),
markedUnread: false,
}}
change={{
type: 'name',

1
ts/model-types.d.ts vendored
View file

@ -149,6 +149,7 @@ export type ConversationAttributesType = {
isPinned: boolean;
lastMessageDeletedForEveryone: boolean;
lastMessageStatus: LastMessageStatus | null;
markedUnread: boolean;
messageCount: number;
messageCountBeforeMessageRequests: number;
messageRequestResponseType: number;

View file

@ -1137,7 +1137,7 @@ export class ConversationModel extends window.Backbone.Model<
deletedForEveryone: this.get('lastMessageDeletedForEveryone')!,
},
lastUpdated: this.get('timestamp')!,
markedUnread: this.get('markedUnread')!,
membersCount: this.isPrivate()
? undefined
: (this.get('membersV2')! || this.get('members')! || []).length,
@ -3019,6 +3019,12 @@ export class ConversationModel extends window.Backbone.Model<
}
}
setMarkedUnread(markedUnread: boolean): void {
this.set({ markedUnread });
window.Signal.Data.updateConversation(this.attributes);
this.captureChange('markedUnread');
}
async updateExpirationTimer(
providedExpireTimer: number | undefined,
providedSource: unknown,
@ -3998,6 +4004,7 @@ export class ConversationModel extends window.Backbone.Model<
// [X] blocked
// [X] whitelisted
// [X] archived
// [X] markedUnread
captureChange(property: string): void {
if (!window.Signal.RemoteConfig.isEnabled('desktop.storageWrite')) {
window.log.info(

View file

@ -113,6 +113,7 @@ export async function toContactRecord(
contactRecord.blocked = conversation.isBlocked();
contactRecord.whitelisted = Boolean(conversation.get('profileSharing'));
contactRecord.archived = Boolean(conversation.get('isArchived'));
contactRecord.markedUnread = Boolean(conversation.get('markedUnread'));
applyUnknownFields(contactRecord, conversation);
@ -137,6 +138,9 @@ export async function toAccountRecord(
}
accountRecord.avatarUrl = window.storage.get('avatarUrl') || '';
accountRecord.noteToSelfArchived = Boolean(conversation.get('isArchived'));
accountRecord.noteToSelfMarkedUnread = Boolean(
conversation.get('markedUnread')
);
accountRecord.readReceipts = Boolean(
window.storage.get('read-receipt-setting')
);
@ -218,6 +222,7 @@ export async function toGroupV1Record(
groupV1Record.blocked = conversation.isBlocked();
groupV1Record.whitelisted = Boolean(conversation.get('profileSharing'));
groupV1Record.archived = Boolean(conversation.get('isArchived'));
groupV1Record.markedUnread = Boolean(conversation.get('markedUnread'));
applyUnknownFields(groupV1Record, conversation);
@ -236,6 +241,7 @@ export async function toGroupV2Record(
groupV2Record.blocked = conversation.isBlocked();
groupV2Record.whitelisted = Boolean(conversation.get('profileSharing'));
groupV2Record.archived = Boolean(conversation.get('isArchived'));
groupV2Record.markedUnread = Boolean(conversation.get('markedUnread'));
applyUnknownFields(groupV2Record, conversation);
@ -386,6 +392,7 @@ export async function mergeGroupV1Record(
conversation.set({
isArchived: Boolean(groupV1Record.archived),
markedUnread: Boolean(groupV1Record.markedUnread),
storageID,
});
@ -446,6 +453,7 @@ export async function mergeGroupV2Record(
conversation.set({
isArchived: Boolean(groupV2Record.archived),
markedUnread: Boolean(groupV2Record.markedUnread),
storageID,
});
@ -537,6 +545,7 @@ export async function mergeContactRecord(
conversation.set({
isArchived: Boolean(contactRecord.archived),
markedUnread: Boolean(contactRecord.markedUnread),
storageID,
});
@ -559,6 +568,7 @@ export async function mergeAccountRecord(
avatarUrl,
linkPreviews,
noteToSelfArchived,
noteToSelfMarkedUnread,
pinnedConversations: remotelyPinnedConversationClasses,
profileKey,
readReceipts,
@ -734,6 +744,7 @@ export async function mergeAccountRecord(
conversation.set({
isArchived: Boolean(noteToSelfArchived),
markedUnread: Boolean(noteToSelfMarkedUnread),
storageID,
});

View file

@ -56,6 +56,7 @@ export type ConversationType = {
text: string;
deletedForEveryone?: boolean;
};
markedUnread: boolean;
phoneNumber?: string;
membersCount?: number;
muteExpiresAt?: number;

View file

@ -36,6 +36,7 @@ describe('LeftPane', () => {
isPinned: true,
isSelected: false,
lastUpdated: Date.now(),
markedUnread: false,
title: 'Philip Glass',
type: 'direct',
},
@ -44,6 +45,7 @@ describe('LeftPane', () => {
isPinned: true,
isSelected: false,
lastUpdated: Date.now(),
markedUnread: false,
title: 'Robert Moog',
type: 'direct',
},
@ -70,6 +72,7 @@ describe('LeftPane', () => {
id: 'fred-convo',
isSelected: false,
lastUpdated: Date.now(),
markedUnread: false,
title: 'Fred Willard',
type: 'direct',
},
@ -78,6 +81,7 @@ describe('LeftPane', () => {
isPinned: false,
isSelected: false,
lastUpdated: Date.now(),
markedUnread: false,
title: 'Robert Moog',
type: 'direct',
},
@ -104,6 +108,7 @@ describe('LeftPane', () => {
id: 'fred-convo',
isSelected: false,
lastUpdated: Date.now(),
markedUnread: false,
title: 'Fred Willard',
type: 'direct',
},
@ -114,6 +119,7 @@ describe('LeftPane', () => {
isPinned: true,
isSelected: false,
lastUpdated: Date.now(),
markedUnread: false,
title: 'Philip Glass',
type: 'direct',
},
@ -148,6 +154,7 @@ describe('LeftPane', () => {
id: 'jerry-convo',
isSelected: false,
lastUpdated: Date.now(),
markedUnread: false,
title: 'Jerry Jordan',
type: 'direct',
},
@ -157,6 +164,7 @@ describe('LeftPane', () => {
id: 'fred-convo',
isSelected: false,
lastUpdated: Date.now(),
markedUnread: false,
title: 'Fred Willard',
type: 'direct',
},
@ -179,6 +187,7 @@ describe('LeftPane', () => {
id: 'fred-convo',
isSelected: false,
lastUpdated: Date.now(),
markedUnread: false,
title: 'Fred Willard',
type: 'direct',
},

View file

@ -41,6 +41,7 @@ describe('state/selectors/conversations', () => {
inboxPosition: 0,
phoneNumber: 'notused',
isArchived: false,
markedUnread: false,
type: 'direct',
isMe: false,
@ -65,6 +66,7 @@ describe('state/selectors/conversations', () => {
inboxPosition: 21,
phoneNumber: 'notused',
isArchived: false,
markedUnread: false,
type: 'direct',
isMe: false,
@ -89,6 +91,7 @@ describe('state/selectors/conversations', () => {
inboxPosition: 22,
phoneNumber: 'notused',
isArchived: false,
markedUnread: false,
type: 'direct',
isMe: false,
@ -113,6 +116,7 @@ describe('state/selectors/conversations', () => {
inboxPosition: 20,
phoneNumber: 'notused',
isArchived: false,
markedUnread: false,
type: 'direct',
isMe: false,
@ -137,6 +141,7 @@ describe('state/selectors/conversations', () => {
inboxPosition: 30,
phoneNumber: 'notused',
isArchived: false,
markedUnread: false,
type: 'direct',
isMe: false,
@ -185,6 +190,7 @@ describe('state/selectors/conversations', () => {
phoneNumber: 'notused',
isArchived: false,
isPinned: true,
markedUnread: false,
type: 'direct',
isMe: false,
@ -210,6 +216,7 @@ describe('state/selectors/conversations', () => {
phoneNumber: 'notused',
isArchived: false,
isPinned: true,
markedUnread: false,
type: 'direct',
isMe: false,
@ -235,6 +242,7 @@ describe('state/selectors/conversations', () => {
phoneNumber: 'notused',
isArchived: false,
isPinned: true,
markedUnread: false,
type: 'direct',
isMe: false,

4
ts/textsecure.d.ts vendored
View file

@ -929,6 +929,7 @@ export declare class ContactRecordClass {
blocked?: boolean | null;
whitelisted?: boolean | null;
archived?: boolean | null;
markedUnread?: boolean;
__unknownFields?: ArrayBuffer;
}
@ -944,6 +945,7 @@ export declare class GroupV1RecordClass {
blocked?: boolean | null;
whitelisted?: boolean | null;
archived?: boolean | null;
markedUnread?: boolean;
__unknownFields?: ArrayBuffer;
}
@ -959,6 +961,7 @@ export declare class GroupV2RecordClass {
blocked?: boolean | null;
whitelisted?: boolean | null;
archived?: boolean | null;
markedUnread?: boolean;
__unknownFields?: ArrayBuffer;
}
@ -995,6 +998,7 @@ export declare class AccountRecordClass {
typingIndicators?: boolean | null;
linkPreviews?: boolean | null;
pinnedConversations?: PinnedConversationClass[];
noteToSelfMarkedUnread?: boolean;
__unknownFields?: ArrayBuffer;
}

View file

@ -631,7 +631,7 @@
"rule": "jQuery-$(",
"path": "js/views/inbox_view.js",
"line": " if (e && this.$(e.target).closest('.capture-audio').length > 0) {",
"lineNumber": 217,
"lineNumber": 219,
"reasonCategory": "usageTrusted",
"updated": "2020-05-29T18:29:18.234Z",
"reasonDetail": "Known DOM elements"
@ -640,7 +640,7 @@
"rule": "jQuery-$(",
"path": "js/views/inbox_view.js",
"line": " this.$('.conversation:first .recorder').trigger('close');",
"lineNumber": 220,
"lineNumber": 222,
"reasonCategory": "usageTrusted",
"updated": "2020-05-29T18:29:18.234Z",
"reasonDetail": "Hardcoded selector"
@ -14703,7 +14703,7 @@
"rule": "React-createRef",
"path": "ts/components/conversation/ConversationHeader.tsx",
"line": " this.menuTriggerRef = React.createRef();",
"lineNumber": 84,
"lineNumber": 86,
"reasonCategory": "usageTrusted",
"updated": "2020-05-20T20:10:43.540Z",
"reasonDetail": "Used to reference popup menu"
@ -15116,4 +15116,4 @@
"reasonCategory": "falseMatch",
"updated": "2020-09-08T23:07:22.682Z"
}
]
]

View file

@ -115,6 +115,12 @@ Whisper.ConversationUnarchivedToast = Whisper.ToastView.extend({
},
});
Whisper.ConversationMarkedUnreadToast = Whisper.ToastView.extend({
render_attributes() {
return { toastMessage: window.i18n('conversationMarkedUnread') };
},
});
Whisper.TapToViewExpiredIncomingToast = Whisper.ToastView.extend({
render_attributes() {
return {
@ -544,6 +550,14 @@ Whisper.ConversationView = Whisper.View.extend({
document.body
);
},
onMarkUnread: () => {
this.model.setMarkedUnread(true);
Whisper.ToastView.show(
Whisper.ConversationMarkedUnreadToast,
document.body
);
},
onMoveToInbox: () => {
this.model.setArchived(false);
@ -3057,6 +3071,7 @@ Whisper.ConversationView = Whisper.View.extend({
);
this.compositionApi.current.reset();
this.model.setMarkedUnread(false);
this.setQuoteMessage(null);
this.resetLinkPreview();
this.clearAttachments();

1
ts/window.d.ts vendored
View file

@ -545,6 +545,7 @@ export type WhisperType = {
};
ConversationArchivedToast: WhatIsThis;
ConversationUnarchivedToast: WhatIsThis;
ConversationMarkedUnreadToast: WhatIsThis;
AppView: WhatIsThis;
WallClockListener: WhatIsThis;
MessageRequests: WhatIsThis;