Implement broadcast message preview (#9762)
This commit is contained in:
parent
6205c70462
commit
51554399fb
15 changed files with 353 additions and 32 deletions
|
@ -45,10 +45,7 @@ import RightPanelStore from "./stores/right-panel/RightPanelStore";
|
|||
import { highlightEvent, isLocationEvent } from "./utils/EventUtils";
|
||||
import { ElementCall } from "./models/Call";
|
||||
import { textForVoiceBroadcastStoppedEvent, VoiceBroadcastInfoEventType } from "./voice-broadcast";
|
||||
|
||||
export function getSenderName(event: MatrixEvent): string {
|
||||
return event.sender?.name ?? event.getSender() ?? _t("Someone");
|
||||
}
|
||||
import { getSenderName } from "./utils/event/getSenderName";
|
||||
|
||||
function getRoomMemberDisplayname(event: MatrixEvent, userId = event.getSender()): string {
|
||||
const client = MatrixClientPeg.get();
|
||||
|
|
|
@ -650,6 +650,8 @@
|
|||
"Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.": "Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.",
|
||||
"You ended a <a>voice broadcast</a>": "You ended a <a>voice broadcast</a>",
|
||||
"%(senderName)s ended a <a>voice broadcast</a>": "%(senderName)s ended a <a>voice broadcast</a>",
|
||||
"You ended a voice broadcast": "You ended a voice broadcast",
|
||||
"%(senderName)s ended a voice broadcast": "%(senderName)s ended a voice broadcast",
|
||||
"Stop live broadcasting?": "Stop live broadcasting?",
|
||||
"Are you sure you want to stop your live broadcast?This will end the broadcast and the full recording will be available in the room.": "Are you sure you want to stop your live broadcast?This will end the broadcast and the full recording will be available in the room.",
|
||||
"Yes, stop broadcast": "Yes, stop broadcast",
|
||||
|
|
|
@ -32,6 +32,8 @@ import { StickerEventPreview } from "./previews/StickerEventPreview";
|
|||
import { ReactionEventPreview } from "./previews/ReactionEventPreview";
|
||||
import { UPDATE_EVENT } from "../AsyncStore";
|
||||
import { IPreview } from "./previews/IPreview";
|
||||
import { VoiceBroadcastInfoEventType } from "../../voice-broadcast";
|
||||
import { VoiceBroadcastPreview } from "./previews/VoiceBroadcastPreview";
|
||||
|
||||
// Emitted event for when a room's preview has changed. First argument will the room for which
|
||||
// the change happened.
|
||||
|
@ -76,6 +78,10 @@ const PREVIEWS: Record<
|
|||
isState: false,
|
||||
previewer: new PollStartEventPreview(),
|
||||
},
|
||||
[VoiceBroadcastInfoEventType]: {
|
||||
isState: true,
|
||||
previewer: new VoiceBroadcastPreview(),
|
||||
},
|
||||
};
|
||||
|
||||
// The maximum number of events we're willing to look back on to get a preview.
|
||||
|
|
|
@ -23,11 +23,15 @@ import { _t, sanitizeForTranslation } from "../../../languageHandler";
|
|||
import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
|
||||
import { getHtmlText } from "../../../HtmlUtils";
|
||||
import { stripHTMLReply, stripPlainReply } from "../../../utils/Reply";
|
||||
import { VoiceBroadcastChunkEventType } from "../../../voice-broadcast/types";
|
||||
|
||||
export class MessageEventPreview implements IPreview {
|
||||
public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string {
|
||||
public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string | null {
|
||||
let eventContent = event.getContent();
|
||||
|
||||
// no preview for broadcast chunks
|
||||
if (eventContent[VoiceBroadcastChunkEventType]) return null;
|
||||
|
||||
if (event.isRelation(RelationType.Replace)) {
|
||||
// It's an edit, generate the preview on the new text
|
||||
eventContent = event.getContent()["m.new_content"];
|
||||
|
|
31
src/stores/room-list/previews/VoiceBroadcastPreview.ts
Normal file
31
src/stores/room-list/previews/VoiceBroadcastPreview.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { VoiceBroadcastInfoState } from "../../../voice-broadcast/types";
|
||||
import { textForVoiceBroadcastStoppedEventWithoutLink } from "../../../voice-broadcast/utils/textForVoiceBroadcastStoppedEventWithoutLink";
|
||||
import { IPreview } from "./IPreview";
|
||||
|
||||
export class VoiceBroadcastPreview implements IPreview {
|
||||
getTextFor(event: MatrixEvent, tagId?: string, isThread?: boolean): string | null {
|
||||
if (!event.isRedacted() && event.getContent()?.state === VoiceBroadcastInfoState.Stopped) {
|
||||
return textForVoiceBroadcastStoppedEventWithoutLink(event);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
23
src/utils/event/getSenderName.ts
Normal file
23
src/utils/event/getSenderName.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { _t } from "../../languageHandler";
|
||||
|
||||
export function getSenderName(event: MatrixEvent): string {
|
||||
return event.sender?.name ?? event.getSender() ?? _t("Someone");
|
||||
}
|
|
@ -19,8 +19,7 @@ limitations under the License.
|
|||
* {@link https://github.com/vector-im/element-meta/discussions/632}
|
||||
*/
|
||||
|
||||
import { RelationType } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
export * from "./types";
|
||||
export * from "./models/VoiceBroadcastPlayback";
|
||||
export * from "./models/VoiceBroadcastPreRecording";
|
||||
export * from "./models/VoiceBroadcastRecording";
|
||||
|
@ -55,27 +54,5 @@ export * from "./utils/shouldDisplayAsVoiceBroadcastTile";
|
|||
export * from "./utils/shouldDisplayAsVoiceBroadcastStoppedText";
|
||||
export * from "./utils/startNewVoiceBroadcastRecording";
|
||||
export * from "./utils/textForVoiceBroadcastStoppedEvent";
|
||||
export * from "./utils/textForVoiceBroadcastStoppedEventWithoutLink";
|
||||
export * from "./utils/VoiceBroadcastResumer";
|
||||
|
||||
export const VoiceBroadcastInfoEventType = "io.element.voice_broadcast_info";
|
||||
export const VoiceBroadcastChunkEventType = "io.element.voice_broadcast_chunk";
|
||||
|
||||
export type VoiceBroadcastLiveness = "live" | "not-live" | "grey";
|
||||
|
||||
export enum VoiceBroadcastInfoState {
|
||||
Started = "started",
|
||||
Paused = "paused",
|
||||
Resumed = "resumed",
|
||||
Stopped = "stopped",
|
||||
}
|
||||
|
||||
export interface VoiceBroadcastInfoEventContent {
|
||||
device_id: string;
|
||||
state: VoiceBroadcastInfoState;
|
||||
chunk_length?: number;
|
||||
last_chunk_sequence?: number;
|
||||
["m.relates_to"]?: {
|
||||
rel_type: RelationType;
|
||||
event_id: string;
|
||||
};
|
||||
}
|
||||
|
|
40
src/voice-broadcast/types.ts
Normal file
40
src/voice-broadcast/types.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { RelationType } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
export const VoiceBroadcastInfoEventType = "io.element.voice_broadcast_info";
|
||||
export const VoiceBroadcastChunkEventType = "io.element.voice_broadcast_chunk";
|
||||
|
||||
export type VoiceBroadcastLiveness = "live" | "not-live" | "grey";
|
||||
|
||||
export enum VoiceBroadcastInfoState {
|
||||
Started = "started",
|
||||
Paused = "paused",
|
||||
Resumed = "resumed",
|
||||
Stopped = "stopped",
|
||||
}
|
||||
|
||||
export interface VoiceBroadcastInfoEventContent {
|
||||
device_id: string;
|
||||
state: VoiceBroadcastInfoState;
|
||||
chunk_length?: number;
|
||||
last_chunk_sequence?: number;
|
||||
["m.relates_to"]?: {
|
||||
rel_type: RelationType;
|
||||
event_id: string;
|
||||
};
|
||||
}
|
|
@ -20,8 +20,8 @@ import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
|||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
import AccessibleButton from "../../components/views/elements/AccessibleButton";
|
||||
import { highlightEvent } from "../../utils/EventUtils";
|
||||
import { getSenderName } from "../../TextForEvent";
|
||||
import { _t } from "../../languageHandler";
|
||||
import { getSenderName } from "../../utils/event/getSenderName";
|
||||
|
||||
export const textForVoiceBroadcastStoppedEvent = (event: MatrixEvent): (() => ReactNode) => {
|
||||
return (): ReactNode => {
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { _t } from "../../languageHandler";
|
||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
import { getSenderName } from "../../utils/event/getSenderName";
|
||||
|
||||
export const textForVoiceBroadcastStoppedEventWithoutLink = (event: MatrixEvent): string => {
|
||||
const ownUserId = MatrixClientPeg.get()?.getUserId();
|
||||
|
||||
if (ownUserId && ownUserId === event.getSender()) {
|
||||
return _t("You ended a voice broadcast", {});
|
||||
}
|
||||
|
||||
return _t("%(senderName)s ended a voice broadcast", { senderName: getSenderName(event) });
|
||||
};
|
|
@ -19,12 +19,13 @@ import TestRenderer from "react-test-renderer";
|
|||
import { ReactElement } from "react";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import { getSenderName, textForEvent } from "../src/TextForEvent";
|
||||
import { textForEvent } from "../src/TextForEvent";
|
||||
import SettingsStore from "../src/settings/SettingsStore";
|
||||
import { createTestClient, stubClient } from "./test-utils";
|
||||
import { MatrixClientPeg } from "../src/MatrixClientPeg";
|
||||
import UserIdentifierCustomisations from "../src/customisations/UserIdentifier";
|
||||
import { ElementCall } from "../src/models/Call";
|
||||
import { getSenderName } from "../src/utils/event/getSenderName";
|
||||
|
||||
jest.mock("../src/settings/SettingsStore");
|
||||
jest.mock("../src/customisations/UserIdentifier", () => ({
|
||||
|
|
96
test/stores/room-list/previews/MessageEventPreview-test.ts
Normal file
96
test/stores/room-list/previews/MessageEventPreview-test.ts
Normal file
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { RelationType } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { MessageEventPreview } from "../../../../src/stores/room-list/previews/MessageEventPreview";
|
||||
import { mkEvent, stubClient } from "../../../test-utils";
|
||||
|
||||
describe("MessageEventPreview", () => {
|
||||
const preview = new MessageEventPreview();
|
||||
const userId = "@user:example.com";
|
||||
|
||||
beforeAll(() => {
|
||||
stubClient();
|
||||
});
|
||||
|
||||
describe("getTextFor", () => {
|
||||
it("when called with an event with empty content should return null", () => {
|
||||
const event = mkEvent({
|
||||
event: true,
|
||||
content: {},
|
||||
user: userId,
|
||||
type: "m.room.message",
|
||||
});
|
||||
expect(preview.getTextFor(event)).toBeNull();
|
||||
});
|
||||
|
||||
it("when called with an event with empty body should return null", () => {
|
||||
const event = mkEvent({
|
||||
event: true,
|
||||
content: {
|
||||
body: "",
|
||||
},
|
||||
user: userId,
|
||||
type: "m.room.message",
|
||||
});
|
||||
expect(preview.getTextFor(event)).toBeNull();
|
||||
});
|
||||
|
||||
it("when called with an event with body should return »user: body«", () => {
|
||||
const event = mkEvent({
|
||||
event: true,
|
||||
content: {
|
||||
body: "test body",
|
||||
},
|
||||
user: userId,
|
||||
type: "m.room.message",
|
||||
});
|
||||
expect(preview.getTextFor(event)).toBe(`${userId}: test body`);
|
||||
});
|
||||
|
||||
it("when called for a replaced event with new content should return the new content body", () => {
|
||||
const event = mkEvent({
|
||||
event: true,
|
||||
content: {
|
||||
["m.new_content"]: {
|
||||
body: "test new content body",
|
||||
},
|
||||
["m.relates_to"]: {
|
||||
rel_type: RelationType.Replace,
|
||||
event_id: "$asd123",
|
||||
},
|
||||
},
|
||||
user: userId,
|
||||
type: "m.room.message",
|
||||
});
|
||||
expect(preview.getTextFor(event)).toBe(`${userId}: test new content body`);
|
||||
});
|
||||
|
||||
it("when called with a broadcast chunk event it should return null", () => {
|
||||
const event = mkEvent({
|
||||
event: true,
|
||||
content: {
|
||||
body: "test body",
|
||||
["io.element.voice_broadcast_chunk"]: {},
|
||||
},
|
||||
user: userId,
|
||||
type: "m.room.message",
|
||||
});
|
||||
expect(preview.getTextFor(event)).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
57
test/stores/room-list/previews/VoiceBroadcastPreview-test.ts
Normal file
57
test/stores/room-list/previews/VoiceBroadcastPreview-test.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { VoiceBroadcastPreview } from "../../../../src/stores/room-list/previews/VoiceBroadcastPreview";
|
||||
import { VoiceBroadcastInfoState } from "../../../../src/voice-broadcast";
|
||||
import { mkEvent } from "../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils";
|
||||
|
||||
describe("VoiceBroadcastPreview.getTextFor", () => {
|
||||
const roomId = "!room:example.com";
|
||||
const userId = "@user:example.com";
|
||||
const deviceId = "d42";
|
||||
let preview: VoiceBroadcastPreview;
|
||||
|
||||
beforeAll(() => {
|
||||
preview = new VoiceBroadcastPreview();
|
||||
});
|
||||
|
||||
it("when passing an event with empty content, it should return null", () => {
|
||||
const event = mkEvent({
|
||||
event: true,
|
||||
content: {},
|
||||
user: userId,
|
||||
type: "m.room.message",
|
||||
});
|
||||
expect(preview.getTextFor(event)).toBeNull();
|
||||
});
|
||||
|
||||
it("when passing a broadcast started event, it should return null", () => {
|
||||
const event = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Started, userId, deviceId);
|
||||
expect(preview.getTextFor(event)).toBeNull();
|
||||
});
|
||||
|
||||
it("when passing a broadcast stopped event, it should return the expected text", () => {
|
||||
const event = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Stopped, userId, deviceId);
|
||||
expect(preview.getTextFor(event)).toBe("@user:example.com ended a voice broadcast");
|
||||
});
|
||||
|
||||
it("when passing a redacted broadcast stopped event, it should return null", () => {
|
||||
const event = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Stopped, userId, deviceId);
|
||||
event.makeRedacted(mkEvent({ event: true, content: {}, user: userId, type: "m.room.redaction" }));
|
||||
expect(preview.getTextFor(event)).toBeNull();
|
||||
});
|
||||
});
|
|
@ -88,6 +88,7 @@ export function createTestClient(): MatrixClient {
|
|||
getIdentityServerUrl: jest.fn(),
|
||||
getDomain: jest.fn().mockReturnValue("matrix.org"),
|
||||
getUserId: jest.fn().mockReturnValue("@userId:matrix.org"),
|
||||
getSafeUserId: jest.fn().mockReturnValue("@userId:matrix.org"),
|
||||
getUserIdLocalpart: jest.fn().mockResolvedValue("userId"),
|
||||
getUser: jest.fn().mockReturnValue({ on: jest.fn() }),
|
||||
getDeviceId: jest.fn().mockReturnValue("ABCDEFGHI"),
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
|
||||
import { textForVoiceBroadcastStoppedEventWithoutLink, VoiceBroadcastInfoState } from "../../../src/voice-broadcast";
|
||||
import { stubClient } from "../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "./test-utils";
|
||||
|
||||
describe("textForVoiceBroadcastStoppedEventWithoutLink", () => {
|
||||
const otherUserId = "@other:example.com";
|
||||
const roomId = "!room:example.com";
|
||||
let client: MatrixClient;
|
||||
|
||||
beforeAll(() => {
|
||||
client = stubClient();
|
||||
});
|
||||
|
||||
const getText = (senderId: string, startEventId?: string) => {
|
||||
const event = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
senderId,
|
||||
client.deviceId!,
|
||||
);
|
||||
return textForVoiceBroadcastStoppedEventWithoutLink(event);
|
||||
};
|
||||
|
||||
it("when called for an own broadcast it should return the expected text", () => {
|
||||
expect(getText(client.getUserId()!)).toBe("You ended a voice broadcast");
|
||||
});
|
||||
|
||||
it("when called for other ones broadcast it should return the expected text", () => {
|
||||
expect(getText(otherUserId)).toBe(`${otherUserId} ended a voice broadcast`);
|
||||
});
|
||||
|
||||
it("when not logged in it should return the exptected text", () => {
|
||||
mocked(client.getUserId).mockReturnValue(null);
|
||||
expect(getText(otherUserId)).toBe(`${otherUserId} ended a voice broadcast`);
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue