diff --git a/playwright/e2e/polls/pollHistory.spec.ts b/playwright/e2e/polls/pollHistory.spec.ts index e9ebf0a30d..20a8b912fe 100644 --- a/playwright/e2e/polls/pollHistory.spec.ts +++ b/playwright/e2e/polls/pollHistory.spec.ts @@ -69,7 +69,7 @@ test.describe("Poll history", () => { async function openPollHistory(app: ElementAppPage): Promise { const { page } = app; await app.toggleRoomInfoPanel(); - await page.locator(".mx_RoomSummaryCard").getByRole("menuitem", { name: "Poll history" }).click(); + await page.locator(".mx_RoomSummaryCard").getByRole("menuitem", { name: "Polls" }).click(); } test.use({ diff --git a/playwright/snapshots/crypto/crypto.spec.ts/RoomSummaryCard-with-verified-e2ee-linux.png b/playwright/snapshots/crypto/crypto.spec.ts/RoomSummaryCard-with-verified-e2ee-linux.png index d7c90ce5fe..f7e0d12ea1 100644 Binary files a/playwright/snapshots/crypto/crypto.spec.ts/RoomSummaryCard-with-verified-e2ee-linux.png and b/playwright/snapshots/crypto/crypto.spec.ts/RoomSummaryCard-with-verified-e2ee-linux.png differ diff --git a/playwright/snapshots/right-panel/right-panel.spec.ts/with-name-and-address-linux.png b/playwright/snapshots/right-panel/right-panel.spec.ts/with-name-and-address-linux.png index 0501086f9e..c33ff1ce34 100644 Binary files a/playwright/snapshots/right-panel/right-panel.spec.ts/with-name-and-address-linux.png and b/playwright/snapshots/right-panel/right-panel.spec.ts/with-name-and-address-linux.png differ diff --git a/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png b/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png index 469a8017d8..01a6c6089b 100644 Binary files a/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png and b/playwright/snapshots/settings/general-room-settings-tab.spec.ts/General-room-settings-tab-should-be-rendered-properly-1-linux.png differ diff --git a/res/css/views/right_panel/_RoomSummaryCard.pcss b/res/css/views/right_panel/_RoomSummaryCard.pcss index 5c3cab320c..da1a5a2510 100644 --- a/res/css/views/right_panel/_RoomSummaryCard.pcss +++ b/res/css/views/right_panel/_RoomSummaryCard.pcss @@ -15,6 +15,9 @@ limitations under the License. */ .mx_RoomSummaryCard { + --cpd-separator-inset: var(--cpd-space-4x); + --cpd-separator-spacing: var(--cpd-space-4x); + .mx_RoomSummaryCard_container { text-align: center; margin: $spacing-20 var(--cpd-space-4x) 0; diff --git a/res/css/views/rooms/_RoomPreviewBar.pcss b/res/css/views/rooms/_RoomPreviewBar.pcss index be50c9faf2..a3fae0e008 100644 --- a/res/css/views/rooms/_RoomPreviewBar.pcss +++ b/res/css/views/rooms/_RoomPreviewBar.pcss @@ -163,6 +163,10 @@ a.mx_RoomPreviewBar_inviter { cursor: pointer; } +.mx_RoomPreviewBar_inviter_mxid { + color: var(--cpd-color-text-secondary); +} + .mx_RoomPreviewBar_icon { margin-right: 8px; vertical-align: text-top; diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index bb35242bc7..0102974423 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -385,18 +385,11 @@ const RoomSummaryCard: React.FC = ({ disabled={!canInviteToState} onSelect={() => inviteToRoom(room)} /> - - + {!isVideoRoom && ( <> - - {pinningEnabled && ( = ({ )} + + + )} + + + + + + {!isVideoRoom && ( + <> + = ({ )} + + { break; } case MessageCase.Invite: { + const isDM = this.isDMInvite(); const avatar = ; const inviteMember = this.getInviteMember(); - let inviterElement: JSX.Element; - if (inviteMember) { - inviterElement = ( - - {inviteMember.rawDisplayName} ( - {inviteMember.userId}) - - ); - } else { - inviterElement = {this.props.inviterName}; - } + const userName = ( + + {inviteMember?.rawDisplayName ?? this.props.inviterName} + + ); + const inviterElement = ( + <> + {isDM + ? _t("room|dm_invite_subtitle", {}, { userName }) + : _t("room|invite_subtitle", {}, { userName })} + {inviteMember && ( + <> +
+ {inviteMember.userId} + + )} + + ); - const isDM = this.isDMInvite(); if (isDM) { title = _t("room|dm_invite_title", { user: inviteMember?.name ?? this.props.inviterName, }); - subTitle = [avatar, _t("room|dm_invite_subtitle", {}, { userName: () => inviterElement })]; primaryActionLabel = _t("room|dm_invite_action"); } else { title = _t("room|invite_title", { roomName }); - subTitle = [avatar, _t("room|invite_subtitle", {}, { userName: () => inviterElement })]; primaryActionLabel = _t("action|accept"); } + subTitle = [avatar, inviterElement]; const myUserId = MatrixClientPeg.safeGet().getSafeUserId(); const member = this.props.room?.currentState.getMember(myUserId); diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx index 8f51a71e72..be096c3797 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.tsx @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { useCallback, useContext, useEffect } from "react"; import { HTTPError } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; @@ -34,69 +34,106 @@ import { SettingsSection } from "../../shared/SettingsSection"; import SettingsSubsection, { SettingsSubsectionText } from "../../shared/SettingsSubsection"; import { SDKContext } from "../../../../../contexts/SDKContext"; import UserPersonalInfoSettings from "../../UserPersonalInfoSettings"; +import { useMatrixClientContext } from "../../../../../contexts/MatrixClientContext"; interface IProps { closeSettingsFn: () => void; } -interface IState { +interface AccountSectionProps { canChangePassword: boolean; - idServerName?: string; - externalAccountManagementUrl?: string; - canMake3pidChanges: boolean; - canSetDisplayName: boolean; - canSetAvatar: boolean; + onPasswordChangeError: (e: Error) => void; + onPasswordChanged: () => void; } -export default class GeneralUserSettingsTab extends React.Component { - public static contextType = SDKContext; - public declare context: React.ContextType; +const AccountSection: React.FC = ({ + canChangePassword, + onPasswordChangeError, + onPasswordChanged, +}) => { + if (!canChangePassword) return <>; - public constructor(props: IProps, context: React.ContextType) { - super(props, context); + return ( + <> + + {_t("settings|general|password_change_section")} + + + + ); +}; - this.state = { - canChangePassword: false, - canMake3pidChanges: false, - canSetDisplayName: false, - canSetAvatar: false, - }; +interface ManagementSectionProps { + onDeactivateClicked: () => void; +} - this.getCapabilities(); - } +const ManagementSection: React.FC = ({ onDeactivateClicked }) => { + return ( + + + + {_t("settings|general|deactivate_section")} + + + + ); +}; - private async getCapabilities(): Promise { - const cli = this.context.client!; +const GeneralUserSettingsTab: React.FC = ({ closeSettingsFn }) => { + const [externalAccountManagementUrl, setExternalAccountManagementUrl] = React.useState(); + const [canMake3pidChanges, setCanMake3pidChanges] = React.useState(false); + const [canSetDisplayName, setCanSetDisplayName] = React.useState(false); + const [canSetAvatar, setCanSetAvatar] = React.useState(false); + const [canChangePassword, setCanChangePassword] = React.useState(false); - const capabilities = (await cli.getCapabilities()) ?? {}; - const changePasswordCap = capabilities["m.change_password"]; + const cli = useMatrixClientContext(); + const sdkContext = useContext(SDKContext); - // You can change your password so long as the capability isn't explicitly disabled. The implicit - // behaviour is you can change your password when the capability is missing or has not-false as - // the enabled flag value. - const canChangePassword = !changePasswordCap || changePasswordCap["enabled"] !== false; + useEffect(() => { + (async () => { + const capabilities = (await cli.getCapabilities()) ?? {}; + const changePasswordCap = capabilities["m.change_password"]; - await this.context.oidcClientStore.readyPromise; // wait for the store to be ready - const externalAccountManagementUrl = this.context.oidcClientStore.accountManagementEndpoint; - // https://spec.matrix.org/v1.7/client-server-api/#m3pid_changes-capability - // We support as far back as v1.1 which doesn't have m.3pid_changes - // so the behaviour for when it is missing has to be assume true - const canMake3pidChanges = !capabilities["m.3pid_changes"] || capabilities["m.3pid_changes"].enabled === true; + // You can change your password so long as the capability isn't explicitly disabled. The implicit + // behaviour is you can change your password when the capability is missing or has not-false as + // the enabled flag value. + const canChangePassword = !changePasswordCap || changePasswordCap["enabled"] !== false; - const canSetDisplayName = - !capabilities["m.set_displayname"] || capabilities["m.set_displayname"].enabled === true; - const canSetAvatar = !capabilities["m.set_avatar_url"] || capabilities["m.set_avatar_url"].enabled === true; + await sdkContext.oidcClientStore.readyPromise; // wait for the store to be ready + const externalAccountManagementUrl = sdkContext.oidcClientStore.accountManagementEndpoint; + // https://spec.matrix.org/v1.7/client-server-api/#m3pid_changes-capability + // We support as far back as v1.1 which doesn't have m.3pid_changes + // so the behaviour for when it is missing has to be assume true + const canMake3pidChanges = + !capabilities["m.3pid_changes"] || capabilities["m.3pid_changes"].enabled === true; - this.setState({ - canChangePassword, - externalAccountManagementUrl, - canMake3pidChanges, - canSetDisplayName, - canSetAvatar, - }); - } + const canSetDisplayName = + !capabilities["m.set_displayname"] || capabilities["m.set_displayname"].enabled === true; + const canSetAvatar = !capabilities["m.set_avatar_url"] || capabilities["m.set_avatar_url"].enabled === true; - private onPasswordChangeError = (err: Error): void => { + setCanMake3pidChanges(canMake3pidChanges); + setCanSetDisplayName(canSetDisplayName); + setCanSetAvatar(canSetAvatar); + setExternalAccountManagementUrl(externalAccountManagementUrl); + setCanChangePassword(canChangePassword); + })(); + }, [cli, sdkContext.oidcClientStore]); + + const onPasswordChangeError = useCallback((err: Error): void => { logger.error("Failed to change password: " + err); let underlyingError = err; @@ -126,85 +163,49 @@ export default class GeneralUserSettingsTab extends React.Component { + const onPasswordChanged = useCallback((): void => { const description = _t("settings|general|password_change_success"); // TODO: Figure out a design that doesn't involve replacing the current dialog Modal.createDialog(ErrorDialog, { title: _t("common|success"), description, }); - }; + }, []); - private onDeactivateClicked = (): void => { + const onDeactivateClicked = useCallback((): void => { Modal.createDialog(DeactivateAccountDialog, { onFinished: (success) => { - if (success) this.props.closeSettingsFn(); + if (success) closeSettingsFn(); }, }); - }; + }, [closeSettingsFn]); - private renderAccountSection(): JSX.Element | undefined { - if (!this.state.canChangePassword) return undefined; - - return ( - <> - - {_t("settings|general|password_change_section")} - - - - ); + let accountManagementSection: JSX.Element | undefined; + const isAccountManagedExternally = Boolean(externalAccountManagementUrl); + if (SettingsStore.getValue(UIFeature.Deactivate) && !isAccountManagedExternally) { + accountManagementSection = ; } - private renderManagementSection(): JSX.Element { - // TODO: Improve warning text for account deactivation - return ( - - - - {_t("settings|general|deactivate_section")} - - + return ( + + + + + - ); - } + {accountManagementSection} + + ); +}; - public render(): React.ReactNode { - let accountManagementSection: JSX.Element | undefined; - const isAccountManagedExternally = !!this.state.externalAccountManagementUrl; - if (SettingsStore.getValue(UIFeature.Deactivate) && !isAccountManagedExternally) { - accountManagementSection = this.renderManagementSection(); - } - - return ( - - - - - {this.renderAccountSection()} - - {accountManagementSection} - - ); - } -} +export default GeneralUserSettingsTab; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0d6319d80a..1cc22ac382 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1846,7 +1846,7 @@ }, "title": "Pinned messages" }, - "pinned_messages_button": "Pinned", + "pinned_messages_button": "Pinned messages", "poll": { "active_heading": "Active polls", "empty_active": "There are no active polls in this room", @@ -1871,7 +1871,7 @@ "view_in_timeline": "View poll in timeline", "view_poll": "View poll" }, - "polls_button": "Poll history", + "polls_button": "Polls", "room_summary_card": { "title": "Room info" }, @@ -1991,7 +1991,7 @@ "invite_reject_ignore": "Reject & Ignore user", "invite_sent_to_email": "This invite was sent to %(email)s", "invite_sent_to_email_room": "This invite to %(roomName)s was sent to %(email)s", - "invite_subtitle": " invited you", + "invite_subtitle": "Invited by ", "invite_this_room": "Invite to this room", "invite_title": "Do you want to join %(roomName)s?", "inviter_unknown": "Unknown", diff --git a/test/components/views/dialogs/RoomSettingsDialog-test.tsx b/test/components/views/dialogs/RoomSettingsDialog-test.tsx index a94e35dc71..d028d43a93 100644 --- a/test/components/views/dialogs/RoomSettingsDialog-test.tsx +++ b/test/components/views/dialogs/RoomSettingsDialog-test.tsx @@ -183,7 +183,7 @@ describe("", () => { it("displays poll history when tab clicked", () => { const { container } = getComponent(); - fireEvent.click(screen.getByText("Poll history")); + fireEvent.click(screen.getByText("Polls")); expect(container.querySelector(".mx_SettingsTab")).toMatchSnapshot(); }); diff --git a/test/components/views/dialogs/__snapshots__/RoomSettingsDialog-test.tsx.snap b/test/components/views/dialogs/__snapshots__/RoomSettingsDialog-test.tsx.snap index 6c148400d8..8f60a60c08 100644 --- a/test/components/views/dialogs/__snapshots__/RoomSettingsDialog-test.tsx.snap +++ b/test/components/views/dialogs/__snapshots__/RoomSettingsDialog-test.tsx.snap @@ -89,7 +89,7 @@ NodeList [ class="mx_TabbedView_tabLabel_text" id="mx_tabpanel_ROOM_POLL_HISTORY_TAB_label" > - Poll history + Polls , ] @@ -105,7 +105,7 @@ exports[` poll history displays poll history when tab clic

- Poll history + Polls

", () => { fireEvent.click(getByText("Question?")); - expect(queryByText("Poll history")).not.toBeInTheDocument(); + expect(queryByText("Polls")).not.toBeInTheDocument(); // elements from MPollBody expect(getByText("Question?")).toMatchSnapshot(); expect(getByText("Socks")).toBeInTheDocument(); @@ -397,13 +397,13 @@ describe("", () => { expect(getByText("Question?")).toBeInTheDocument(); // header not shown - expect(queryByText("Poll history")).not.toBeInTheDocument(); + expect(queryByText("Polls")).not.toBeInTheDocument(); expect(getByText("Active polls")).toMatchSnapshot(); fireEvent.click(getByText("Active polls")); // main list header displayed again - expect(getByText("Poll history")).toBeInTheDocument(); + expect(getByText("Polls")).toBeInTheDocument(); // active filter still active expect(getByTestId("filter-tab-PollHistory_filter-ACTIVE").firstElementChild).toBeChecked(); // list displayed diff --git a/test/components/views/polls/pollHistory/__snapshots__/PollHistory-test.tsx.snap b/test/components/views/polls/pollHistory/__snapshots__/PollHistory-test.tsx.snap index 70f66bb803..f78f2e4642 100644 --- a/test/components/views/polls/pollHistory/__snapshots__/PollHistory-test.tsx.snap +++ b/test/components/views/polls/pollHistory/__snapshots__/PollHistory-test.tsx.snap @@ -37,7 +37,7 @@ exports[` renders a list of active polls when there are polls in

- Poll history + Polls

", () => { mocked(settingsHooks.useFeatureEnabled).mockImplementation((feature) => feature === "feature_pinning"); const { getByText } = getComponent(); - expect(getByText("Pinned")).toBeInTheDocument(); + expect(getByText("Pinned messages")).toBeInTheDocument(); }); }); @@ -279,14 +279,14 @@ describe("", () => { it("renders poll history option", () => { const { getByText } = getComponent(); - expect(getByText("Poll history")).toBeInTheDocument(); + expect(getByText("Polls")).toBeInTheDocument(); }); it("opens poll history dialog on button click", () => { const permalinkCreator = new RoomPermalinkCreator(room); const { getByText } = getComponent({ permalinkCreator }); - fireEvent.click(getByText("Poll history")); + fireEvent.click(getByText("Polls")); expect(Modal.createDialog).toHaveBeenCalledWith(PollHistoryDialog, { room, diff --git a/test/components/views/right_panel/__snapshots__/RoomSummaryCard-test.tsx.snap b/test/components/views/right_panel/__snapshots__/RoomSummaryCard-test.tsx.snap index 31502f1bb2..240f101417 100644 --- a/test/components/views/right_panel/__snapshots__/RoomSummaryCard-test.tsx.snap +++ b/test/components/views/right_panel/__snapshots__/RoomSummaryCard-test.tsx.snap @@ -180,6 +180,55 @@ exports[` has button to edit topic 1`] = ` /> +