Mute conversations
This commit is contained in:
parent
de7a69dee9
commit
84e52c948b
12 changed files with 185 additions and 3 deletions
|
@ -2745,5 +2745,39 @@
|
|||
"callingDeviceSelection__select--no-device": {
|
||||
"message": "No devices available",
|
||||
"description": "Message for when there are no available devices to select for input/output audio or video"
|
||||
},
|
||||
"muteNotificationsTitle": {
|
||||
"message": "Mute notifications",
|
||||
"description": "Label for the mute notifications drop-down selector"
|
||||
},
|
||||
"muteHour": {
|
||||
"message": "Mute for one hour",
|
||||
"description": "Label for muting the conversation"
|
||||
},
|
||||
"muteDay": {
|
||||
"message": "Mute for one day",
|
||||
"description": "Label for muting the conversation"
|
||||
},
|
||||
"muteWeek": {
|
||||
"message": "Mute for one week",
|
||||
"description": "Label for muting the conversation"
|
||||
},
|
||||
"muteYear": {
|
||||
"message": "Mute for one year",
|
||||
"description": "Label for muting the conversation"
|
||||
},
|
||||
"unmute": {
|
||||
"message": "Unmute",
|
||||
"description": "Label for unmuting the conversation"
|
||||
},
|
||||
"muteExpirationLabel": {
|
||||
"message": "Muted until $duration$",
|
||||
"description": "Shown in the mute notifications submenu whenever a conversation has been muted",
|
||||
"placeholders": {
|
||||
"duration": {
|
||||
"content": "$1",
|
||||
"example": "10/23/2023, 7:10 PM"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
1
images/icons/v2/sound-off-outline-24.svg
Normal file
1
images/icons/v2/sound-off-outline-24.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>sound-off-outline-24</title><path d="M20.061,12l2.469,2.47-1.06,1.06L19,13.061,16.53,15.53l-1.06-1.06L17.939,12,15.47,9.53l1.06-1.06L19,10.939,21.47,8.47l1.06,1.06ZM13.5,2.636a.494.494,0,0,0-.335.132L8,7.5H4a2,2,0,0,0-2,2v5a2,2,0,0,0,2,2H8l5.162,4.732a.494.494,0,0,0,.335.132.5.5,0,0,0,.5-.5V3.137A.5.5,0,0,0,13.5,2.636ZM12.75,19.25l-1.41-1.723L8.583,15H4a.5.5,0,0,1-.5-.5v-5A.5.5,0,0,1,4,9H8.583L11.34,6.473,12.75,4.75,12.5,7.5v9Z"/></svg>
|
After Width: | Height: | Size: 530 B |
|
@ -499,6 +499,7 @@
|
|||
? undefined
|
||||
: (this.get('members') || []).length,
|
||||
messageRequestsEnabled,
|
||||
muteExpiresAt: this.get('muteExpiresAt'),
|
||||
name: this.get('name'),
|
||||
phoneNumber: this.getNumber(),
|
||||
profileName: this.getProfileName(),
|
||||
|
@ -2844,6 +2845,10 @@
|
|||
},
|
||||
|
||||
async notify(message, reaction) {
|
||||
if (this.get('muteExpiresAt') && Date.now() < this.get('muteExpiresAt')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!message.isIncoming() && !reaction) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -357,6 +357,24 @@
|
|||
paste: 'onPaste',
|
||||
},
|
||||
|
||||
getMuteExpirationLabel() {
|
||||
const muteExpiresAt = this.model.get('muteExpiresAt');
|
||||
if (!muteExpiresAt) {
|
||||
return;
|
||||
}
|
||||
|
||||
const today = window.moment(Date.now());
|
||||
const expires = window.moment(muteExpiresAt);
|
||||
|
||||
if (today.isSame(expires, 'day')) {
|
||||
// eslint-disable-next-line consistent-return
|
||||
return expires.format('hh:mm A');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
return expires.format('M/D/YY, hh:mm A');
|
||||
},
|
||||
|
||||
setupHeader() {
|
||||
const getHeaderProps = () => {
|
||||
const expireTimer = this.model.get('expireTimer');
|
||||
|
@ -376,6 +394,8 @@
|
|||
value: item.get('seconds'),
|
||||
})),
|
||||
|
||||
muteExpirationLabel: this.getMuteExpirationLabel(),
|
||||
|
||||
onSetDisappearingMessages: seconds =>
|
||||
this.setDisappearingMessages(seconds),
|
||||
onDeleteMessages: () => this.destroyMessages(),
|
||||
|
@ -387,6 +407,7 @@
|
|||
: this.model.getTitle();
|
||||
searchInConversation(this.model.id, name);
|
||||
},
|
||||
onSetMuteNotifications: ms => this.setMuteNotifications(ms),
|
||||
|
||||
// These are view only and don't update the Conversation model, so they
|
||||
// need a manual update call.
|
||||
|
@ -2485,6 +2506,12 @@
|
|||
}
|
||||
},
|
||||
|
||||
setMuteNotifications(ms) {
|
||||
this.model.set({
|
||||
muteExpiresAt: ms > 0 ? Date.now() + ms : undefined,
|
||||
});
|
||||
},
|
||||
|
||||
async destroyMessages() {
|
||||
try {
|
||||
await this.confirm(i18n('deleteConversationConfirmation'));
|
||||
|
|
|
@ -3621,6 +3621,27 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
|||
}
|
||||
}
|
||||
|
||||
.module-conversation-list-item__muted {
|
||||
display: inline-block;
|
||||
height: 14px;
|
||||
margin-right: 4px;
|
||||
vertical-align: middle;
|
||||
width: 14px;
|
||||
|
||||
@include light-theme {
|
||||
@include color-svg(
|
||||
'../images/icons/v2/sound-off-outline-24.svg',
|
||||
$color-gray-60
|
||||
);
|
||||
}
|
||||
@include dark-theme {
|
||||
@include color-svg(
|
||||
'../images/icons/v2/sound-off-outline-24.svg',
|
||||
$color-gray-25
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.module-conversation-list-item--has-unread {
|
||||
padding-left: 12px;
|
||||
|
||||
|
|
|
@ -246,3 +246,10 @@ story.add('Missing Text', () => {
|
|||
/>
|
||||
);
|
||||
});
|
||||
|
||||
story.add('Muted Conversation', () => {
|
||||
const props = createProps();
|
||||
const muteExpiresAt = Date.now() + 1000 * 60 * 60;
|
||||
|
||||
return <ConversationListItem {...props} muteExpiresAt={muteExpiresAt} />;
|
||||
});
|
||||
|
|
|
@ -33,6 +33,7 @@ export type PropsData = {
|
|||
type: 'group' | 'direct';
|
||||
avatarPath?: string;
|
||||
isMe?: boolean;
|
||||
muteExpiresAt?: number;
|
||||
|
||||
lastUpdated: number;
|
||||
unreadCount?: number;
|
||||
|
@ -167,6 +168,7 @@ export class ConversationListItem extends React.PureComponent<Props> {
|
|||
i18n,
|
||||
isAccepted,
|
||||
lastMessage,
|
||||
muteExpiresAt,
|
||||
shouldShowDraft,
|
||||
typingContact,
|
||||
unreadCount,
|
||||
|
@ -201,6 +203,9 @@ export class ConversationListItem extends React.PureComponent<Props> {
|
|||
: null
|
||||
)}
|
||||
>
|
||||
{muteExpiresAt && (
|
||||
<span className="module-conversation-list-item__muted" />
|
||||
)}
|
||||
{!isAccepted ? (
|
||||
<span className="module-conversation-list-item__message-request">
|
||||
{i18n('ConversationListItem--message-request')}
|
||||
|
|
|
@ -34,6 +34,7 @@ const actionProps: PropsActionsType = {
|
|||
onDeleteMessages: action('onDeleteMessages'),
|
||||
onResetSession: action('onResetSession'),
|
||||
onSearchInConversation: action('onSearchInConversation'),
|
||||
onSetMuteNotifications: action('onSetMuteNotifications'),
|
||||
onOutgoingAudioCallInConversation: action(
|
||||
'onOutgoingAudioCallInConversation'
|
||||
),
|
||||
|
@ -172,6 +173,20 @@ const stories: Array<ConversationHeaderStory> = [
|
|||
...housekeepingProps,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Muting Conversation',
|
||||
props: {
|
||||
color: 'ultramarine',
|
||||
title: '(202) 555-0006',
|
||||
phoneNumber: '(202) 555-0006',
|
||||
type: 'direct',
|
||||
id: '6',
|
||||
muteExpirationLabel: '10/18/3000, 11:11 AM',
|
||||
isAccepted: true,
|
||||
...actionProps,
|
||||
...housekeepingProps,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -13,6 +13,7 @@ import { InContactsIcon } from '../InContactsIcon';
|
|||
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
import { ColorType } from '../../types/Colors';
|
||||
import { getMuteOptions } from '../../util/getMuteOptions';
|
||||
|
||||
interface TimerOption {
|
||||
name: string;
|
||||
|
@ -37,11 +38,13 @@ export interface PropsDataType {
|
|||
leftGroup?: boolean;
|
||||
|
||||
expirationSettingName?: string;
|
||||
muteExpirationLabel?: string;
|
||||
showBackButton?: boolean;
|
||||
timerOptions?: Array<TimerOption>;
|
||||
}
|
||||
|
||||
export interface PropsActionsType {
|
||||
onSetMuteNotifications: (seconds: number) => void;
|
||||
onSetDisappearingMessages: (seconds: number) => void;
|
||||
onDeleteMessages: () => void;
|
||||
onResetSession: () => void;
|
||||
|
@ -289,9 +292,11 @@ export class ConversationHeader extends React.Component<PropsType> {
|
|||
type,
|
||||
isArchived,
|
||||
leftGroup,
|
||||
muteExpirationLabel,
|
||||
onDeleteMessages,
|
||||
onResetSession,
|
||||
onSetDisappearingMessages,
|
||||
onSetMuteNotifications,
|
||||
onShowAllMedia,
|
||||
onShowGroupMembers,
|
||||
onShowSafetyNumber,
|
||||
|
@ -300,7 +305,26 @@ export class ConversationHeader extends React.Component<PropsType> {
|
|||
timerOptions,
|
||||
} = this.props;
|
||||
|
||||
const muteOptions = [];
|
||||
if (muteExpirationLabel) {
|
||||
muteOptions.push(
|
||||
...[
|
||||
{
|
||||
name: i18n('muteExpirationLabel', [muteExpirationLabel]),
|
||||
disabled: true,
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
name: i18n('unmute'),
|
||||
value: 0,
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
muteOptions.push(...getMuteOptions(i18n));
|
||||
|
||||
const disappearingTitle = i18n('disappearingMessages') as any;
|
||||
const muteTitle = i18n('muteNotificationsTitle') as any;
|
||||
const isGroup = type === 'group';
|
||||
|
||||
return (
|
||||
|
@ -319,6 +343,19 @@ export class ConversationHeader extends React.Component<PropsType> {
|
|||
))}
|
||||
</SubMenu>
|
||||
) : null}
|
||||
<SubMenu title={muteTitle}>
|
||||
{muteOptions.map(item => (
|
||||
<MenuItem
|
||||
key={item.name}
|
||||
disabled={item.disabled}
|
||||
onClick={() => {
|
||||
onSetMuteNotifications(item.value);
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</SubMenu>
|
||||
<MenuItem onClick={onShowAllMedia}>{i18n('viewRecentMedia')}</MenuItem>
|
||||
{isGroup ? (
|
||||
<MenuItem onClick={onShowGroupMembers}>
|
||||
|
|
|
@ -49,6 +49,7 @@ export type ConversationType = {
|
|||
};
|
||||
phoneNumber?: string;
|
||||
membersCount?: number;
|
||||
muteExpiresAt?: number;
|
||||
type: 'direct' | 'group';
|
||||
isMe?: boolean;
|
||||
lastUpdated: number;
|
||||
|
|
29
ts/util/getMuteOptions.ts
Normal file
29
ts/util/getMuteOptions.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import moment from 'moment';
|
||||
import { LocalizerType } from '../types/Util';
|
||||
|
||||
type MuteOption = {
|
||||
name: string;
|
||||
disabled?: boolean;
|
||||
value: number;
|
||||
};
|
||||
|
||||
export function getMuteOptions(i18n: LocalizerType): Array<MuteOption> {
|
||||
return [
|
||||
{
|
||||
name: i18n('muteHour'),
|
||||
value: moment.duration(1, 'hour').as('milliseconds'),
|
||||
},
|
||||
{
|
||||
name: i18n('muteDay'),
|
||||
value: moment.duration(1, 'day').as('milliseconds'),
|
||||
},
|
||||
{
|
||||
name: i18n('muteWeek'),
|
||||
value: moment.duration(1, 'week').as('milliseconds'),
|
||||
},
|
||||
{
|
||||
name: i18n('muteYear'),
|
||||
value: moment.duration(1, 'year').as('milliseconds'),
|
||||
},
|
||||
];
|
||||
}
|
|
@ -223,7 +223,7 @@
|
|||
"rule": "jQuery-wrap(",
|
||||
"path": "js/models/conversations.js",
|
||||
"line": " await wrap(",
|
||||
"lineNumber": 673,
|
||||
"lineNumber": 674,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2020-06-09T20:26:46.515Z"
|
||||
},
|
||||
|
@ -10634,7 +10634,7 @@
|
|||
"rule": "React-createRef",
|
||||
"path": "ts/components/conversation/ConversationHeader.js",
|
||||
"line": " this.menuTriggerRef = react_1.default.createRef();",
|
||||
"lineNumber": 15,
|
||||
"lineNumber": 16,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-07-31T00:19:18.696Z",
|
||||
"reasonDetail": "Used to reference popup menu"
|
||||
|
@ -10643,7 +10643,7 @@
|
|||
"rule": "React-createRef",
|
||||
"path": "ts/components/conversation/ConversationHeader.tsx",
|
||||
"line": " this.menuTriggerRef = React.createRef();",
|
||||
"lineNumber": 76,
|
||||
"lineNumber": 79,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-05-20T20:10:43.540Z",
|
||||
"reasonDetail": "Used to reference popup menu"
|
||||
|
|
Loading…
Reference in a new issue