Improve/add notifications for location and poll events (#7552)

* Add getSenderName()

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* Handle location and poll event notifications

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* i18n

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* pollQuestions -> pollQuestion

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* Make lookup safe and remove poll end event lookup as it wouldn't work

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>

* i18n

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
Šimon Brandner 2022-01-19 01:58:31 +01:00 committed by GitHub
parent aac5964121
commit 1d45921d14
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 81 additions and 12 deletions

View file

@ -21,6 +21,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { MsgType } from "matrix-js-sdk/src/@types/event"; import { MsgType } from "matrix-js-sdk/src/@types/event";
import { LOCATION_EVENT_TYPE } from "matrix-js-sdk/src/@types/location";
import { MatrixClientPeg } from './MatrixClientPeg'; import { MatrixClientPeg } from './MatrixClientPeg';
import SdkConfig from './SdkConfig'; import SdkConfig from './SdkConfig';
@ -56,10 +57,16 @@ This is useful when the content body contains fallback text that would explain t
type of tile. type of tile.
*/ */
const msgTypeHandlers = { const msgTypeHandlers = {
[MsgType.KeyVerificationRequest]: (event) => { [MsgType.KeyVerificationRequest]: (event: MatrixEvent) => {
const name = (event.sender || {}).name; const name = (event.sender || {}).name;
return _t("%(name)s is requesting verification", { name }); return _t("%(name)s is requesting verification", { name });
}, },
[LOCATION_EVENT_TYPE.name]: (event: MatrixEvent) => {
return TextForEvent.textForLocationEvent(event)();
},
[LOCATION_EVENT_TYPE.altName]: (event: MatrixEvent) => {
return TextForEvent.textForLocationEvent(event)();
},
}; };
export const Notifier = { export const Notifier = {

View file

@ -20,7 +20,16 @@ import { logger } from "matrix-js-sdk/src/logger";
import { removeDirectionOverrideChars } from 'matrix-js-sdk/src/utils'; import { removeDirectionOverrideChars } from 'matrix-js-sdk/src/utils';
import { GuestAccess, HistoryVisibility, JoinRule } from "matrix-js-sdk/src/@types/partials"; import { GuestAccess, HistoryVisibility, JoinRule } from "matrix-js-sdk/src/@types/partials";
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event"; import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
import { M_EMOTE, M_NOTICE, M_MESSAGE, MessageEvent } from "matrix-events-sdk"; import {
M_EMOTE,
M_NOTICE,
M_MESSAGE,
MessageEvent,
M_POLL_START,
M_POLL_END,
PollStartEvent,
} from "matrix-events-sdk";
import { LOCATION_EVENT_TYPE } from "matrix-js-sdk/src/@types/location";
import { _t } from './languageHandler'; import { _t } from './languageHandler';
import * as Roles from './Roles'; import * as Roles from './Roles';
@ -36,12 +45,16 @@ import { ROOM_SECURITY_TAB } from "./components/views/dialogs/RoomSettingsDialog
import AccessibleButton from './components/views/elements/AccessibleButton'; import AccessibleButton from './components/views/elements/AccessibleButton';
import RightPanelStore from './stores/right-panel/RightPanelStore'; import RightPanelStore from './stores/right-panel/RightPanelStore';
export function getSenderName(event: MatrixEvent): string {
return event.sender?.name ?? event.getSender() ?? _t("Someone");
}
// These functions are frequently used just to check whether an event has // These functions are frequently used just to check whether an event has
// any text to display at all. For this reason they return deferred values // any text to display at all. For this reason they return deferred values
// to avoid the expense of looking up translations when they're not needed. // to avoid the expense of looking up translations when they're not needed.
function textForCallInviteEvent(event: MatrixEvent): () => string | null { function textForCallInviteEvent(event: MatrixEvent): () => string | null {
const getSenderName = () => event.sender ? event.sender.name : _t('Someone'); const senderName = getSenderName(event);
// FIXME: Find a better way to determine this from the event? // FIXME: Find a better way to determine this from the event?
let isVoice = true; let isVoice = true;
if (event.getContent().offer && event.getContent().offer.sdp && if (event.getContent().offer && event.getContent().offer.sdp &&
@ -55,19 +68,19 @@ function textForCallInviteEvent(event: MatrixEvent): () => string | null {
// and more accurate, we break out the string-based variables to a couple booleans. // and more accurate, we break out the string-based variables to a couple booleans.
if (isVoice && isSupported) { if (isVoice && isSupported) {
return () => _t("%(senderName)s placed a voice call.", { return () => _t("%(senderName)s placed a voice call.", {
senderName: getSenderName(), senderName: senderName,
}); });
} else if (isVoice && !isSupported) { } else if (isVoice && !isSupported) {
return () => _t("%(senderName)s placed a voice call. (not supported by this browser)", { return () => _t("%(senderName)s placed a voice call. (not supported by this browser)", {
senderName: getSenderName(), senderName: senderName,
}); });
} else if (!isVoice && isSupported) { } else if (!isVoice && isSupported) {
return () => _t("%(senderName)s placed a video call.", { return () => _t("%(senderName)s placed a video call.", {
senderName: getSenderName(), senderName: senderName,
}); });
} else if (!isVoice && !isSupported) { } else if (!isVoice && !isSupported) {
return () => _t("%(senderName)s placed a video call. (not supported by this browser)", { return () => _t("%(senderName)s placed a video call. (not supported by this browser)", {
senderName: getSenderName(), senderName: senderName,
}); });
} }
} }
@ -325,6 +338,17 @@ function textForServerACLEvent(ev: MatrixEvent): () => string | null {
} }
function textForMessageEvent(ev: MatrixEvent): () => string | null { function textForMessageEvent(ev: MatrixEvent): () => string | null {
const type = ev.getType();
const content = ev.getContent();
const msgtype = content.msgtype;
if (
(LOCATION_EVENT_TYPE.matches(type) || LOCATION_EVENT_TYPE.matches(msgtype)) &&
SettingsStore.getValue("feature_location_share")
) {
return textForLocationEvent(ev);
}
return () => { return () => {
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
let message = ev.getContent().body; let message = ev.getContent().body;
@ -418,7 +442,7 @@ function textForCanonicalAliasEvent(ev: MatrixEvent): () => string | null {
} }
function textForThreePidInviteEvent(event: MatrixEvent): () => string | null { function textForThreePidInviteEvent(event: MatrixEvent): () => string | null {
const senderName = event.sender ? event.sender.name : event.getSender(); const senderName = getSenderName(event);
if (!isValid3pidInvite(event)) { if (!isValid3pidInvite(event)) {
return () => _t('%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.', { return () => _t('%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.', {
@ -434,7 +458,7 @@ function textForThreePidInviteEvent(event: MatrixEvent): () => string | null {
} }
function textForHistoryVisibilityEvent(event: MatrixEvent): () => string | null { function textForHistoryVisibilityEvent(event: MatrixEvent): () => string | null {
const senderName = event.sender ? event.sender.name : event.getSender(); const senderName = getSenderName(event);
switch (event.getContent().history_visibility) { switch (event.getContent().history_visibility) {
case HistoryVisibility.Invited: case HistoryVisibility.Invited:
return () => _t('%(senderName)s made future room history visible to all room members, ' return () => _t('%(senderName)s made future room history visible to all room members, '
@ -456,7 +480,7 @@ function textForHistoryVisibilityEvent(event: MatrixEvent): () => string | null
// Currently will only display a change if a user's power level is changed // Currently will only display a change if a user's power level is changed
function textForPowerEvent(event: MatrixEvent): () => string | null { function textForPowerEvent(event: MatrixEvent): () => string | null {
const senderName = event.sender ? event.sender.name : event.getSender(); const senderName = getSenderName(event);
if (!event.getPrevContent() || !event.getPrevContent().users || if (!event.getPrevContent() || !event.getPrevContent().users ||
!event.getContent() || !event.getContent().users) { !event.getContent() || !event.getContent().users) {
return null; return null;
@ -523,7 +547,7 @@ const onPinnedMessagesClick = (): void => {
function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => string | JSX.Element | null { function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => string | JSX.Element | null {
if (!SettingsStore.getValue("feature_pinning")) return null; if (!SettingsStore.getValue("feature_pinning")) return null;
const senderName = event.sender ? event.sender.name : event.getSender(); const senderName = getSenderName(event);
const roomId = event.getRoomId(); const roomId = event.getRoomId();
const pinned = event.getContent().pinned ?? []; const pinned = event.getContent().pinned ?? [];
@ -729,6 +753,25 @@ function textForMjolnirEvent(event: MatrixEvent): () => string | null {
"for %(reason)s", { senderName, oldGlob: prevEntity, newGlob: entity, reason }); "for %(reason)s", { senderName, oldGlob: prevEntity, newGlob: entity, reason });
} }
export function textForLocationEvent(event: MatrixEvent): () => string | null {
return () => _t("%(senderName)s has shared their location", {
senderName: getSenderName(event),
});
}
function textForPollStartEvent(event: MatrixEvent): () => string | null {
return () => _t("%(senderName)s has started a poll - %(pollQuestion)s", {
senderName: getSenderName(event),
pollQuestion: (event.unstableExtensibleEvent as PollStartEvent)?.question?.text,
});
}
function textForPollEndEvent(event: MatrixEvent): () => string | null {
return () => _t("%(senderName)s has ended a poll", {
senderName: getSenderName(event),
});
}
interface IHandlers { interface IHandlers {
[type: string]: [type: string]:
(ev: MatrixEvent, allowJSX: boolean, showHiddenEvents?: boolean) => (ev: MatrixEvent, allowJSX: boolean, showHiddenEvents?: boolean) =>
@ -739,6 +782,10 @@ const handlers: IHandlers = {
[EventType.RoomMessage]: textForMessageEvent, [EventType.RoomMessage]: textForMessageEvent,
[EventType.Sticker]: textForMessageEvent, [EventType.Sticker]: textForMessageEvent,
[EventType.CallInvite]: textForCallInviteEvent, [EventType.CallInvite]: textForCallInviteEvent,
[M_POLL_START.name]: textForPollStartEvent,
[M_POLL_END.name]: textForPollEndEvent,
[M_POLL_START.altName]: textForPollStartEvent,
[M_POLL_END.altName]: textForPollEndEvent,
}; };
const stateHandlers: IHandlers = { const stateHandlers: IHandlers = {

View file

@ -589,6 +589,9 @@
"%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", "%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s",
"%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", "%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s",
"%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s", "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s",
"%(senderName)s has shared their location": "%(senderName)s has shared their location",
"%(senderName)s has started a poll - %(pollQuestion)s": "%(senderName)s has started a poll - %(pollQuestion)s",
"%(senderName)s has ended a poll": "%(senderName)s has ended a poll",
"Light": "Light", "Light": "Light",
"Light high contrast": "Light high contrast", "Light high contrast": "Light high contrast",
"Dark": "Dark", "Dark": "Dark",

View file

@ -3,7 +3,7 @@ import './skinned-sdk';
import { MatrixEvent } from "matrix-js-sdk"; import { MatrixEvent } from "matrix-js-sdk";
import renderer from 'react-test-renderer'; import renderer from 'react-test-renderer';
import { textForEvent } from "../src/TextForEvent"; import { getSenderName, textForEvent } from "../src/TextForEvent";
import SettingsStore from "../src/settings/SettingsStore"; import SettingsStore from "../src/settings/SettingsStore";
import { SettingLevel } from "../src/settings/SettingLevel"; import { SettingLevel } from "../src/settings/SettingLevel";
@ -54,6 +54,18 @@ function renderComponent(component): string {
} }
describe('TextForEvent', () => { describe('TextForEvent', () => {
describe("getSenderName()", () => {
it("Prefers sender.name", () => {
expect(getSenderName({ sender: { name: "Alice" } } as MatrixEvent)).toBe("Alice");
});
it("Handles missing sender", () => {
expect(getSenderName({ getSender: () => "Alice" } as MatrixEvent)).toBe("Alice");
});
it("Handles missing sender and get sender", () => {
expect(getSenderName({ getSender: () => undefined } as MatrixEvent)).toBe("Someone");
});
});
describe("TextForPinnedEvent", () => { describe("TextForPinnedEvent", () => {
SettingsStore.setValue("feature_pinning", null, SettingLevel.DEVICE, true); SettingsStore.setValue("feature_pinning", null, SettingLevel.DEVICE, true);