Add options to change global notification
This commit is contained in:
parent
4757283c41
commit
d3f902429d
6 changed files with 400 additions and 19 deletions
174
src/app/molecules/global-notification/GlobalNotification.jsx
Normal file
174
src/app/molecules/global-notification/GlobalNotification.jsx
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import initMatrix from '../../../client/initMatrix';
|
||||||
|
import { openReusableContextMenu } from '../../../client/action/navigation';
|
||||||
|
import { getEventCords } from '../../../util/common';
|
||||||
|
|
||||||
|
import Text from '../../atoms/text/Text';
|
||||||
|
import Button from '../../atoms/button/Button';
|
||||||
|
import { MenuHeader } from '../../atoms/context-menu/ContextMenu';
|
||||||
|
import SettingTile from '../setting-tile/SettingTile';
|
||||||
|
|
||||||
|
import NotificationSelector from './NotificationSelector';
|
||||||
|
|
||||||
|
import ChevronBottom from '../../../../public/res/ic/outlined/chevron-bottom.svg';
|
||||||
|
|
||||||
|
import { useAccountData } from '../../hooks/useAccountData';
|
||||||
|
|
||||||
|
export const notifType = {
|
||||||
|
ON: 'on',
|
||||||
|
OFF: 'off',
|
||||||
|
NOISY: 'noisy',
|
||||||
|
};
|
||||||
|
export const typeToLabel = {
|
||||||
|
[notifType.ON]: 'On',
|
||||||
|
[notifType.OFF]: 'Off',
|
||||||
|
[notifType.NOISY]: 'Noisy',
|
||||||
|
};
|
||||||
|
Object.freeze(notifType);
|
||||||
|
|
||||||
|
const DM = '.m.rule.room_one_to_one';
|
||||||
|
const ENC_DM = '.m.rule.encrypted_room_one_to_one';
|
||||||
|
const ROOM = '.m.rule.message';
|
||||||
|
const ENC_ROOM = '.m.rule.encrypted';
|
||||||
|
|
||||||
|
export function getActionType(rule) {
|
||||||
|
const { actions } = rule;
|
||||||
|
if (actions.find((action) => action?.set_tweak === 'sound')) return notifType.NOISY;
|
||||||
|
if (actions.find((action) => action?.set_tweak === 'highlight')) return notifType.ON;
|
||||||
|
if (actions.find((action) => action === 'dont_notify')) return notifType.OFF;
|
||||||
|
return notifType.OFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTypeActions(type, highlightValue = false) {
|
||||||
|
if (type === notifType.OFF) return ['dont_notify'];
|
||||||
|
|
||||||
|
const highlight = { set_tweak: 'highlight' };
|
||||||
|
if (typeof highlightValue === 'boolean') highlight.value = highlightValue;
|
||||||
|
if (type === notifType.ON) return ['notify', highlight];
|
||||||
|
|
||||||
|
const sound = { set_tweak: 'sound', value: 'default' };
|
||||||
|
return ['notify', sound, highlight];
|
||||||
|
}
|
||||||
|
|
||||||
|
function useGlobalNotif() {
|
||||||
|
const mx = initMatrix.matrixClient;
|
||||||
|
const pushRules = useAccountData('m.push_rules');
|
||||||
|
const underride = pushRules?.global?.underride ?? [];
|
||||||
|
const rulesToType = {
|
||||||
|
[DM]: notifType.ON,
|
||||||
|
[ENC_DM]: notifType.ON,
|
||||||
|
[ROOM]: notifType.NOISY,
|
||||||
|
[ENC_ROOM]: notifType.NOISY,
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRuleCondition = (rule) => {
|
||||||
|
const condition = [];
|
||||||
|
if (rule === DM || rule === ENC_DM) {
|
||||||
|
condition.push({ kind: 'room_member_count', is: '2' });
|
||||||
|
}
|
||||||
|
condition.push({
|
||||||
|
kind: 'event_match',
|
||||||
|
key: 'type',
|
||||||
|
pattern: [ENC_DM, ENC_ROOM].includes(rule) ? 'm.room.encrypted' : 'm.room.message',
|
||||||
|
});
|
||||||
|
return condition;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setRule = (rule, type) => {
|
||||||
|
const content = pushRules ?? {};
|
||||||
|
if (!content.global) content.global = {};
|
||||||
|
if (!content.global.underride) content.global.underride = [];
|
||||||
|
const ur = content.global.underride;
|
||||||
|
let ruleContent = ur.find((action) => action?.rule_id === rule);
|
||||||
|
if (!ruleContent) {
|
||||||
|
ruleContent = {
|
||||||
|
conditions: getRuleCondition(type),
|
||||||
|
actions: [],
|
||||||
|
rule_id: rule,
|
||||||
|
default: true,
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
|
ur.push(ruleContent);
|
||||||
|
}
|
||||||
|
ruleContent.actions = getTypeActions(type);
|
||||||
|
|
||||||
|
mx.setAccountData('m.push_rules', content);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dmRule = underride.find((rule) => rule.rule_id === DM);
|
||||||
|
const encDmRule = underride.find((rule) => rule.rule_id === ENC_DM);
|
||||||
|
const roomRule = underride.find((rule) => rule.rule_id === ROOM);
|
||||||
|
const encRoomRule = underride.find((rule) => rule.rule_id === ENC_ROOM);
|
||||||
|
|
||||||
|
if (dmRule) rulesToType[DM] = getActionType(dmRule);
|
||||||
|
if (encDmRule) rulesToType[ENC_DM] = getActionType(encDmRule);
|
||||||
|
if (roomRule) rulesToType[ROOM] = getActionType(roomRule);
|
||||||
|
if (encRoomRule) rulesToType[ENC_ROOM] = getActionType(encRoomRule);
|
||||||
|
|
||||||
|
return [rulesToType, setRule];
|
||||||
|
}
|
||||||
|
|
||||||
|
function GlobalNotification() {
|
||||||
|
const [rulesToType, setRule] = useGlobalNotif();
|
||||||
|
|
||||||
|
const onSelect = (evt, rule) => {
|
||||||
|
openReusableContextMenu(
|
||||||
|
'bottom',
|
||||||
|
getEventCords(evt, '.btn-surface'),
|
||||||
|
(requestClose) => (
|
||||||
|
<NotificationSelector
|
||||||
|
value={rulesToType[rule]}
|
||||||
|
onSelect={(value) => {
|
||||||
|
if (rulesToType[rule] !== value) setRule(rule, value);
|
||||||
|
requestClose();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="global-notification">
|
||||||
|
<MenuHeader>Global Notifications</MenuHeader>
|
||||||
|
<SettingTile
|
||||||
|
title="Direct messages"
|
||||||
|
options={(
|
||||||
|
<Button onClick={(evt) => onSelect(evt, DM)} iconSrc={ChevronBottom}>
|
||||||
|
{ typeToLabel[rulesToType[DM]] }
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
content={<Text variant="b3">Default notification settings for all direct message.</Text>}
|
||||||
|
/>
|
||||||
|
<SettingTile
|
||||||
|
title="Encrypted direct messages"
|
||||||
|
options={(
|
||||||
|
<Button onClick={(evt) => onSelect(evt, ENC_DM)} iconSrc={ChevronBottom}>
|
||||||
|
{typeToLabel[rulesToType[ENC_DM]]}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
content={<Text variant="b3">Default notification settings for all encrypted direct message.</Text>}
|
||||||
|
/>
|
||||||
|
<SettingTile
|
||||||
|
title="Rooms messages"
|
||||||
|
options={(
|
||||||
|
<Button onClick={(evt) => onSelect(evt, ROOM)} iconSrc={ChevronBottom}>
|
||||||
|
{typeToLabel[rulesToType[ROOM]]}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
content={<Text variant="b3">Default notification settings for all room message.</Text>}
|
||||||
|
/>
|
||||||
|
<SettingTile
|
||||||
|
title="Encrypted rooms messages"
|
||||||
|
options={(
|
||||||
|
<Button onClick={(evt) => onSelect(evt, ENC_ROOM)} iconSrc={ChevronBottom}>
|
||||||
|
{typeToLabel[rulesToType[ENC_ROOM]]}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
content={<Text variant="b3">Default notification settings for all encrypted room message.</Text>}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GlobalNotification;
|
171
src/app/molecules/global-notification/KeywordNotification.jsx
Normal file
171
src/app/molecules/global-notification/KeywordNotification.jsx
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import initMatrix from '../../../client/initMatrix';
|
||||||
|
import { openReusableContextMenu } from '../../../client/action/navigation';
|
||||||
|
import { getEventCords } from '../../../util/common';
|
||||||
|
|
||||||
|
import Text from '../../atoms/text/Text';
|
||||||
|
import Button from '../../atoms/button/Button';
|
||||||
|
import { MenuHeader } from '../../atoms/context-menu/ContextMenu';
|
||||||
|
import SettingTile from '../setting-tile/SettingTile';
|
||||||
|
|
||||||
|
import NotificationSelector from './NotificationSelector';
|
||||||
|
|
||||||
|
import ChevronBottom from '../../../../public/res/ic/outlined/chevron-bottom.svg';
|
||||||
|
|
||||||
|
import { useAccountData } from '../../hooks/useAccountData';
|
||||||
|
import {
|
||||||
|
notifType, typeToLabel, getActionType, getTypeActions,
|
||||||
|
} from './GlobalNotification';
|
||||||
|
|
||||||
|
const DISPLAY_NAME = '.m.rule.contains_display_name';
|
||||||
|
const ROOM_PING = '.m.rule.roomnotif';
|
||||||
|
const USERNAME = '.m.rule.contains_user_name';
|
||||||
|
const KEYWORD = 'keyword';
|
||||||
|
|
||||||
|
function useKeywordNotif() {
|
||||||
|
const mx = initMatrix.matrixClient;
|
||||||
|
const pushRules = useAccountData('m.push_rules');
|
||||||
|
const override = pushRules?.global?.override ?? [];
|
||||||
|
const content = pushRules?.global?.content ?? [];
|
||||||
|
|
||||||
|
const rulesToType = {
|
||||||
|
[DISPLAY_NAME]: notifType.NOISY,
|
||||||
|
[ROOM_PING]: notifType.NOISY,
|
||||||
|
[USERNAME]: notifType.NOISY,
|
||||||
|
};
|
||||||
|
|
||||||
|
const setRule = (rule, type) => {
|
||||||
|
const evtContent = pushRules ?? {};
|
||||||
|
if (!evtContent.global) evtContent.global = {};
|
||||||
|
if (!evtContent.global.override) evtContent.global.override = [];
|
||||||
|
if (!evtContent.global.content) evtContent.global.content = [];
|
||||||
|
const or = evtContent.global.override;
|
||||||
|
const ct = evtContent.global.content;
|
||||||
|
|
||||||
|
if (rule === DISPLAY_NAME || rule === ROOM_PING) {
|
||||||
|
let orRule = or.find((r) => r?.rule_id === rule);
|
||||||
|
if (!orRule) {
|
||||||
|
orRule = {
|
||||||
|
conditions: [],
|
||||||
|
actions: [],
|
||||||
|
rule_id: rule,
|
||||||
|
default: true,
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
|
or.push(orRule);
|
||||||
|
}
|
||||||
|
if (rule === DISPLAY_NAME) {
|
||||||
|
orRule.conditions = [{ kind: 'contains_display_name' }];
|
||||||
|
orRule.actions = getTypeActions(type, true);
|
||||||
|
} else {
|
||||||
|
orRule.conditions = [
|
||||||
|
{ kind: 'event_match', key: 'content.body', pattern: '@room' },
|
||||||
|
{ kind: 'sender_notification_permission', key: 'room' },
|
||||||
|
];
|
||||||
|
orRule.actions = getTypeActions(type, true);
|
||||||
|
}
|
||||||
|
} else if (rule === USERNAME) {
|
||||||
|
let usernameRule = ct.find((r) => r?.rule_id === rule);
|
||||||
|
if (!usernameRule) {
|
||||||
|
const userId = mx.getUserId();
|
||||||
|
const username = userId.match(/^@?(\S+):(\S+)$/)?.[1] ?? userId;
|
||||||
|
usernameRule = {
|
||||||
|
actions: [],
|
||||||
|
default: true,
|
||||||
|
enabled: true,
|
||||||
|
pattern: username,
|
||||||
|
rule_id: rule,
|
||||||
|
};
|
||||||
|
ct.push(usernameRule);
|
||||||
|
}
|
||||||
|
usernameRule.actions = getTypeActions(type, true);
|
||||||
|
} else {
|
||||||
|
const keyRules = ct.filter((r) => r.rule_id !== USERNAME);
|
||||||
|
keyRules.forEach((r) => {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
r.actions = getTypeActions(type, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mx.setAccountData('m.push_rules', evtContent);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dsRule = override.find((rule) => rule.rule_id === DISPLAY_NAME);
|
||||||
|
const roomRule = override.find((rule) => rule.rule_id === ROOM_PING);
|
||||||
|
const usernameRule = content.find((rule) => rule.rule_id === USERNAME);
|
||||||
|
const keywordRule = content.find((rule) => rule.rule_id !== USERNAME);
|
||||||
|
|
||||||
|
if (dsRule) rulesToType[DISPLAY_NAME] = getActionType(dsRule);
|
||||||
|
if (roomRule) rulesToType[ROOM_PING] = getActionType(roomRule);
|
||||||
|
if (usernameRule) rulesToType[USERNAME] = getActionType(usernameRule);
|
||||||
|
if (keywordRule) rulesToType[KEYWORD] = getActionType(keywordRule);
|
||||||
|
|
||||||
|
return [rulesToType, setRule];
|
||||||
|
}
|
||||||
|
|
||||||
|
function GlobalNotification() {
|
||||||
|
const [rulesToType, setRule] = useKeywordNotif();
|
||||||
|
|
||||||
|
const onSelect = (evt, rule) => {
|
||||||
|
openReusableContextMenu(
|
||||||
|
'bottom',
|
||||||
|
getEventCords(evt, '.btn-surface'),
|
||||||
|
(requestClose) => (
|
||||||
|
<NotificationSelector
|
||||||
|
value={rulesToType[rule]}
|
||||||
|
onSelect={(value) => {
|
||||||
|
if (rulesToType[rule] !== value) setRule(rule, value);
|
||||||
|
requestClose();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="keyword-notification">
|
||||||
|
<MenuHeader>Mentions & keywords</MenuHeader>
|
||||||
|
<SettingTile
|
||||||
|
title="Message containing my display name"
|
||||||
|
options={(
|
||||||
|
<Button onClick={(evt) => onSelect(evt, DISPLAY_NAME)} iconSrc={ChevronBottom}>
|
||||||
|
{ typeToLabel[rulesToType[DISPLAY_NAME]] }
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
content={<Text variant="b3">Default notification settings for all message containing your display name.</Text>}
|
||||||
|
/>
|
||||||
|
<SettingTile
|
||||||
|
title="Message containing my username"
|
||||||
|
options={(
|
||||||
|
<Button onClick={(evt) => onSelect(evt, USERNAME)} iconSrc={ChevronBottom}>
|
||||||
|
{ typeToLabel[rulesToType[USERNAME]] }
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
content={<Text variant="b3">Default notification settings for all message containing your username.</Text>}
|
||||||
|
/>
|
||||||
|
<SettingTile
|
||||||
|
title="Message containing @room"
|
||||||
|
options={(
|
||||||
|
<Button onClick={(evt) => onSelect(evt, ROOM_PING)} iconSrc={ChevronBottom}>
|
||||||
|
{typeToLabel[rulesToType[ROOM_PING]]}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
content={<Text variant="b3">Default notification settings for all messages containing @room.</Text>}
|
||||||
|
/>
|
||||||
|
{ rulesToType[KEYWORD] && (
|
||||||
|
<SettingTile
|
||||||
|
title="Message containing keywords"
|
||||||
|
options={(
|
||||||
|
<Button onClick={(evt) => onSelect(evt, KEYWORD)} iconSrc={ChevronBottom}>
|
||||||
|
{typeToLabel[rulesToType[KEYWORD]]}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
content={<Text variant="b3">Default notification settings for all message containing keywords.</Text>}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GlobalNotification;
|
|
@ -0,0 +1,26 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu';
|
||||||
|
|
||||||
|
import CheckIC from '../../../../public/res/ic/outlined/check.svg';
|
||||||
|
|
||||||
|
function NotificationSelector({
|
||||||
|
value, onSelect,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<MenuHeader>Notification</MenuHeader>
|
||||||
|
<MenuItem iconSrc={value === 'off' ? CheckIC : null} variant={value === 'off' ? 'positive' : 'surface'} onClick={() => onSelect('off')}>Off</MenuItem>
|
||||||
|
<MenuItem iconSrc={value === 'on' ? CheckIC : null} variant={value === 'on' ? 'positive' : 'surface'} onClick={() => onSelect('on')}>On</MenuItem>
|
||||||
|
<MenuItem iconSrc={value === 'noisy' ? CheckIC : null} variant={value === 'noisy' ? 'positive' : 'surface'} onClick={() => onSelect('noisy')}>Noisy</MenuItem>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationSelector.propTypes = {
|
||||||
|
value: PropTypes.oneOf(['off', 'on', 'noisy']).isRequired,
|
||||||
|
onSelect: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotificationSelector;
|
|
@ -56,7 +56,9 @@ function Drawer() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
scrollRef.current.scrollTop = 0;
|
if (scrollRef.current) {
|
||||||
|
scrollRef.current.scrollTop = 0;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, [selectedTab]);
|
}, [selectedTab]);
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,8 @@ import SettingTile from '../../molecules/setting-tile/SettingTile';
|
||||||
import ImportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/ImportE2ERoomKeys';
|
import ImportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/ImportE2ERoomKeys';
|
||||||
import ExportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/ExportE2ERoomKeys';
|
import ExportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/ExportE2ERoomKeys';
|
||||||
import { ImagePackUser, ImagePackGlobal } from '../../molecules/image-pack/ImagePack';
|
import { ImagePackUser, ImagePackGlobal } from '../../molecules/image-pack/ImagePack';
|
||||||
|
import GlobalNotification from '../../molecules/global-notification/GlobalNotification';
|
||||||
|
import KeywordNotification from '../../molecules/global-notification/KeywordNotification';
|
||||||
|
|
||||||
import ProfileEditor from '../profile-editor/ProfileEditor';
|
import ProfileEditor from '../profile-editor/ProfileEditor';
|
||||||
import CrossSigning from './CrossSigning';
|
import CrossSigning from './CrossSigning';
|
||||||
|
@ -150,24 +152,28 @@ function NotificationsSection() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="settings-notifications">
|
<>
|
||||||
<MenuHeader>Notification & Sound</MenuHeader>
|
<div className="settings-notifications">
|
||||||
<SettingTile
|
<MenuHeader>Notification & Sound</MenuHeader>
|
||||||
title="Desktop notification"
|
<SettingTile
|
||||||
options={renderOptions()}
|
title="Desktop notification"
|
||||||
content={<Text variant="b3">Show desktop notification when new messages arrive.</Text>}
|
options={renderOptions()}
|
||||||
/>
|
content={<Text variant="b3">Show desktop notification when new messages arrive.</Text>}
|
||||||
<SettingTile
|
/>
|
||||||
title="Notification Sound"
|
<SettingTile
|
||||||
options={(
|
title="Notification Sound"
|
||||||
<Toggle
|
options={(
|
||||||
isActive={settings.isNotificationSounds}
|
<Toggle
|
||||||
onToggle={() => { toggleNotificationSounds(); updateState({}); }}
|
isActive={settings.isNotificationSounds}
|
||||||
/>
|
onToggle={() => { toggleNotificationSounds(); updateState({}); }}
|
||||||
)}
|
/>
|
||||||
content={<Text variant="b3">Play sound when new messages arrive.</Text>}
|
)}
|
||||||
/>
|
content={<Text variant="b3">Play sound when new messages arrive.</Text>}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
<GlobalNotification />
|
||||||
|
<KeywordNotification />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,8 @@
|
||||||
}
|
}
|
||||||
.settings-appearance__card,
|
.settings-appearance__card,
|
||||||
.settings-notifications,
|
.settings-notifications,
|
||||||
|
.global-notification,
|
||||||
|
.keyword-notification,
|
||||||
.settings-security__card,
|
.settings-security__card,
|
||||||
.settings-security .device-manage,
|
.settings-security .device-manage,
|
||||||
.settings-about__card,
|
.settings-about__card,
|
||||||
|
|
Loading…
Reference in a new issue