Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Merge branch 'develop' into unread-title-indicator
Browse files Browse the repository at this point in the history
  • Loading branch information
florianduros authored Feb 7, 2023
2 parents 73c2f42 + 54a6ce5 commit 4c79455
Show file tree
Hide file tree
Showing 22 changed files with 293 additions and 172 deletions.
9 changes: 9 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
module.exports = {
plugins: ["matrix-org"],
extends: ["plugin:matrix-org/babel", "plugin:matrix-org/react", "plugin:matrix-org/a11y"],
parserOptions: {
project: ["./tsconfig.json"],
},
env: {
browser: true,
node: true,
Expand Down Expand Up @@ -168,6 +171,12 @@ module.exports = {
"@typescript-eslint/explicit-member-accessibility": "off",
},
},
{
files: ["cypress/**/*.ts"],
parserOptions: {
project: ["./cypress/tsconfig.json"],
},
},
],
settings: {
react: {
Expand Down
13 changes: 7 additions & 6 deletions cypress/e2e/crypto/decryption-failure.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ const handleVerificationRequest = (request: VerificationRequest): Chainable<Emoj
verifier.on("show_sas", onShowSas);
verifier.verify();
}),
// extra timeout, as this sometimes takes a while
{ timeout: 30_000 },
);
};

Expand Down Expand Up @@ -111,9 +113,8 @@ describe("Decryption Failure Bar", () => {
})
.then(() => {
cy.botSendMessage(bot, roomId, "test");
cy.wait(5000);
cy.get(".mx_DecryptionFailureBar .mx_DecryptionFailureBar_message_headline").should(
"have.text",
cy.contains(
".mx_DecryptionFailureBar .mx_DecryptionFailureBar_message_headline",
"Verify this device to access all messages",
);

Expand All @@ -124,6 +125,7 @@ describe("Decryption Failure Bar", () => {

const verificationRequestPromise = waitForVerificationRequest(otherDevice);
cy.get(".mx_CompleteSecurity_actionRow .mx_AccessibleButton").click();
cy.contains("To proceed, please accept the verification request on your other device.");
cy.wrap(verificationRequestPromise).then((verificationRequest: VerificationRequest) => {
cy.wrap(verificationRequest.accept());
handleVerificationRequest(verificationRequest).then((emojis) => {
Expand Down Expand Up @@ -170,9 +172,8 @@ describe("Decryption Failure Bar", () => {
);

cy.botSendMessage(bot, roomId, "test");
cy.wait(5000);
cy.get(".mx_DecryptionFailureBar .mx_DecryptionFailureBar_message_headline").should(
"have.text",
cy.contains(
".mx_DecryptionFailureBar .mx_DecryptionFailureBar_message_headline",
"Reset your keys to prevent future decryption errors",
);

Expand Down
2 changes: 2 additions & 0 deletions cypress/support/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ function setupBotClient(
}
})
.then(() => cli),
// extra timeout, as this sometimes takes a while
{ timeout: 30_000 },
);
});
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@
"eslint-plugin-deprecate": "^0.7.0",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-matrix-org": "0.9.0",
"eslint-plugin-matrix-org": "0.10.0",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-unicorn": "^45.0.0",
Expand Down
2 changes: 2 additions & 0 deletions res/css/views/dialogs/_AddExistingToSpaceDialog.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ limitations under the License.
}

.mx_AddExistingToSpace_section {
margin-right: 12px; // provides space for scrollbar so that checkbox and scrollbar do not collide

&:not(:first-child) {
margin-top: 24px;
}
Expand Down
15 changes: 7 additions & 8 deletions src/components/structures/PipContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -258,17 +258,16 @@ class PipContainerInner extends React.Component<IProps, IState> {
}

private createVoiceBroadcastPlaybackPipContent(voiceBroadcastPlayback: VoiceBroadcastPlayback): CreatePipChildren {
if (this.state.viewedRoomId === voiceBroadcastPlayback.infoEvent.getRoomId()) {
return ({ onStartMoving }) => (
<div onMouseDown={onStartMoving}>
<VoiceBroadcastPlaybackBody playback={voiceBroadcastPlayback} pip={true} />
</div>
const content =
this.state.viewedRoomId === voiceBroadcastPlayback.infoEvent.getRoomId() ? (
<VoiceBroadcastPlaybackBody playback={voiceBroadcastPlayback} pip={true} />
) : (
<VoiceBroadcastSmallPlaybackBody playback={voiceBroadcastPlayback} />
);
}

return ({ onStartMoving }) => (
<div onMouseDown={onStartMoving}>
<VoiceBroadcastSmallPlaybackBody playback={voiceBroadcastPlayback} />
<div key={voiceBroadcastPlayback.infoEvent.getId()} onMouseDown={onStartMoving}>
{content}
</div>
);
}
Expand Down
3 changes: 2 additions & 1 deletion src/components/views/dialogs/ServerOfflineDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ export default class ServerOfflineDialog extends React.PureComponent<IProps> {
private renderTimeline(): React.ReactElement[] {
return EchoStore.instance.contexts.map((c, i) => {
if (!c.firstFailedTime) return null; // not useful
if (!(c instanceof RoomEchoContext)) throw new Error("Cannot render unknown context: " + c);
if (!(c instanceof RoomEchoContext))
throw new Error("Cannot render unknown context: " + c.constructor.name);
const header = (
<div className="mx_ServerOfflineDialog_content_context_timeline_header">
<RoomAvatar width={24} height={24} room={c.room} />
Expand Down
35 changes: 16 additions & 19 deletions src/components/views/elements/EventListSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -507,39 +507,36 @@ export default class EventListSummary extends React.Component<IProps> {
eventsToRender.forEach((e, index) => {
const type = e.getType();

let userId = e.getSender();
if (type === EventType.RoomMember) {
userId = e.getStateKey();
let userKey = e.getSender()!;
if (type === EventType.RoomThirdPartyInvite) {
userKey = e.getContent().display_name;
} else if (type === EventType.RoomMember) {
userKey = e.getStateKey();
} else if (e.isRedacted()) {
userId = e.getUnsigned()?.redacted_because?.sender;
userKey = e.getUnsigned()?.redacted_because?.sender;
}

// Initialise a user's events
if (!userEvents[userId]) {
userEvents[userId] = [];
if (!userEvents[userKey]) {
userEvents[userKey] = [];
}

let displayName = userId;
if (type === EventType.RoomThirdPartyInvite) {
displayName = e.getContent().display_name;
if (e.sender) {
latestUserAvatarMember.set(userId, e.sender);
}
} else if (e.isRedacted()) {
const sender = this.context?.room.getMember(userId);
let displayName = userKey;
if (e.isRedacted()) {
const sender = this.context?.room?.getMember(userKey);
if (sender) {
displayName = sender.name;
latestUserAvatarMember.set(userId, sender);
latestUserAvatarMember.set(userKey, sender);
}
} else if (e.target && TARGET_AS_DISPLAY_NAME_EVENTS.includes(type as EventType)) {
displayName = e.target.name;
latestUserAvatarMember.set(userId, e.target);
} else if (e.sender) {
latestUserAvatarMember.set(userKey, e.target);
} else if (e.sender && type !== EventType.RoomThirdPartyInvite) {
displayName = e.sender.name;
latestUserAvatarMember.set(userId, e.sender);
latestUserAvatarMember.set(userKey, e.sender);
}

userEvents[userId].push({
userEvents[userKey].push({
mxEvent: e,
displayName,
index: index,
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/rooms/RoomPreviewCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ const RoomPreviewCard: FC<IProps> = ({ room, onJoinButtonClicked, onRejectButton
joinButtons = (
<>
<AccessibleButton
kind="secondary"
kind="primary_outline"
onClick={() => {
setBusy(true);
onRejectButtonClicked();
Expand Down
6 changes: 5 additions & 1 deletion src/components/views/settings/ProfileSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ export default class ProfileSettings extends React.Component<{}, IState> {
withDisplayName: true,
});

// False negative result from no-base-to-string rule, doesn't seem to account for Symbol.toStringTag
// eslint-disable-next-line @typescript-eslint/no-base-to-string
const avatarUrl = this.state.avatarUrl?.toString();

return (
<form onSubmit={this.saveProfile} autoComplete="off" noValidate={true} className="mx_ProfileSettings">
<input
Expand Down Expand Up @@ -216,7 +220,7 @@ export default class ProfileSettings extends React.Component<{}, IState> {
</p>
</div>
<AvatarSetting
avatarUrl={this.state.avatarUrl?.toString()}
avatarUrl={avatarUrl}
avatarName={this.state.displayName || this.state.userId}
avatarAltText={_t("Profile picture")}
uploadAvatar={this.uploadAvatar}
Expand Down
2 changes: 1 addition & 1 deletion src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,7 @@
"Unable to decrypt voice broadcast": "Unable to decrypt voice broadcast",
"Unable to play this voice broadcast": "Unable to play this 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.",
"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",
"Listen to live broadcast?": "Listen to live broadcast?",
"If you start listening to this live broadcast, your current live broadcast recording will be ended.": "If you start listening to this live broadcast, your current live broadcast recording will be ended.",
Expand Down
5 changes: 3 additions & 2 deletions src/rageshake/submit-rageshake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true): Promise<Form
body.append("user_id", client.credentials.userId);
body.append("device_id", client.deviceId);

if (client.isCryptoEnabled()) {
// TODO: make this work with rust crypto
if (client.isCryptoEnabled() && client.crypto) {
const keys = [`ed25519:${client.getDeviceEd25519Key()}`];
if (client.getDeviceCurve25519Key) {
keys.push(`curve25519:${client.getDeviceCurve25519Key()}`);
Expand Down Expand Up @@ -259,7 +260,7 @@ export async function downloadBugReport(opts: IOpts = {}): Promise<void> {
reader.readAsArrayBuffer(value as Blob);
});
} else {
metadata += `${key} = ${value}\n`;
metadata += `${key} = ${value as string}\n`;
}
}
tape.append("issue.txt", metadata);
Expand Down
3 changes: 2 additions & 1 deletion src/sentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ function getEnabledLabs(): string {
}

async function getCryptoContext(client: MatrixClient): Promise<CryptoContext> {
if (!client.isCryptoEnabled()) {
// TODO: make this work with rust crypto
if (!client.isCryptoEnabled() || !client.crypto) {
return {};
}
const keys = [`ed25519:${client.getDeviceEd25519Key()}`];
Expand Down
2 changes: 1 addition & 1 deletion src/stores/widgets/StopGapWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ export class StopGapWidget extends EventEmitter {
// Now open the integration manager
// TODO: Spec this interaction.
const data = ev.detail.data;
const integType = data?.integType;
const integType = data?.integType as string;
const integId = <string>data?.integId;

// noinspection JSIgnoredPromiseFromCall
Expand Down
2 changes: 1 addition & 1 deletion src/utils/FileUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export function presentableTextForFile(
// it since it is "ugly", users generally aren't aware what it
// means and the type of the attachment can usually be inferred
// from the file extension.
text += " (" + filesize(content.info.size) + ")";
text += " (" + <string>filesize(content.info.size) + ")";
}
return text;
}
4 changes: 2 additions & 2 deletions src/utils/Whenable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ import { logger } from "matrix-js-sdk/src/logger";
import { IDestroyable } from "./IDestroyable";
import { arrayFastClone } from "./arrays";

export type WhenFn<T> = (w: Whenable<T>) => void;
export type WhenFn<T extends string | number> = (w: Whenable<T>) => void;

/**
* Whenables are a cheap way to have Observable patterns mixed with typical
* usage of Promises, without having to tear down listeners or calls. Whenables
* are intended to be used when a condition will be met multiple times and
* the consumer needs to know *when* that happens.
*/
export abstract class Whenable<T> implements IDestroyable {
export abstract class Whenable<T extends string | number> implements IDestroyable {
private listeners: { condition: T | null; fn: WhenFn<T> }[] = [];

/**
Expand Down
4 changes: 2 additions & 2 deletions src/utils/exportUtils/HtmlExport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import React, { ReactNode } from "react";
import React from "react";
import ReactDOM from "react-dom";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
Expand Down Expand Up @@ -65,7 +65,7 @@ export default class HTMLExporter extends Exporter {
this.threadsEnabled = SettingsStore.getValue("feature_threadenabled");
}

protected async getRoomAvatar(): Promise<ReactNode> {
protected async getRoomAvatar(): Promise<string> {
let blob: Blob | undefined = undefined;
const avatarUrl = Avatar.avatarUrlForRoom(this.room, 32, 32, "crop");
const avatarPath = "room.png";
Expand Down
2 changes: 1 addition & 1 deletion src/voice-broadcast/hooks/useVoiceBroadcastRecording.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const showStopBroadcastingDialog = async (): Promise<boolean> => {
description: (
<p>
{_t(
"Are you sure you want to stop your live broadcast?" +
"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.",
)}
</p>
Expand Down
4 changes: 4 additions & 0 deletions src/voice-broadcast/models/VoiceBroadcastPlayback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,11 @@ export class VoiceBroadcastPlayback
}

if (!this.playbacks.has(eventId)) {
// set to buffering while loading the chunk data
const currentState = this.getState();
this.setState(VoiceBroadcastPlaybackState.Buffering);
await this.loadPlayback(event);
this.setState(currentState);
}

const playback = this.playbacks.get(eventId);
Expand Down
55 changes: 54 additions & 1 deletion test/components/views/elements/EventListSummary-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { MatrixEvent, RoomMember } from "matrix-js-sdk/src/matrix";

import {
getMockClientWithEventEmitter,
mkEvent,
mkMembership,
mockClientMethodsUser,
unmockClientPeg,
Expand Down Expand Up @@ -100,7 +101,7 @@ describe("EventListSummary", function () {
// is created by replacing the first "$" in userIdTemplate with `i` for
// `i = 0 .. n`.
const generateEventsForUsers = (userIdTemplate, n, events) => {
let eventsForUsers = [];
let eventsForUsers: MatrixEvent[] = [];
let userId = "";
for (let i = 0; i < n; i++) {
userId = userIdTemplate.replace("$", i);
Expand Down Expand Up @@ -656,4 +657,56 @@ describe("EventListSummary", function () {

expect(summaryText).toBe("user_0, user_1 and 18 others joined");
});

it("should not blindly group 3pid invites and treat them as distinct users instead", () => {
const events = [
mkEvent({
event: true,
skey: "randomstring1",
user: "@user1:server",
type: "m.room.third_party_invite",
content: {
display_name: "n...@d...",
key_validity_url: "https://blah",
public_key: "public_key",
},
}),
mkEvent({
event: true,
skey: "randomstring2",
user: "@user1:server",
type: "m.room.third_party_invite",
content: {
display_name: "n...@d...",
key_validity_url: "https://blah",
public_key: "public_key",
},
}),
mkEvent({
event: true,
skey: "randomstring3",
user: "@user1:server",
type: "m.room.third_party_invite",
content: {
display_name: "d...@w...",
key_validity_url: "https://blah",
public_key: "public_key",
},
}),
];

const props = {
events: events,
children: generateTiles(events),
summaryLength: 2,
avatarsMaxLength: 5,
threshold: 3,
};

const wrapper = renderComponent(props);
const summary = wrapper.find(".mx_GenericEventListSummary_summary");
const summaryText = summary.text();

expect(summaryText).toBe("n...@d... was invited 2 times, d...@w... was invited");
});
});
Loading

0 comments on commit 4c79455

Please sign in to comment.