Allow requesting to join knock rooms via spotlight (#11482)

Signed-off-by: Charly Nguyen <charly.nguyen@nordeck.net>
pull/28788/head^2
Charly Nguyen 2023-08-30 08:54:26 +02:00 committed by GitHub
parent 200631f3a5
commit 50160b9969
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 106 additions and 4 deletions

View File

@ -24,6 +24,7 @@ import {
RoomType, RoomType,
Room, Room,
HierarchyRoom, HierarchyRoom,
JoinRule,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { normalize } from "matrix-js-sdk/src/utils"; import { normalize } from "matrix-js-sdk/src/utils";
import React, { ChangeEvent, RefObject, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; import React, { ChangeEvent, RefObject, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
@ -276,6 +277,10 @@ const roomAriaUnreadLabel = (room: Room, notification: RoomNotificationState): s
} }
}; };
const canAskToJoin = (joinRule?: JoinRule): boolean => {
return SettingsStore.getValue("feature_ask_to_join") && JoinRule.Knock === joinRule;
};
interface IDirectoryOpts { interface IDirectoryOpts {
limit: number; limit: number;
query: string; query: string;
@ -514,7 +519,14 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
}, [results, filter]); }, [results, filter]);
const viewRoom = ( const viewRoom = (
room: { roomId: string; roomAlias?: string; autoJoin?: boolean; shouldPeek?: boolean; viaServers?: string[] }, room: {
roomId: string;
roomAlias?: string;
autoJoin?: boolean;
shouldPeek?: boolean;
viaServers?: string[];
joinRule?: IPublicRoomsChunkRoom["join_rule"];
},
persist = false, persist = false,
viaKeyboard = false, viaKeyboard = false,
): void => { ): void => {
@ -538,10 +550,15 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
metricsViaKeyboard: viaKeyboard, metricsViaKeyboard: viaKeyboard,
room_id: room.roomId, room_id: room.roomId,
room_alias: room.roomAlias, room_alias: room.roomAlias,
auto_join: room.autoJoin, auto_join: room.autoJoin && !canAskToJoin(room.joinRule),
should_peek: room.shouldPeek, should_peek: room.shouldPeek,
via_servers: room.viaServers, via_servers: room.viaServers,
}); });
if (canAskToJoin(room.joinRule)) {
defaultDispatcher.dispatch({ action: Action.PromptAskToJoin });
}
onFinished(); onFinished();
}; };
@ -647,12 +664,15 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
} }
if (isPublicRoomResult(result)) { if (isPublicRoomResult(result)) {
const clientRoom = cli.getRoom(result.publicRoom.room_id); const clientRoom = cli.getRoom(result.publicRoom.room_id);
const joinRule = result.publicRoom.join_rule;
// Element Web currently does not allow guests to join rooms, so we // Element Web currently does not allow guests to join rooms, so we
// instead show them view buttons for all rooms. If the room is not // instead show them view buttons for all rooms. If the room is not
// world readable, a modal will appear asking you to register first. If // world readable, a modal will appear asking you to register first. If
// it is readable, the preview appears as normal. // it is readable, the preview appears as normal.
const showViewButton = const showViewButton =
clientRoom?.getMyMembership() === "join" || result.publicRoom.world_readable || cli.isGuest(); clientRoom?.getMyMembership() === "join" ||
(result.publicRoom.world_readable && !canAskToJoin(joinRule)) ||
cli.isGuest();
const listener = (ev: ButtonEvent): void => { const listener = (ev: ButtonEvent): void => {
ev.stopPropagation(); ev.stopPropagation();
@ -665,12 +685,20 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
autoJoin: !result.publicRoom.world_readable && !cli.isGuest(), autoJoin: !result.publicRoom.world_readable && !cli.isGuest(),
shouldPeek: result.publicRoom.world_readable || cli.isGuest(), shouldPeek: result.publicRoom.world_readable || cli.isGuest(),
viaServers: config ? [config.roomServer] : undefined, viaServers: config ? [config.roomServer] : undefined,
joinRule,
}, },
true, true,
ev.type !== "click", ev.type !== "click",
); );
}; };
let buttonLabel;
if (showViewButton) {
buttonLabel = _t("action|view");
} else {
buttonLabel = canAskToJoin(joinRule) ? _t("action|ask_to_join") : _t("action|join");
}
return ( return (
<Option <Option
id={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}`} id={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}`}
@ -683,7 +711,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
onClick={listener} onClick={listener}
tabIndex={-1} tabIndex={-1}
> >
{showViewButton ? _t("action|view") : _t("action|join")} {buttonLabel}
</AccessibleButton> </AccessibleButton>
} }
aria-labelledby={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_name`} aria-labelledby={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_name`}

View File

@ -74,6 +74,7 @@
"resend": "Resend", "resend": "Resend",
"next": "Next", "next": "Next",
"view": "View", "view": "View",
"ask_to_join": "Ask to join",
"forward": "Forward", "forward": "Forward",
"copy_link": "Copy link", "copy_link": "Copy link",
"logout": "Logout", "logout": "Logout",

View File

@ -20,6 +20,7 @@ import {
ConnectionError, ConnectionError,
IProtocol, IProtocol,
IPublicRoomsChunkRoom, IPublicRoomsChunkRoom,
JoinRule,
MatrixClient, MatrixClient,
Room, Room,
RoomMember, RoomMember,
@ -38,6 +39,7 @@ import SettingsStore from "../../../../src/settings/SettingsStore";
import { SettingLevel } from "../../../../src/settings/SettingLevel"; import { SettingLevel } from "../../../../src/settings/SettingLevel";
import defaultDispatcher from "../../../../src/dispatcher/dispatcher"; import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
import SdkConfig from "../../../../src/SdkConfig"; import SdkConfig from "../../../../src/SdkConfig";
import { Action } from "../../../../src/dispatcher/actions";
jest.useFakeTimers(); jest.useFakeTimers();
@ -574,4 +576,75 @@ describe("Spotlight Dialog", () => {
expect(screen.getByText("Failed to query public rooms")).toBeInTheDocument(); expect(screen.getByText("Failed to query public rooms")).toBeInTheDocument();
}); });
describe("knock rooms", () => {
const knockRoom: IPublicRoomsChunkRoom = {
guest_can_join: false,
join_rule: JoinRule.Knock,
num_joined_members: 0,
room_id: "some-room-id",
world_readable: false,
};
const viewRoomParams = {
action: Action.ViewRoom,
metricsTrigger: "WebUnifiedSearch",
metricsViaKeyboard: false,
room_alias: undefined,
room_id: knockRoom.room_id,
should_peek: false,
via_servers: ["example.tld"],
};
beforeEach(() => (mockedClient = mockClient({ rooms: [knockRoom] })));
describe("when disabling feature", () => {
beforeEach(async () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) =>
setting === "feature_ask_to_join" ? false : [],
);
render(<SpotlightDialog initialFilter={Filter.PublicRooms} onFinished={() => {}} />);
// search is debounced
jest.advanceTimersByTime(200);
await flushPromisesWithFakeTimers();
fireEvent.click(screen.getByRole("button", { name: "View" }));
});
it("should not skip to auto join", async () => {
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ ...viewRoomParams, auto_join: true });
});
it("should not prompt ask to join", async () => {
expect(defaultDispatcher.dispatch).not.toHaveBeenCalledWith({ action: Action.PromptAskToJoin });
});
});
describe("when enabling feature", () => {
beforeEach(async () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) =>
setting === "feature_ask_to_join" ? true : [],
);
jest.spyOn(mockedClient, "getRoom").mockReturnValue(null);
render(<SpotlightDialog initialFilter={Filter.PublicRooms} onFinished={() => {}} />);
// search is debounced
jest.advanceTimersByTime(200);
await flushPromisesWithFakeTimers();
fireEvent.click(screen.getByRole("button", { name: "Ask to join" }));
});
it("should skip to auto join", async () => {
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ ...viewRoomParams, auto_join: false });
});
it("should prompt ask to join", async () => {
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: Action.PromptAskToJoin });
});
});
});
}); });