fix(notifications): Advanced Push Rules mode switch keeps highlight/sound tweaks
RuleModeSwitcher rebuilt a rule's actions from scratch, so changing the mode of e.g. .m.rule.is_user_mention silently dropped its highlight tweak account-wide. Derive the existing highlight/custom-sound tweaks from the rule's current actions and pass them through as NotificationModeOptions. Adds getNotificationModeOptionsFromActions + unit tests. Fixes #19 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
@@ -18,6 +18,7 @@ import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
|||||||
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
||||||
import {
|
import {
|
||||||
getNotificationModeActions,
|
getNotificationModeActions,
|
||||||
|
getNotificationModeOptionsFromActions,
|
||||||
NotificationMode,
|
NotificationMode,
|
||||||
useNotificationModeActions,
|
useNotificationModeActions,
|
||||||
} from '../../../hooks/useNotificationMode';
|
} from '../../../hooks/useNotificationMode';
|
||||||
@@ -131,7 +132,13 @@ type RuleModeSwitcherProps = {
|
|||||||
|
|
||||||
function RuleModeSwitcher({ kind, pushRule }: RuleModeSwitcherProps) {
|
function RuleModeSwitcher({ kind, pushRule }: RuleModeSwitcherProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const getModeActions = useNotificationModeActions();
|
// Preserve any `highlight`/custom sound tweak already on the rule — otherwise
|
||||||
|
// switching mode here rebuilds actions from scratch and silently drops them.
|
||||||
|
const options = useMemo(
|
||||||
|
() => getNotificationModeOptionsFromActions(pushRule.actions),
|
||||||
|
[pushRule.actions],
|
||||||
|
);
|
||||||
|
const getModeActions = useNotificationModeActions(options);
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
async (mode: NotificationMode) => {
|
async (mode: NotificationMode) => {
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { test } from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { PushRuleActionName, TweakName } from 'matrix-js-sdk';
|
||||||
|
import { getNotificationModeOptionsFromActions } from './useNotificationMode';
|
||||||
|
|
||||||
|
test('getNotificationModeOptionsFromActions: no tweaks -> no highlight, no sound value', () => {
|
||||||
|
const options = getNotificationModeOptionsFromActions([PushRuleActionName.Notify]);
|
||||||
|
assert.deepEqual(options, { soundValue: undefined, highlight: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getNotificationModeOptionsFromActions: picks up highlight: true', () => {
|
||||||
|
const options = getNotificationModeOptionsFromActions([
|
||||||
|
PushRuleActionName.Notify,
|
||||||
|
{ set_tweak: TweakName.Highlight, value: true },
|
||||||
|
]);
|
||||||
|
assert.equal(options.highlight, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getNotificationModeOptionsFromActions: highlight: false is not treated as set', () => {
|
||||||
|
const options = getNotificationModeOptionsFromActions([
|
||||||
|
PushRuleActionName.Notify,
|
||||||
|
{ set_tweak: TweakName.Highlight, value: false },
|
||||||
|
]);
|
||||||
|
assert.equal(options.highlight, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getNotificationModeOptionsFromActions: picks up a custom sound value', () => {
|
||||||
|
const options = getNotificationModeOptionsFromActions([
|
||||||
|
PushRuleActionName.Notify,
|
||||||
|
{ set_tweak: TweakName.Sound, value: 'ping.ogg' },
|
||||||
|
]);
|
||||||
|
assert.equal(options.soundValue, 'ping.ogg');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getNotificationModeOptionsFromActions: picks up both tweaks together', () => {
|
||||||
|
const options = getNotificationModeOptionsFromActions([
|
||||||
|
PushRuleActionName.Notify,
|
||||||
|
{ set_tweak: TweakName.Sound, value: 'ping.ogg' },
|
||||||
|
{ set_tweak: TweakName.Highlight, value: true },
|
||||||
|
]);
|
||||||
|
assert.deepEqual(options, { soundValue: 'ping.ogg', highlight: true });
|
||||||
|
});
|
||||||
@@ -49,6 +49,29 @@ export const getNotificationModeActions = (
|
|||||||
return actions;
|
return actions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Derive the options that would reproduce an existing rule's tweaks, so a mode
|
||||||
|
// switch rebuilds actions on top of them instead of silently dropping a
|
||||||
|
// `highlight` tweak (or a custom sound) that isn't part of the mode itself.
|
||||||
|
export const getNotificationModeOptionsFromActions = (
|
||||||
|
actions: PushRuleAction[],
|
||||||
|
): NotificationModeOptions => {
|
||||||
|
const soundTweak = actions.find(
|
||||||
|
(action) => typeof action === 'object' && action.set_tweak === TweakName.Sound,
|
||||||
|
);
|
||||||
|
const highlightTweak = actions.find(
|
||||||
|
(action) => typeof action === 'object' && action.set_tweak === TweakName.Highlight,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
soundValue:
|
||||||
|
soundTweak && typeof soundTweak === 'object' && typeof soundTweak.value === 'string'
|
||||||
|
? soundTweak.value
|
||||||
|
: undefined,
|
||||||
|
highlight:
|
||||||
|
!!highlightTweak && typeof highlightTweak === 'object' && highlightTweak.value !== false,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type GetNotificationModeCallback = (mode: NotificationMode) => PushRuleAction[];
|
export type GetNotificationModeCallback = (mode: NotificationMode) => PushRuleAction[];
|
||||||
export const useNotificationModeActions = (
|
export const useNotificationModeActions = (
|
||||||
options?: NotificationModeOptions,
|
options?: NotificationModeOptions,
|
||||||
|
|||||||
Reference in New Issue
Block a user