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": {
|
"callingDeviceSelection__select--no-device": {
|
||||||
"message": "No devices available",
|
"message": "No devices available",
|
||||||
"description": "Message for when there are no available devices to select for input/output audio or video"
|
"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
|
? undefined
|
||||||
: (this.get('members') || []).length,
|
: (this.get('members') || []).length,
|
||||||
messageRequestsEnabled,
|
messageRequestsEnabled,
|
||||||
|
muteExpiresAt: this.get('muteExpiresAt'),
|
||||||
name: this.get('name'),
|
name: this.get('name'),
|
||||||
phoneNumber: this.getNumber(),
|
phoneNumber: this.getNumber(),
|
||||||
profileName: this.getProfileName(),
|
profileName: this.getProfileName(),
|
||||||
|
@ -2844,6 +2845,10 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
async notify(message, reaction) {
|
async notify(message, reaction) {
|
||||||
|
if (this.get('muteExpiresAt') && Date.now() < this.get('muteExpiresAt')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!message.isIncoming() && !reaction) {
|
if (!message.isIncoming() && !reaction) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -357,6 +357,24 @@
|
||||||
paste: 'onPaste',
|
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() {
|
setupHeader() {
|
||||||
const getHeaderProps = () => {
|
const getHeaderProps = () => {
|
||||||
const expireTimer = this.model.get('expireTimer');
|
const expireTimer = this.model.get('expireTimer');
|
||||||
|
@ -376,6 +394,8 @@
|
||||||
value: item.get('seconds'),
|
value: item.get('seconds'),
|
||||||
})),
|
})),
|
||||||
|
|
||||||
|
muteExpirationLabel: this.getMuteExpirationLabel(),
|
||||||
|
|
||||||
onSetDisappearingMessages: seconds =>
|
onSetDisappearingMessages: seconds =>
|
||||||
this.setDisappearingMessages(seconds),
|
this.setDisappearingMessages(seconds),
|
||||||
onDeleteMessages: () => this.destroyMessages(),
|
onDeleteMessages: () => this.destroyMessages(),
|
||||||
|
@ -387,6 +407,7 @@
|
||||||
: this.model.getTitle();
|
: this.model.getTitle();
|
||||||
searchInConversation(this.model.id, name);
|
searchInConversation(this.model.id, name);
|
||||||
},
|
},
|
||||||
|
onSetMuteNotifications: ms => this.setMuteNotifications(ms),
|
||||||
|
|
||||||
// These are view only and don't update the Conversation model, so they
|
// These are view only and don't update the Conversation model, so they
|
||||||
// need a manual update call.
|
// need a manual update call.
|
||||||
|
@ -2485,6 +2506,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setMuteNotifications(ms) {
|
||||||
|
this.model.set({
|
||||||
|
muteExpiresAt: ms > 0 ? Date.now() + ms : undefined,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
async destroyMessages() {
|
async destroyMessages() {
|
||||||
try {
|
try {
|
||||||
await this.confirm(i18n('deleteConversationConfirmation'));
|
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 {
|
.module-conversation-list-item--has-unread {
|
||||||
padding-left: 12px;
|
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';
|
type: 'group' | 'direct';
|
||||||
avatarPath?: string;
|
avatarPath?: string;
|
||||||
isMe?: boolean;
|
isMe?: boolean;
|
||||||
|
muteExpiresAt?: number;
|
||||||
|
|
||||||
lastUpdated: number;
|
lastUpdated: number;
|
||||||
unreadCount?: number;
|
unreadCount?: number;
|
||||||
|
@ -167,6 +168,7 @@ export class ConversationListItem extends React.PureComponent<Props> {
|
||||||
i18n,
|
i18n,
|
||||||
isAccepted,
|
isAccepted,
|
||||||
lastMessage,
|
lastMessage,
|
||||||
|
muteExpiresAt,
|
||||||
shouldShowDraft,
|
shouldShowDraft,
|
||||||
typingContact,
|
typingContact,
|
||||||
unreadCount,
|
unreadCount,
|
||||||
|
@ -201,6 +203,9 @@ export class ConversationListItem extends React.PureComponent<Props> {
|
||||||
: null
|
: null
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
{muteExpiresAt && (
|
||||||
|
<span className="module-conversation-list-item__muted" />
|
||||||
|
)}
|
||||||
{!isAccepted ? (
|
{!isAccepted ? (
|
||||||
<span className="module-conversation-list-item__message-request">
|
<span className="module-conversation-list-item__message-request">
|
||||||
{i18n('ConversationListItem--message-request')}
|
{i18n('ConversationListItem--message-request')}
|
||||||
|
|
|
@ -34,6 +34,7 @@ const actionProps: PropsActionsType = {
|
||||||
onDeleteMessages: action('onDeleteMessages'),
|
onDeleteMessages: action('onDeleteMessages'),
|
||||||
onResetSession: action('onResetSession'),
|
onResetSession: action('onResetSession'),
|
||||||
onSearchInConversation: action('onSearchInConversation'),
|
onSearchInConversation: action('onSearchInConversation'),
|
||||||
|
onSetMuteNotifications: action('onSetMuteNotifications'),
|
||||||
onOutgoingAudioCallInConversation: action(
|
onOutgoingAudioCallInConversation: action(
|
||||||
'onOutgoingAudioCallInConversation'
|
'onOutgoingAudioCallInConversation'
|
||||||
),
|
),
|
||||||
|
@ -172,6 +173,20 @@ const stories: Array<ConversationHeaderStory> = [
|
||||||
...housekeepingProps,
|
...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 { LocalizerType } from '../../types/Util';
|
||||||
import { ColorType } from '../../types/Colors';
|
import { ColorType } from '../../types/Colors';
|
||||||
|
import { getMuteOptions } from '../../util/getMuteOptions';
|
||||||
|
|
||||||
interface TimerOption {
|
interface TimerOption {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -37,11 +38,13 @@ export interface PropsDataType {
|
||||||
leftGroup?: boolean;
|
leftGroup?: boolean;
|
||||||
|
|
||||||
expirationSettingName?: string;
|
expirationSettingName?: string;
|
||||||
|
muteExpirationLabel?: string;
|
||||||
showBackButton?: boolean;
|
showBackButton?: boolean;
|
||||||
timerOptions?: Array<TimerOption>;
|
timerOptions?: Array<TimerOption>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PropsActionsType {
|
export interface PropsActionsType {
|
||||||
|
onSetMuteNotifications: (seconds: number) => void;
|
||||||
onSetDisappearingMessages: (seconds: number) => void;
|
onSetDisappearingMessages: (seconds: number) => void;
|
||||||
onDeleteMessages: () => void;
|
onDeleteMessages: () => void;
|
||||||
onResetSession: () => void;
|
onResetSession: () => void;
|
||||||
|
@ -289,9 +292,11 @@ export class ConversationHeader extends React.Component<PropsType> {
|
||||||
type,
|
type,
|
||||||
isArchived,
|
isArchived,
|
||||||
leftGroup,
|
leftGroup,
|
||||||
|
muteExpirationLabel,
|
||||||
onDeleteMessages,
|
onDeleteMessages,
|
||||||
onResetSession,
|
onResetSession,
|
||||||
onSetDisappearingMessages,
|
onSetDisappearingMessages,
|
||||||
|
onSetMuteNotifications,
|
||||||
onShowAllMedia,
|
onShowAllMedia,
|
||||||
onShowGroupMembers,
|
onShowGroupMembers,
|
||||||
onShowSafetyNumber,
|
onShowSafetyNumber,
|
||||||
|
@ -300,7 +305,26 @@ export class ConversationHeader extends React.Component<PropsType> {
|
||||||
timerOptions,
|
timerOptions,
|
||||||
} = this.props;
|
} = 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 disappearingTitle = i18n('disappearingMessages') as any;
|
||||||
|
const muteTitle = i18n('muteNotificationsTitle') as any;
|
||||||
const isGroup = type === 'group';
|
const isGroup = type === 'group';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -319,6 +343,19 @@ export class ConversationHeader extends React.Component<PropsType> {
|
||||||
))}
|
))}
|
||||||
</SubMenu>
|
</SubMenu>
|
||||||
) : null}
|
) : 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>
|
<MenuItem onClick={onShowAllMedia}>{i18n('viewRecentMedia')}</MenuItem>
|
||||||
{isGroup ? (
|
{isGroup ? (
|
||||||
<MenuItem onClick={onShowGroupMembers}>
|
<MenuItem onClick={onShowGroupMembers}>
|
||||||
|
|
|
@ -49,6 +49,7 @@ export type ConversationType = {
|
||||||
};
|
};
|
||||||
phoneNumber?: string;
|
phoneNumber?: string;
|
||||||
membersCount?: number;
|
membersCount?: number;
|
||||||
|
muteExpiresAt?: number;
|
||||||
type: 'direct' | 'group';
|
type: 'direct' | 'group';
|
||||||
isMe?: boolean;
|
isMe?: boolean;
|
||||||
lastUpdated: number;
|
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(",
|
"rule": "jQuery-wrap(",
|
||||||
"path": "js/models/conversations.js",
|
"path": "js/models/conversations.js",
|
||||||
"line": " await wrap(",
|
"line": " await wrap(",
|
||||||
"lineNumber": 673,
|
"lineNumber": 674,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2020-06-09T20:26:46.515Z"
|
"updated": "2020-06-09T20:26:46.515Z"
|
||||||
},
|
},
|
||||||
|
@ -10634,7 +10634,7 @@
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/conversation/ConversationHeader.js",
|
"path": "ts/components/conversation/ConversationHeader.js",
|
||||||
"line": " this.menuTriggerRef = react_1.default.createRef();",
|
"line": " this.menuTriggerRef = react_1.default.createRef();",
|
||||||
"lineNumber": 15,
|
"lineNumber": 16,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-07-31T00:19:18.696Z",
|
"updated": "2019-07-31T00:19:18.696Z",
|
||||||
"reasonDetail": "Used to reference popup menu"
|
"reasonDetail": "Used to reference popup menu"
|
||||||
|
@ -10643,7 +10643,7 @@
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/conversation/ConversationHeader.tsx",
|
"path": "ts/components/conversation/ConversationHeader.tsx",
|
||||||
"line": " this.menuTriggerRef = React.createRef();",
|
"line": " this.menuTriggerRef = React.createRef();",
|
||||||
"lineNumber": 76,
|
"lineNumber": 79,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2020-05-20T20:10:43.540Z",
|
"updated": "2020-05-20T20:10:43.540Z",
|
||||||
"reasonDetail": "Used to reference popup menu"
|
"reasonDetail": "Used to reference popup menu"
|
||||||
|
|
Loading…
Add table
Reference in a new issue