diff --git a/cypress/e2e/read-receipts/read-receipts.spec.ts b/cypress/e2e/read-receipts/read-receipts.spec.ts new file mode 100644 index 00000000000..da84b0060f5 --- /dev/null +++ b/cypress/e2e/read-receipts/read-receipts.spec.ts @@ -0,0 +1,133 @@ +/* +Copyright 2023 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 type { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix"; +import type { ISendEventResponse } from "matrix-js-sdk/src/@types/requests"; +import type { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts"; +import { HomeserverInstance } from "../../plugins/utils/homeserver"; + +describe("Read receipts", () => { + const userName = "Mae"; + const botName = "Other User"; + const selectedRoomName = "Selected Room"; + const otherRoomName = "Other Room"; + + let homeserver: HomeserverInstance; + let otherRoomId: string; + let selectedRoomId: string; + let bot: MatrixClient | undefined; + + const botSendMessage = (): Cypress.Chainable => { + return cy.botSendMessage(bot, otherRoomId, "Message"); + }; + + const fakeEventFromSent = (eventResponse: ISendEventResponse): MatrixEvent => { + return { + getRoomId: () => otherRoomId, + getId: () => eventResponse.event_id, + threadRootId: undefined, + getTs: () => 1, + } as any as MatrixEvent; + }; + + beforeEach(() => { + /* + * Create 2 rooms: + * + * - Selected room - this one is clicked in the UI + * - Other room - this one contains the bot, which will send events so + * we can check its unread state. + */ + cy.startHomeserver("default").then((data) => { + homeserver = data; + cy.initTestUser(homeserver, userName) + .then(() => { + cy.createRoom({ name: selectedRoomName }).then((createdRoomId) => { + selectedRoomId = createdRoomId; + }); + }) + .then(() => { + cy.createRoom({ name: otherRoomName }).then((createdRoomId) => { + otherRoomId = createdRoomId; + }); + }) + .then(() => { + cy.getBot(homeserver, { displayName: botName }).then((botClient) => { + bot = botClient; + }); + }) + .then(() => { + // Invite the bot to Other room + cy.inviteUser(otherRoomId, bot.getUserId()); + cy.visit("/#/room/" + otherRoomId); + cy.findByText(botName + " joined the room").should("exist"); + + // Then go into Selected room + cy.visit("/#/room/" + selectedRoomId); + }); + }); + }); + + afterEach(() => { + cy.stopHomeserver(homeserver); + }); + + it( + "Considers room read if there's a receipt for main even if an earlier unthreaded receipt exists #24629", + { + // When #24629 exists, the test fails the first time but passes later, so we disable retries + // to be sure we are going to fail if the bug comes back. + // Why does it pass the second time? I wish I knew. (andyb) + retries: 0, + }, + () => { + // Details are in https://github.com/vector-im/element-web/issues/24629 + // This proves we've fixed one of the "stuck unreads" issues. + + // Given we sent 3 events on the main thread + botSendMessage(); + botSendMessage().then((main2) => { + botSendMessage().then((main3) => { + // (So the room starts off unread) + cy.findByLabelText(`${otherRoomName} 3 unread messages.`).should("exist"); + + // When we send a threaded receipt for the last event in main + // And an unthreaded receipt for an earlier event + cy.sendReadReceipt(fakeEventFromSent(main3)); + cy.sendReadReceipt(fakeEventFromSent(main2), "m.read" as any as ReceiptType, true); + + // (So the room has no unreads) + cy.findByLabelText(`${otherRoomName}`).should("exist"); + + // And we persuade the app to persist its state to indexeddb by reloading and waiting + cy.reload(); + cy.findByLabelText(`${selectedRoomName}`).should("exist"); + + // And we reload again, fetching the persisted state FROM indexeddb + cy.reload(); + + // Then the room is read, because the persisted state correctly remembers both + // receipts. (In #24629, the unthreaded receipt overwrote the main thread one, + // meaning that the room still said it had unread messages.) + cy.findByLabelText(`${otherRoomName}`).should("exist"); + cy.findByLabelText(`${otherRoomName} Unread messages.`).should("not.exist"); + }); + }); + }, + ); +}); diff --git a/cypress/e2e/timeline/timeline.spec.ts b/cypress/e2e/timeline/timeline.spec.ts index 06b289d1e2f..c2743a3c891 100644 --- a/cypress/e2e/timeline/timeline.spec.ts +++ b/cypress/e2e/timeline/timeline.spec.ts @@ -207,7 +207,7 @@ describe("Timeline", () => { cy.get(".mx_GenericEventListSummary[data-layout=irc] .mx_GenericEventListSummary_spacer").should( "have.css", "line-height", - "18px", // $irc-line-height: $font-18px (See: _IRCLayout.pcss) + "18px", // var(--irc-line-height): $font-18px (See: _IRCLayout.pcss) ); cy.get(".mx_MainSplit").percySnapshotElement("Expanded GELS on IRC layout", { percyCSS }); diff --git a/cypress/plugins/docker/index.ts b/cypress/plugins/docker/index.ts index 9f755da6742..66bab0b8532 100644 --- a/cypress/plugins/docker/index.ts +++ b/cypress/plugins/docker/index.ts @@ -36,12 +36,21 @@ export async function dockerRun(opts: { const params = opts.params ?? []; if (params?.includes("-v") && userInfo.uid >= 0) { - // On *nix we run the docker container as our uid:gid otherwise cleaning it up its media_store can be difficult - params.push("-u", `${userInfo.uid}:${userInfo.gid}`); - + // Run the docker container as our uid:gid to prevent problems with permissions. if (await isPodman()) { - // keep the user ID if the docker command is actually podman - params.push("--userns=keep-id"); + // Note: this setup is for podman rootless containers. + + // In podman, run as root in the container, which maps to the current + // user on the host. This is probably the default since Synapse's + // Dockerfile doesn't specify, but we're being explicit here + // because it's important for the permissions to work. + params.push("-u", "0:0"); + + // Tell Synapse not to switch UID + params.push("-e", "UID=0"); + params.push("-e", "GID=0"); + } else { + params.push("-u", `${userInfo.uid}:${userInfo.gid}`); } } diff --git a/cypress/support/client.ts b/cypress/support/client.ts index 535669d6be4..fca966613e6 100644 --- a/cypress/support/client.ts +++ b/cypress/support/client.ts @@ -20,7 +20,8 @@ import type { FileType, Upload, UploadOpts } from "matrix-js-sdk/src/http-api"; import type { ICreateRoomOpts, ISendEventResponse } from "matrix-js-sdk/src/@types/requests"; import type { MatrixClient } from "matrix-js-sdk/src/client"; import type { Room } from "matrix-js-sdk/src/models/room"; -import type { IContent } from "matrix-js-sdk/src/models/event"; +import type { IContent, MatrixEvent } from "matrix-js-sdk/src/models/event"; +import type { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts"; import Chainable = Cypress.Chainable; import { UserCredentials } from "./login"; @@ -69,6 +70,13 @@ declare global { eventType: string, content: IContent, ): Chainable; + /** + * @param {MatrixEvent} event + * @param {ReceiptType} receiptType + * @param {boolean} unthreaded + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ + sendReadReceipt(event: MatrixEvent, receiptType?: ReceiptType, unthreaded?: boolean): Chainable<{}>; /** * @param {string} name * @param {module:client.callback} callback Optional. @@ -195,6 +203,15 @@ Cypress.Commands.add( }, ); +Cypress.Commands.add( + "sendReadReceipt", + (event: MatrixEvent, receiptType?: ReceiptType, unthreaded?: boolean): Chainable<{}> => { + return cy.getClient().then(async (cli: MatrixClient) => { + return cli.sendReadReceipt(event, receiptType, unthreaded); + }); + }, +); + Cypress.Commands.add("setDisplayName", (name: string): Chainable<{}> => { return cy.getClient().then(async (cli: MatrixClient) => { return cli.setDisplayName(name); diff --git a/res/css/_common.pcss b/res/css/_common.pcss index 79dd8218f26..1502b60c70b 100644 --- a/res/css/_common.pcss +++ b/res/css/_common.pcss @@ -33,13 +33,12 @@ $MessageTimestamp_width_hover: calc($MessageTimestamp_width - 2 * $selected-mess $slider-dot-size: 1em; $slider-selection-dot-size: 2.4em; -$container-border-width: 8px; -$container-gap-width: 8px; /* only even numbers should be used because otherwise we get 0.5px margin values. */ - $timeline-image-border-radius: 8px; :root { font-size: 10px; + --container-border-width: 8px; + --container-gap-width: 8px; /* only even numbers should be used because otherwise we get 0.5px margin values. */ --transition-short: 0.1s; --transition-standard: 0.3s; --MessageTimestamp-width: $MessageTimestamp_width; diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 94153b2e894..85ffa5c2382 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -322,7 +322,6 @@ @import "./views/settings/_CrossSigningPanel.pcss"; @import "./views/settings/_CryptographyPanel.pcss"; @import "./views/settings/_DevicesPanel.pcss"; -@import "./views/settings/_EmailAddresses.pcss"; @import "./views/settings/_FontScalingPanel.pcss"; @import "./views/settings/_ImageSizePanel.pcss"; @import "./views/settings/_IntegrationManager.pcss"; diff --git a/res/css/structures/_MainSplit.pcss b/res/css/structures/_MainSplit.pcss index 07dabdfa210..35e4ea25c7d 100644 --- a/res/css/structures/_MainSplit.pcss +++ b/res/css/structures/_MainSplit.pcss @@ -23,10 +23,10 @@ limitations under the License. } .mx_MainSplit > .mx_RightPanel_ResizeWrapper { - padding: $container-gap-width; + padding: var(--container-gap-width); /* The resizer should be centered: only half of the gap-width is handled by the right panel. */ /* The other half by the RoomView. */ - padding-left: calc($container-gap-width / 2); + padding-left: calc(var(--container-gap-width) / 2); height: calc(100vh - 51px); /* height of .mx_RoomHeader.light-panel */ &:hover .mx_ResizeHandle_horizontal::before { diff --git a/res/css/structures/_MatrixChat.pcss b/res/css/structures/_MatrixChat.pcss index 09157eac1d4..760527a7ffd 100644 --- a/res/css/structures/_MatrixChat.pcss +++ b/res/css/structures/_MatrixChat.pcss @@ -81,7 +81,7 @@ limitations under the License. /* or less, Safari and other WebKit based browsers get confused about overflows somehow and */ /* https://github.com/vector-im/element-web/issues/19863 happens. */ .mx_MatrixChat > .mx_ResizeHandle.mx_ResizeHandle_horizontal { - margin: 0 calc(-5.5px - $container-gap-width / 2) 0 calc(-6.5px + $container-gap-width / 2); + margin: 0 calc(-5.5px - var(--container-gap-width) / 2) 0 calc(-6.5px + var(--container-gap-width) / 2); /* The condition to prevent bleeding is: (margin-left + margin-right < -11px) */ /* (IF there is NO margin on the leftPanel_wrapper) */ /* The resizeHandle does not change the gap between the left panel and the room view: */ @@ -91,7 +91,7 @@ limitations under the License. /* the handle requires no space */ /* right: -6px left: -6px positions the element exactly on the edge of leftPanel. */ /* left+=1 and right-=1 => resizeHandle moves 1px to the right closer to the center of the gap. */ - /* We want the handle to be in the middle of the gap so it is shifted by ($container-gap-width / 2) */ + /* We want the handle to be in the middle of the gap so it is shifted by (var(--container-gap-width) / 2) */ } .mx_MatrixChat > .mx_ResizeHandle_horizontal:hover { diff --git a/res/css/structures/_RightPanel.pcss b/res/css/structures/_RightPanel.pcss index 3c6a292c9cd..4bfd555b3c7 100644 --- a/res/css/structures/_RightPanel.pcss +++ b/res/css/structures/_RightPanel.pcss @@ -22,7 +22,7 @@ limitations under the License. display: flex; flex-direction: column; border-radius: 8px; - padding: $container-border-width 0; + padding: var(--container-border-width) 0; box-sizing: border-box; height: 100%; contain: strict; diff --git a/res/css/structures/_RoomView.pcss b/res/css/structures/_RoomView.pcss index 1ef90f80db2..67626af2877 100644 --- a/res/css/structures/_RoomView.pcss +++ b/res/css/structures/_RoomView.pcss @@ -109,7 +109,7 @@ limitations under the License. flex: 1; display: flex; flex-direction: column; - margin-right: calc($container-gap-width / 2); + margin-right: calc(var(--container-gap-width) / 2); } } diff --git a/res/css/views/messages/_MImageReplyBody.pcss b/res/css/views/messages/_MImageReplyBody.pcss index f1b9514adc9..51ed8ed2bdf 100644 --- a/res/css/views/messages/_MImageReplyBody.pcss +++ b/res/css/views/messages/_MImageReplyBody.pcss @@ -18,23 +18,8 @@ limitations under the License. display: flex; column-gap: $spacing-4; - .mx_MImageBody_thumbnail_container, - .mx_MImageReplyBody_info { + .mx_MImageBody_thumbnail_container { flex: 1; min-width: 0; /* Prevent a blowout */ } - - .mx_MImageReplyBody_info { - .mx_MImageReplyBody_sender { - grid-area: sender; - - .mx_DisambiguatedProfile { - max-width: 100%; - } - } - - .mx_MImageReplyBody_filename { - grid-area: filename; - } - } } diff --git a/res/css/views/right_panel/_TimelineCard.pcss b/res/css/views/right_panel/_TimelineCard.pcss index 13f949b39e5..acfeaf75551 100644 --- a/res/css/views/right_panel/_TimelineCard.pcss +++ b/res/css/views/right_panel/_TimelineCard.pcss @@ -134,7 +134,7 @@ limitations under the License. &.mx_EventTile_info .mx_MessageActionBar { /* 1px: border width */ - inset-inline-end: calc($container-gap-width + var(--BaseCard_padding-inline) + 1px); + inset-inline-end: calc(var(--container-gap-width) + var(--BaseCard_padding-inline) + 1px); } .mx_ReactionsRow { @@ -178,7 +178,7 @@ limitations under the License. /* 6px: scroll bar width (magic number) */ /* stylelint-disable-next-line declaration-colon-space-after */ inset-inline-end: calc( - -1 * var(--ReadReceiptGroup_EventBubbleTile-spacing-end) + $container-gap-width + 6px + -1 * var(--ReadReceiptGroup_EventBubbleTile-spacing-end) + var(--container-gap-width) + 6px ); } diff --git a/res/css/views/right_panel/_WidgetCard.pcss b/res/css/views/right_panel/_WidgetCard.pcss index 6759cbc0d8a..b50f30145e3 100644 --- a/res/css/views/right_panel/_WidgetCard.pcss +++ b/res/css/views/right_panel/_WidgetCard.pcss @@ -22,8 +22,3 @@ limitations under the License. border: 0; } } - -.mx_WidgetCard_maxPinnedTooltip { - background-color: $alert; - color: #ffffff; -} diff --git a/res/css/views/room_settings/_AliasSettings.pcss b/res/css/views/room_settings/_AliasSettings.pcss index 15a7b8a8708..31f111fa698 100644 --- a/res/css/views/room_settings/_AliasSettings.pcss +++ b/res/css/views/room_settings/_AliasSettings.pcss @@ -14,19 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_AliasSettings_editable { - border: 0px; - border-bottom: 1px solid $strong-input-border-color; - padding: 0px; - min-width: 240px; -} - -.mx_AliasSettings_editable:focus { - border-bottom: 1px solid $accent; - outline: none; - box-shadow: none; -} - .mx_AliasSettings_localAddresses { cursor: pointer; color: $accent; diff --git a/res/css/views/rooms/_AppsDrawer.pcss b/res/css/views/rooms/_AppsDrawer.pcss index bb11e92acd1..7c9ae0b51a9 100644 --- a/res/css/views/rooms/_AppsDrawer.pcss +++ b/res/css/views/rooms/_AppsDrawer.pcss @@ -20,9 +20,9 @@ $MiniAppTileHeight: 220px; $MinWidth: 240px; .mx_AppsDrawer { - margin: $container-gap-width; + margin: var(--container-gap-width); /* The left side gap is fully handled by this margin. To prohibit bleeding on webkit browser. */ - margin-right: calc($container-gap-width / 2); + margin-right: calc(var(--container-gap-width) / 2); margin-bottom: 0; /* No bottom margin for the correct gap to the CallView below. */ position: relative; display: flex; @@ -31,7 +31,7 @@ $MinWidth: 240px; flex-grow: 1; &.mx_AppsDrawer_maximise { - margin-bottom: $container-gap-width; + margin-bottom: var(--container-gap-width); } .mx_AppsContainer_resizerHandleContainer { @@ -118,7 +118,7 @@ $MinWidth: 240px; } .mx_AppsContainer_resizer { - margin-bottom: $container-gap-width; + margin-bottom: var(--container-gap-width); } .mx_AppsContainer { @@ -132,11 +132,11 @@ $MinWidth: 240px; min-height: 0; .mx_AppTile:first-of-type { - border-left-width: $container-border-width; + border-left-width: var(--container-border-width); border-radius: 10px 0 0 10px; } .mx_AppTile:last-of-type { - border-right-width: $container-border-width; + border-right-width: var(--container-border-width); border-radius: 0 10px 10px 0; } @@ -150,7 +150,7 @@ $MinWidth: 240px; } .mx_AppTile { - border: $container-border-width solid $widget-menu-bar-bg-color; + border: var(--container-border-width) solid $widget-menu-bar-bg-color; display: flex; flex-direction: column; box-sizing: border-box; @@ -161,7 +161,7 @@ $MinWidth: 240px; width: 100% !important; /* to override the inline style set by the resizer */ margin: 0; padding: 0; - border: $container-border-width solid $widget-menu-bar-bg-color; + border: var(--container-border-width) solid $widget-menu-bar-bg-color; border-radius: 8px; display: flex; flex-direction: column; @@ -251,23 +251,23 @@ $MinWidth: 240px; width: 24px; } - &.mx_AppTileMenuBar_iconButton_collapse::before { + &.mx_AppTileMenuBar_iconButton--collapse::before { mask-image: url("$(res)/img/element-icons/minimise-collapse.svg"); } - &.mx_AppTileMenuBar_iconButton_maximise::before { + &.mx_AppTileMenuBar_iconButton--maximise::before { mask-image: url("$(res)/img/element-icons/maximise-expand.svg"); } - &.mx_AppTileMenuBar_iconButton_minimise::before { + &.mx_AppTileMenuBar_iconButton--minimise::before { mask-image: url("$(res)/img/element-icons/minus-button.svg"); } - &.mx_AppTileMenuBar_iconButton_popout::before { + &.mx_AppTileMenuBar_iconButton--popout::before { mask-image: url("$(res)/img/feather-customised/widget/external-link.svg"); } - &.mx_AppTileMenuBar_iconButton_menu::before { + &.mx_AppTileMenuBar_iconButton--menu::before { mask-image: url("$(res)/img/element-icons/room/ellipsis.svg"); } } diff --git a/res/css/views/rooms/_EventTile.pcss b/res/css/views/rooms/_EventTile.pcss index 88b600365a6..bde48014d33 100644 --- a/res/css/views/rooms/_EventTile.pcss +++ b/res/css/views/rooms/_EventTile.pcss @@ -281,22 +281,156 @@ $left-gutter: 64px; } &[data-layout="irc"] { + --EventTile_irc_line-padding-block: 1px; + /* add --right-padding value of MessageTimestamp only */ /* stylelint-disable-next-line declaration-colon-space-after */ --EventTile_irc_line_info-margin-inline-start: calc( var(--name-width) + var(--icon-width) + 1 * var(--right-padding) ); + display: flex; + align-items: flex-start; + padding-top: 0; + + > a { + text-decoration: none; /* timestamps are links which shouldn't be underlined */ + min-width: $MessageTimestamp_width; /* ensure space for EventTile without timestamp */ + } + + > * { + margin-right: var(--right-padding); + } + + .mx_EventTile_avatar, + .mx_EventTile_e2eIcon { + height: var(--irc-line-height); + } + + .mx_EventTile_avatar, + .mx_DisambiguatedProfile, + .mx_EventTile_e2eIcon, + .mx_EventTile_msgOption { + flex-shrink: 0; + } + + .mx_EventTile_avatar { + order: 1; + position: relative; + display: flex; + align-items: center; + + /* Need to use important to override the js provided height and width values. */ + > .mx_BaseAvatar, + > .mx_BaseAvatar > * { + height: $font-14px !important; + width: $font-14px !important; + font-size: $font-10px !important; + line-height: $font-15px !important; + } + } + + .mx_DisambiguatedProfile { + order: 2; + width: var(--name-width); + margin-inline-end: 0; /* override mx_EventTile > * */ + + > .mx_DisambiguatedProfile_displayName { + width: 100%; + text-align: end; + overflow: hidden; + text-overflow: ellipsis; + } + + > .mx_DisambiguatedProfile_mxid { + visibility: collapse; + margin-left: 0; /* Override the inherited margin. */ + padding: 0 5px; /* TODO: Use a spacing variable */ + } + + &:hover { + overflow: visible; + z-index: 10; + + > .mx_DisambiguatedProfile_displayName { + overflow: visible; + display: inline; + background-color: $event-selected-color; + border-radius: 8px 0 0 8px; + padding-right: $spacing-8; + } + + > .mx_DisambiguatedProfile_mxid { + visibility: visible; + opacity: 1; + background-color: $event-selected-color; + } + } + } + + .mx_EventTile_e2eIcon { + padding: 0; + flex-grow: 0; + background-position: center; + } + + .mx_EventTile_line { + .mx_EventTile_e2eIcon, + .mx_TextualEvent, + .mx_ViewSourceEvent, + .mx_MTextBody { + /* add a 1px padding top and bottom because our larger + emoji font otherwise gets cropped by anti-zalgo */ + padding: var(--EventTile_irc_line-padding-block) 0; + } + + .mx_EventTile_e2eIcon, + .mx_TextualEvent, + .mx_MTextBody { + display: inline-block; + } + + .mx_ReplyTile { + .mx_MTextBody { + display: -webkit-box; /* Enable -webkit-line-clamp */ + } + } + } + + .mx_EventTile_line, + .mx_EventTile_reply { + order: 3; + display: flex; + flex-direction: column; + flex-grow: 1; + flex-shrink: 1; + min-width: 0; + } + + .mx_EventTile_reply { + order: 4; + } + .mx_EventTile_msgOption { + order: 5; + .mx_ReadReceiptGroup { - inset-block-start: -0.3rem; /* ($irc-line-height - avatar height) / 2 */ + inset-block-start: -0.3rem; /* (var(--irc-line-height) - avatar height) / 2 */ } } + .mx_ReplyChain { + margin: 0; + } + .mx_MessageTimestamp { text-align: right; } + .mx_EditMessageComposer_buttons { + position: relative; + } + .mx_EventTileBubble { position: relative; left: var(--EventTile_irc_line_info-margin-inline-start); @@ -306,10 +440,6 @@ $left-gutter: 64px; } } - .mx_ReplyChain { - margin: 0; - } - .mx_ReplyTile .mx_EventTileBubble { left: unset; /* Cancel the value specified above for the tile inside ReplyTile */ } @@ -332,6 +462,18 @@ $left-gutter: 64px; .mx_EventTile_line { margin-inline-start: var(--EventTile_irc_line_info-margin-inline-start); } + + .mx_ViewSourceEvent, /* For hidden events */ + .mx_TextualEvent { + line-height: var(--irc-line-height); + } + } + + &.mx_EventTile_emote { + .mx_EventTile_avatar { + /* add --right-padding value of MessageTimestamp only */ + margin-left: calc(var(--name-width) + var(--icon-width) + 1 * var(--right-padding)); + } } } diff --git a/res/css/views/rooms/_IRCLayout.pcss b/res/css/views/rooms/_IRCLayout.pcss index cd4371b6a02..07400f2f497 100644 --- a/res/css/views/rooms/_IRCLayout.pcss +++ b/res/css/views/rooms/_IRCLayout.pcss @@ -14,12 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -$irc-line-height: $font-18px; +:root { + --irc-line-height: $font-18px; +} .mx_IRCLayout { --name-width: 80px; /* cf. ircDisplayNameWidth on Settings.tsx */ --icon-width: 14px; - --line-height: $irc-line-height; + --line-height: var(--irc-line-height); --right-padding: 5px; /* TODO: Use a spacing variable */ line-height: var(--line-height) !important; @@ -30,155 +32,7 @@ $irc-line-height: $font-18px; .mx_NewRoomIntro { > h2 { - line-height: initial; /* Cancel $irc-line-height */ - } - } - - .mx_EventTile[data-layout="irc"] { - --EventTile_irc_line-padding-block: 1px; - - display: flex; - align-items: flex-start; - padding-top: 0; - - > a { - text-decoration: none; /* timestamps are links which shouldn't be underlined */ - min-width: $MessageTimestamp_width; /* ensure space for EventTile without timestamp */ - } - - > * { - margin-right: var(--right-padding); - } - - .mx_EventTile_avatar, - .mx_EventTile_e2eIcon { - height: $irc-line-height; - } - - .mx_EventTile_avatar, - .mx_DisambiguatedProfile, - .mx_EventTile_e2eIcon, - .mx_EventTile_msgOption { - flex-shrink: 0; - } - - .mx_EventTile_avatar { - order: 1; - position: relative; - display: flex; - align-items: center; - - /* Need to use important to override the js provided height and width values. */ - > .mx_BaseAvatar, - > .mx_BaseAvatar > * { - height: $font-14px !important; - width: $font-14px !important; - font-size: $font-10px !important; - line-height: $font-15px !important; - } - } - - .mx_DisambiguatedProfile { - order: 2; - width: var(--name-width); - margin-inline-end: 0; /* override mx_EventTile > * */ - - > .mx_DisambiguatedProfile_displayName { - width: 100%; - text-align: end; - overflow: hidden; - text-overflow: ellipsis; - } - - > .mx_DisambiguatedProfile_mxid { - visibility: collapse; - margin-left: 0; /* Override the inherited margin. */ - padding: 0 5px; /* TODO: Use a spacing variable */ - } - - &:hover { - overflow: visible; - z-index: 10; - - > .mx_DisambiguatedProfile_displayName { - overflow: visible; - display: inline; - background-color: $event-selected-color; - border-radius: 8px 0 0 8px; - padding-right: $spacing-8; - } - - > .mx_DisambiguatedProfile_mxid { - visibility: visible; - opacity: 1; - background-color: $event-selected-color; - } - } - } - - .mx_EventTile_e2eIcon { - padding: 0; - flex-grow: 0; - background-position: center; - } - - .mx_EventTile_line { - .mx_EventTile_e2eIcon, - .mx_TextualEvent, - .mx_ViewSourceEvent, - .mx_MTextBody { - /* add a 1px padding top and bottom because our larger - emoji font otherwise gets cropped by anti-zalgo */ - padding: var(--EventTile_irc_line-padding-block) 0; - } - - .mx_EventTile_e2eIcon, - .mx_TextualEvent, - .mx_MTextBody { - display: inline-block; - } - - .mx_ReplyTile { - .mx_MTextBody { - display: -webkit-box; /* Enable -webkit-line-clamp */ - } - } - } - - .mx_EventTile_line, - .mx_EventTile_reply { - order: 3; - display: flex; - flex-direction: column; - flex-grow: 1; - flex-shrink: 1; - min-width: 0; - } - - .mx_EventTile_reply { - order: 4; - } - - .mx_EventTile_msgOption { - order: 5; - } - - .mx_EditMessageComposer_buttons { - position: relative; - } - - &.mx_EventTile_info { - .mx_ViewSourceEvent, /* For hidden events */ - .mx_TextualEvent { - line-height: $irc-line-height; - } - } - } - - .mx_EventTile_emote { - .mx_EventTile_avatar { - /* add --right-padding value of MessageTimestamp only */ - margin-left: calc(var(--name-width) + var(--icon-width) + 1 * var(--right-padding)); + line-height: initial; /* Cancel var(--irc-line-height) */ } } diff --git a/res/css/views/rooms/_RoomList.pcss b/res/css/views/rooms/_RoomList.pcss index 93a8fc09946..32d03253d4d 100644 --- a/res/css/views/rooms/_RoomList.pcss +++ b/res/css/views/rooms/_RoomList.pcss @@ -42,49 +42,3 @@ limitations under the License. .mx_RoomList_iconInvite::before { mask-image: url("$(res)/img/element-icons/room/share.svg"); } - -.mx_RoomList_explorePrompt { - margin: 4px 12px 4px; - padding-top: 12px; - border-top: 1px solid $input-border-color; - font-size: $font-14px; - - div:first-child { - font-weight: var(--font-semi-bold); - line-height: $font-18px; - color: $primary-content; - } - - .mx_AccessibleButton { - color: $primary-content; - position: relative; - padding: 8px 8px 8px 32px; - font-size: inherit; - margin-top: 12px; - display: block; - text-align: start; - background-color: $panel-actions; - border-radius: 4px; - - &::before { - content: ""; - width: 16px; - height: 16px; - position: absolute; - top: 8px; - left: 8px; - background: $secondary-content; - mask-position: center; - mask-size: contain; - mask-repeat: no-repeat; - } - - &.mx_RoomList_explorePrompt_startChat::before { - mask-image: url("$(res)/img/element-icons/feedback.svg"); - } - - &.mx_RoomList_explorePrompt_explore::before { - mask-image: url("$(res)/img/element-icons/roomlist/explore.svg"); - } - } -} diff --git a/res/css/views/rooms/_RoomPreviewBar.pcss b/res/css/views/rooms/_RoomPreviewBar.pcss index 8d35cab7173..e57ea372134 100644 --- a/res/css/views/rooms/_RoomPreviewBar.pcss +++ b/res/css/views/rooms/_RoomPreviewBar.pcss @@ -100,8 +100,8 @@ limitations under the License. /* With maximised widgets, the panel fits in better when rounded */ .mx_MainSplit_maximisedWidget .mx_RoomPreviewBar_panel { - margin: $container-gap-width; - margin-right: calc($container-gap-width / 2); /* Shared with right panel */ + margin: var(--container-gap-width); + margin-right: calc(var(--container-gap-width) / 2); /* Shared with right panel */ margin-top: 0; /* Already covered by apps drawer */ border-radius: 8px; } diff --git a/res/css/views/rooms/_Stickers.pcss b/res/css/views/rooms/_Stickers.pcss index 2aafca71bcc..75d947f4994 100644 --- a/res/css/views/rooms/_Stickers.pcss +++ b/res/css/views/rooms/_Stickers.pcss @@ -44,7 +44,3 @@ cursor: pointer; color: $accent; } - -.mx_Stickers_hideStickers { - z-index: 2001; -} diff --git a/res/css/views/settings/_EmailAddresses.pcss b/res/css/views/settings/_EmailAddresses.pcss deleted file mode 100644 index f401c64484d..00000000000 --- a/res/css/views/settings/_EmailAddresses.pcss +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright 2019 New Vector Ltd -Copyright 2019 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. -*/ - -.mx_ExistingEmailAddress { - display: flex; - align-items: center; - margin-bottom: 5px; -} - -.mx_ExistingEmailAddress_email, -.mx_ExistingEmailAddress_promptText { - flex: 1; - margin-right: 10px; -} - -.mx_ExistingEmailAddress_confirmBtn { - margin-left: 5px; -} diff --git a/res/css/views/settings/_PhoneNumbers.pcss b/res/css/views/settings/_PhoneNumbers.pcss index d64d1a63338..c0ad9d7a2f0 100644 --- a/res/css/views/settings/_PhoneNumbers.pcss +++ b/res/css/views/settings/_PhoneNumbers.pcss @@ -15,23 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_ExistingPhoneNumber { - display: flex; - align-items: center; - margin-bottom: 5px; -} - -.mx_ExistingPhoneNumber_address, -.mx_ExistingPhoneNumber_promptText { - flex: 1; - margin-right: 10px; -} - -.mx_ExistingPhoneNumber_confirmBtn { - margin-left: 5px; -} - -.mx_ExistingPhoneNumber_verification { +.mx_GeneralUserSettingsTab_discovery_existing_verification { display: inline-flex; align-items: center; diff --git a/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.pcss b/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.pcss index d48314b4384..89015df3b66 100644 --- a/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.pcss +++ b/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.pcss @@ -37,12 +37,27 @@ limitations under the License. .mx_GeneralUserSettingsTab_accountSection .mx_EmailAddresses, .mx_GeneralUserSettingsTab_accountSection .mx_PhoneNumbers, -.mx_GeneralUserSettingsTab_discovery .mx_ExistingEmailAddress, -.mx_GeneralUserSettingsTab_discovery .mx_ExistingPhoneNumber, +.mx_GeneralUserSettingsTab_discovery .mx_GeneralUserSettingsTab_discovery_existing, .mx_GeneralUserSettingsTab_languageInput { margin-inline-end: var(--SettingsTab_fullWidthField-margin-inline-end); } +.mx_GeneralUserSettingsTab_discovery_existing { + display: flex; + align-items: center; + margin-bottom: 5px; +} + +.mx_GeneralUserSettingsTab_discovery_existing_address, +.mx_GeneralUserSettingsTab_discovery_existing_promptText { + flex: 1; + margin-right: 10px; +} + +.mx_GeneralUserSettingsTab_discovery_existing_button { + margin-left: 5px; +} + .mx_GeneralUserSettingsTab_warningIcon { vertical-align: middle; } diff --git a/res/css/views/voip/_CallView.pcss b/res/css/views/voip/_CallView.pcss index 72c5dc1839a..8241ff92270 100644 --- a/res/css/views/voip/_CallView.pcss +++ b/res/css/views/voip/_CallView.pcss @@ -20,8 +20,8 @@ limitations under the License. display: flex; flex-direction: column; - margin: $container-gap-width; - margin-right: calc($container-gap-width / 2); + margin: var(--container-gap-width); + margin-right: calc(var(--container-gap-width) / 2); background-color: $header-panel-bg-color; padding: 8px; diff --git a/res/css/views/voip/_LegacyCallView.pcss b/res/css/views/voip/_LegacyCallView.pcss index ec221c4c6fb..730a02b6356 100644 --- a/res/css/views/voip/_LegacyCallView.pcss +++ b/res/css/views/voip/_LegacyCallView.pcss @@ -187,9 +187,9 @@ limitations under the License. padding-bottom: 10px; - margin: $container-gap-width; + margin: var(--container-gap-width); /* The left side gap is fully handled by this margin. To prohibit bleeding on webkit browser. */ - margin-right: calc($container-gap-width / 2); + margin-right: calc(var(--container-gap-width) / 2); margin-bottom: 10px; } diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index fb3cb8e2913..0acfef92c1a 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -265,7 +265,6 @@ class LoggedInView extends React.Component { resizer.setClassNames({ handle: "mx_ResizeHandle", vertical: "mx_ResizeHandle_vertical", - reverse: "mx_ResizeHandle_reverse", }); return resizer; } diff --git a/src/components/views/elements/AppTile.tsx b/src/components/views/elements/AppTile.tsx index bf1ccb35d19..ddb14ebff1e 100644 --- a/src/components/views/elements/AppTile.tsx +++ b/src/components/views/elements/AppTile.tsx @@ -693,9 +693,9 @@ export default class AppTile extends React.Component { this.props.room && WidgetLayoutStore.instance.isInContainer(this.props.room, this.props.app, Container.Center); const maximisedClasses = classNames({ - mx_AppTileMenuBar_iconButton: true, - mx_AppTileMenuBar_iconButton_collapse: isMaximised, - mx_AppTileMenuBar_iconButton_maximise: !isMaximised, + "mx_AppTileMenuBar_iconButton": true, + "mx_AppTileMenuBar_iconButton--collapse": isMaximised, + "mx_AppTileMenuBar_iconButton--maximise": !isMaximised, }); layoutButtons.push( { layoutButtons.push( , @@ -731,13 +731,13 @@ export default class AppTile extends React.Component { {layoutButtons} {this.props.showPopout && !this.state.requiresClient && ( )} ; } -const ResizeHandle: React.FC = ({ vertical, reverse, id, passRef }) => { +const ResizeHandle: React.FC = ({ vertical, id, passRef }) => { const classNames = ["mx_ResizeHandle"]; if (vertical) { classNames.push("mx_ResizeHandle_vertical"); } else { classNames.push("mx_ResizeHandle_horizontal"); } - if (reverse) { - classNames.push("mx_ResizeHandle_reverse"); - } return (
diff --git a/src/components/views/rooms/AppsDrawer.tsx b/src/components/views/rooms/AppsDrawer.tsx index cd5e9bc889e..bf4e0fb0ac5 100644 --- a/src/components/views/rooms/AppsDrawer.tsx +++ b/src/components/views/rooms/AppsDrawer.tsx @@ -110,7 +110,6 @@ export default class AppsDrawer extends React.Component { const classNames = { handle: "mx_ResizeHandle", vertical: "mx_ResizeHandle_vertical", - reverse: "mx_ResizeHandle_reverse", }; const collapseConfig = { onResizeStart: () => { @@ -267,7 +266,7 @@ export default class AppsDrawer extends React.Component { if (i < 1) return app; return ( - apps.length / 2} /> + {app} ); diff --git a/src/components/views/settings/account/EmailAddresses.tsx b/src/components/views/settings/account/EmailAddresses.tsx index c90e1c2768c..08e014c65b8 100644 --- a/src/components/views/settings/account/EmailAddresses.tsx +++ b/src/components/views/settings/account/EmailAddresses.tsx @@ -94,21 +94,21 @@ export class ExistingEmailAddress extends React.Component - +
+ {_t("Remove %(email)s?", { email: this.props.email.address })} {_t("Remove")} {_t("Cancel")} @@ -117,8 +117,8 @@ export class ExistingEmailAddress extends React.Component - {this.props.email.address} +
+ {this.props.email.address} {_t("Remove")} diff --git a/src/components/views/settings/account/PhoneNumbers.tsx b/src/components/views/settings/account/PhoneNumbers.tsx index 113baa4639f..ede650642de 100644 --- a/src/components/views/settings/account/PhoneNumbers.tsx +++ b/src/components/views/settings/account/PhoneNumbers.tsx @@ -89,21 +89,21 @@ export class ExistingPhoneNumber extends React.Component - +
+ {_t("Remove %(phone)s?", { phone: this.props.msisdn.address })} {_t("Remove")} {_t("Cancel")} @@ -112,8 +112,10 @@ export class ExistingPhoneNumber extends React.Component - +{this.props.msisdn.address} +
+ + +{this.props.msisdn.address} + {_t("Remove")} diff --git a/src/components/views/settings/discovery/EmailAddresses.tsx b/src/components/views/settings/discovery/EmailAddresses.tsx index 7c524266a4e..f11222af481 100644 --- a/src/components/views/settings/discovery/EmailAddresses.tsx +++ b/src/components/views/settings/discovery/EmailAddresses.tsx @@ -217,7 +217,7 @@ export class EmailAddress extends React.Component {_t("Verify the link in your inbox")} @@ -239,7 +239,7 @@ export class EmailAddress extends React.Component @@ -249,8 +249,8 @@ export class EmailAddress extends React.Component - {address} +
+ {address} {status}
); diff --git a/src/components/views/settings/discovery/PhoneNumbers.tsx b/src/components/views/settings/discovery/PhoneNumbers.tsx index 16f53385aa5..9ae1719259b 100644 --- a/src/components/views/settings/discovery/PhoneNumbers.tsx +++ b/src/components/views/settings/discovery/PhoneNumbers.tsx @@ -222,7 +222,7 @@ export class PhoneNumber extends React.Component + {_t("Please enter verification code sent via text.")}
@@ -243,7 +243,7 @@ export class PhoneNumber extends React.Component @@ -253,7 +253,7 @@ export class PhoneNumber extends React.Component @@ -263,8 +263,8 @@ export class PhoneNumber extends React.Component - +{address} +
+ +{address} {status}
); diff --git a/src/stores/SetupEncryptionStore.ts b/src/stores/SetupEncryptionStore.ts index 46c0193f94f..353d361795e 100644 --- a/src/stores/SetupEncryptionStore.ts +++ b/src/stores/SetupEncryptionStore.ts @@ -31,6 +31,7 @@ import Modal from "../Modal"; import InteractiveAuthDialog from "../components/views/dialogs/InteractiveAuthDialog"; import { _t } from "../languageHandler"; import { SdkContextClass } from "../contexts/SDKContext"; +import { asyncSome } from "../utils/arrays"; export enum Phase { Loading = 0, @@ -108,20 +109,12 @@ export class SetupEncryptionStore extends EventEmitter { // do we have any other verified devices which are E2EE which we can verify against? const dehydratedDevice = await cli.getDehydratedDevice(); const ownUserId = cli.getUserId()!; - const crossSigningInfo = cli.getStoredCrossSigningForUser(ownUserId); - this.hasDevicesToVerifyAgainst = cli.getStoredDevicesForUser(ownUserId).some((device) => { + this.hasDevicesToVerifyAgainst = await asyncSome(cli.getStoredDevicesForUser(ownUserId), async (device) => { if (!device.getIdentityKey() || (dehydratedDevice && device.deviceId == dehydratedDevice?.device_id)) { return false; } - // check if the device is signed by the cross-signing key stored for our user. Note that this is - // *different* to calling `cryptoApi.getDeviceVerificationStatus`, because even if we have stored - // a cross-signing key for our user, we don't necessarily trust it yet (In legacy Crypto, we have not - // yet imported it into `Crypto.crossSigningInfo`, which for maximal confusion is a different object to - // `Crypto.getStoredCrossSigningForUser(ownUserId)`). - // - // TODO: figure out wtf to to here for rust-crypto - const verificationStatus = crossSigningInfo?.checkDeviceTrust(crossSigningInfo, device, false, true); - return !!verificationStatus?.isCrossSigningVerified(); + const verificationStatus = await cli.getCrypto()?.getDeviceVerificationStatus(ownUserId, device.deviceId); + return !!verificationStatus?.signedByOwner; }); this.phase = Phase.Intro;