mirror of
https://github.com/element-hq/element-web
synced 2024-11-29 12:58:53 +03:00
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:
parent
aac5964121
commit
1d45921d14
4 changed files with 81 additions and 12 deletions
|
@ -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 = {
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue