From ba71fb169f952e1bf0077fc13aa314c3a9ec2486 Mon Sep 17 00:00:00 2001 From: Robin Date: Mon, 4 Apr 2022 10:25:33 -0400 Subject: [PATCH 1/5] Scale emoji with size of surrounding text (#8224) * Scale emoji with size of surrounding text * Fix lint --- res/css/views/elements/_RichText.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/res/css/views/elements/_RichText.scss b/res/css/views/elements/_RichText.scss index 221fc54a055..4615f5da23f 100644 --- a/res/css/views/elements/_RichText.scss +++ b/res/css/views/elements/_RichText.scss @@ -76,7 +76,9 @@ a.mx_Pill { } .mx_Emoji { - font-size: 1.8rem; + // Should be 1.8rem for our default 1.4rem message bodies, + // and scale with the size of the surrounding text + font-size: calc(18 / 14 * 1em); vertical-align: bottom; } From 371ccd78580f6ecff3d74b526a602c84af2f4f0c Mon Sep 17 00:00:00 2001 From: Robin Date: Mon, 4 Apr 2022 10:29:40 -0400 Subject: [PATCH 2/5] Don't use m.call for Jitsi video rooms (#8223) --- src/components/structures/RoomView.tsx | 2 +- src/components/structures/SpaceRoomView.tsx | 2 +- src/components/views/context_menus/RoomContextMenu.tsx | 2 +- src/components/views/dialogs/CreateRoomDialog.tsx | 2 +- src/components/views/right_panel/RoomSummaryCard.tsx | 2 +- src/components/views/rooms/RoomList.tsx | 4 ++-- src/components/views/rooms/RoomListHeader.tsx | 4 ++-- src/components/views/rooms/RoomTile.tsx | 2 +- src/createRoom.ts | 4 ++-- test/components/views/rooms/RoomTile-test.tsx | 2 +- test/test-utils/test-utils.ts | 2 +- 11 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index ab06369406e..ca529b8adf8 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -375,7 +375,7 @@ export class RoomView extends React.Component { }; private getMainSplitContentType = (room: Room) => { - if (SettingsStore.getValue("feature_video_rooms") && room.isCallRoom()) { + if (SettingsStore.getValue("feature_video_rooms") && room.isElementVideoRoom()) { return MainSplitContentType.Video; } if (WidgetLayoutStore.instance.hasMaximisedWidget(room)) { diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index f6f8ae3f472..aaf3e4e1358 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -347,7 +347,7 @@ const SpaceLandingAddButton = ({ space }) => { e.stopPropagation(); closeMenu(); - if (await showCreateNewRoom(space, RoomType.UnstableCall)) { + if (await showCreateNewRoom(space, RoomType.ElementVideo)) { defaultDispatcher.fire(Action.UpdateSpaceHierarchy); } }} diff --git a/src/components/views/context_menus/RoomContextMenu.tsx b/src/components/views/context_menus/RoomContextMenu.tsx index 35ecf97a9ca..8a0f5a04c18 100644 --- a/src/components/views/context_menus/RoomContextMenu.tsx +++ b/src/components/views/context_menus/RoomContextMenu.tsx @@ -105,7 +105,7 @@ const RoomContextMenu = ({ room, onFinished, ...props }: IProps) => { } const isDm = DMRoomMap.shared().getUserIdForRoomId(room.roomId); - const isVideoRoom = useFeatureEnabled("feature_video_rooms") && room.isCallRoom(); + const isVideoRoom = useFeatureEnabled("feature_video_rooms") && room.isElementVideoRoom(); let inviteOption: JSX.Element; if (room.canInvite(cli.getUserId()) && !isDm) { diff --git a/src/components/views/dialogs/CreateRoomDialog.tsx b/src/components/views/dialogs/CreateRoomDialog.tsx index feec3443137..ffb28719a7e 100644 --- a/src/components/views/dialogs/CreateRoomDialog.tsx +++ b/src/components/views/dialogs/CreateRoomDialog.tsx @@ -221,7 +221,7 @@ export default class CreateRoomDialog extends React.Component { }); render() { - const isVideoRoom = this.props.type === RoomType.UnstableCall; + const isVideoRoom = this.props.type === RoomType.ElementVideo; let aliasField; if (this.state.joinRule === JoinRule.Public) { diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 65654f37ea2..018d2c6927f 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -269,7 +269,7 @@ const RoomSummaryCard: React.FC = ({ room, onClose }) => { const isRoomEncrypted = useIsEncrypted(cli, room); const roomContext = useContext(RoomContext); const e2eStatus = roomContext.e2eStatus; - const isVideoRoom = useFeatureEnabled("feature_video_rooms") && room.isCallRoom(); + const isVideoRoom = useFeatureEnabled("feature_video_rooms") && room.isElementVideoRoom(); const alias = room.getCanonicalAlias() || room.getAltAliases()[0] || ""; const header = diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index b7e1f87dc5b..69b5179af51 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -243,7 +243,7 @@ const UntaggedAuxButton = ({ tabIndex }: IAuxButtonProps) => { e.preventDefault(); e.stopPropagation(); closeMenu(); - showCreateNewRoom(activeSpace, RoomType.UnstableCall); + showCreateNewRoom(activeSpace, RoomType.ElementVideo); }} disabled={!canAddRooms} tooltip={canAddRooms ? undefined @@ -289,7 +289,7 @@ const UntaggedAuxButton = ({ tabIndex }: IAuxButtonProps) => { closeMenu(); defaultDispatcher.dispatch({ action: "view_create_room", - type: RoomType.UnstableCall, + type: RoomType.ElementVideo, }); }} /> } diff --git a/src/components/views/rooms/RoomListHeader.tsx b/src/components/views/rooms/RoomListHeader.tsx index cc85c4e6dac..6b97e8a660a 100644 --- a/src/components/views/rooms/RoomListHeader.tsx +++ b/src/components/views/rooms/RoomListHeader.tsx @@ -217,7 +217,7 @@ const RoomListHeader = ({ onVisibilityChange }: IProps) => { onClick={(e) => { e.preventDefault(); e.stopPropagation(); - showCreateNewRoom(activeSpace, RoomType.UnstableCall); + showCreateNewRoom(activeSpace, RoomType.ElementVideo); closePlusMenu(); }} /> } @@ -310,7 +310,7 @@ const RoomListHeader = ({ onVisibilityChange }: IProps) => { e.stopPropagation(); defaultDispatcher.dispatch({ action: "view_create_room", - type: RoomType.UnstableCall, + type: RoomType.ElementVideo, }); closePlusMenu(); }} diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx index a2557506e2a..30585b35ca4 100644 --- a/src/components/views/rooms/RoomTile.tsx +++ b/src/components/views/rooms/RoomTile.tsx @@ -123,7 +123,7 @@ export default class RoomTile extends React.PureComponent { this.notificationState = RoomNotificationStateStore.instance.getRoomState(this.props.room); this.roomProps = EchoChamber.forRoom(this.props.room); - this.isVideoRoom = SettingsStore.getValue("feature_video_rooms") && this.props.room.isCallRoom(); + this.isVideoRoom = SettingsStore.getValue("feature_video_rooms") && this.props.room.isElementVideoRoom(); } private onRoomNameUpdate = (room: Room) => { diff --git a/src/createRoom.ts b/src/createRoom.ts index adfca7dd3a9..f2b09bc0b3b 100644 --- a/src/createRoom.ts +++ b/src/createRoom.ts @@ -129,7 +129,7 @@ export default async function createRoom(opts: IOpts): Promise { }; // In video rooms, allow all users to send video member updates - if (opts.roomType === RoomType.UnstableCall) { + if (opts.roomType === RoomType.ElementVideo) { createOpts.power_level_content_override = { events: { [VIDEO_CHANNEL_MEMBER]: 0, @@ -263,7 +263,7 @@ export default async function createRoom(opts: IOpts): Promise { } }).then(() => { // Set up video rooms with a Jitsi widget - if (opts.roomType === RoomType.UnstableCall) { + if (opts.roomType === RoomType.ElementVideo) { return addVideoChannel(roomId, createOpts.name); } }).then(function() { diff --git a/test/components/views/rooms/RoomTile-test.tsx b/test/components/views/rooms/RoomTile-test.tsx index b0a98c8b3c3..92135c3b099 100644 --- a/test/components/views/rooms/RoomTile-test.tsx +++ b/test/components/views/rooms/RoomTile-test.tsx @@ -73,7 +73,7 @@ describe("RoomTile", () => { describe("video rooms", () => { const room = mkRoom(cli, "!1:example.org"); - room.isCallRoom.mockReturnValue(true); + room.isElementVideoRoom.mockReturnValue(true); it("tracks connection state", () => { const tile = mount( diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts index 45edc7fa15e..ca2737d3e23 100644 --- a/test/test-utils/test-utils.ts +++ b/test/test-utils/test-utils.ts @@ -367,7 +367,7 @@ export function mkStubRoom(roomId: string = null, name: string, client: MatrixCl getAvatarUrl: () => 'mxc://avatar.url/room.png', getMxcAvatarUrl: () => 'mxc://avatar.url/room.png', isSpaceRoom: jest.fn().mockReturnValue(false), - isCallRoom: jest.fn().mockReturnValue(false), + isElementVideoRoom: jest.fn().mockReturnValue(false), getUnreadNotificationCount: jest.fn(() => 0), getEventReadUpTo: jest.fn(() => null), getCanonicalAlias: jest.fn(), From c0c447ab9bb7cfb9319e56cbbc1cb87048ca3322 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 5 Apr 2022 11:10:23 +0100 Subject: [PATCH 3/5] Fix URL previews being enabled when room first created (#8227) We didn't update whether URL previews should be enabled when encryption was enabled in a room, so when you create a room (or enable encryption) it starts off with URL previews using the setting for non-e2e rooms untilyou switch rooms / refresh. --- src/components/structures/RoomView.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index ca529b8adf8..2cf8b42265d 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -944,6 +944,7 @@ export class RoomView extends React.Component { if (ev.getType() === "m.room.encryption") { this.updateE2EStatus(room); + this.updatePreviewUrlVisibility(room); } // ignore anything but real-time updates at the end of the room: From 4f6b9394260bbc4a54c0282598ee527524727e87 Mon Sep 17 00:00:00 2001 From: Robin Date: Tue, 5 Apr 2022 06:30:57 -0400 Subject: [PATCH 4/5] More video rooms design updates (#8222) * Update video room icon * Hide room header border in video rooms * Fix inconsistent padding on AppTile frames --- res/css/structures/_RoomView.scss | 20 ++++++++++++------- res/css/views/rooms/_AppsDrawer.scss | 2 -- res/css/views/rooms/_RoomList.scss | 2 +- res/css/views/rooms/_RoomListHeader.scss | 2 +- res/img/element-icons/roomlist/hash-video.svg | 5 +++++ src/components/structures/RoomView.tsx | 1 + 6 files changed, 21 insertions(+), 11 deletions(-) create mode 100644 res/img/element-icons/roomlist/hash-video.svg diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 3547225ce7f..a008a83aa6b 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -213,14 +213,20 @@ hr.mx_RoomView_myReadMarker { } // Immersive widgets -.mx_RoomView_body > .mx_AppTile { - margin: $container-gap-width; - margin-right: calc($container-gap-width / 2); - width: auto; - height: 100%; - padding-top: 33px; // to match the right panel chat heading +.mx_RoomView_immersive { + .mx_RoomHeader_wrapper { + border: unset; + } - border-radius: 8px; + .mx_AppTile { + margin: $container-gap-width; + margin-right: calc($container-gap-width / 2); + width: auto; + height: 100%; + padding-top: 33px; // to match the right panel chat heading + + border-radius: 8px; + } } .mx_RoomView_callStatusBar .mx_UploadBar_uploadProgressInner { diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index 1b008d76aa0..47aa9a9d2ab 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -148,8 +148,6 @@ $MinWidth: 240px; width: 50%; min-width: $MinWidth; border: $container-border-width solid $widget-menu-bar-bg-color; - border-left-width: 5px; - border-right-width: 5px; display: flex; flex-direction: column; box-sizing: border-box; diff --git a/res/css/views/rooms/_RoomList.scss b/res/css/views/rooms/_RoomList.scss index 2763ad653fc..bbac00e0e60 100644 --- a/res/css/views/rooms/_RoomList.scss +++ b/res/css/views/rooms/_RoomList.scss @@ -25,7 +25,7 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/roomlist/hash-plus.svg'); } .mx_RoomList_iconNewVideoRoom::before { - mask-image: url('$(res)/img/element-icons/call/video-call.svg'); + mask-image: url('$(res)/img/element-icons/roomlist/hash-video.svg'); } .mx_RoomList_iconAddExistingRoom::before { mask-image: url('$(res)/img/element-icons/roomlist/hash.svg'); diff --git a/res/css/views/rooms/_RoomListHeader.scss b/res/css/views/rooms/_RoomListHeader.scss index c4bc8c151f8..7f5d06d5499 100644 --- a/res/css/views/rooms/_RoomListHeader.scss +++ b/res/css/views/rooms/_RoomListHeader.scss @@ -107,7 +107,7 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/roomlist/hash-plus.svg'); } .mx_RoomListHeader_iconNewVideoRoom::before { - mask-image: url('$(res)/img/element-icons/call/video-call.svg'); + mask-image: url('$(res)/img/element-icons/roomlist/hash-video.svg'); } .mx_RoomListHeader_iconExplore::before { mask-image: url('$(res)/img/element-icons/roomlist/hash-search.svg'); diff --git a/res/img/element-icons/roomlist/hash-video.svg b/res/img/element-icons/roomlist/hash-video.svg new file mode 100644 index 00000000000..b0e1decf686 --- /dev/null +++ b/res/img/element-icons/roomlist/hash-video.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 2cf8b42265d..29f64455743 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -2100,6 +2100,7 @@ export class RoomView extends React.Component { const mainClasses = classNames("mx_RoomView", { mx_RoomView_inCall: Boolean(activeCall), + mx_RoomView_immersive: this.state.mainSplitContentType === MainSplitContentType.Video, }); const showChatEffects = SettingsStore.getValue('showChatEffects'); From 27e48062b6d6f4e42900963b5344ca69e16ace70 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 5 Apr 2022 17:01:34 +0100 Subject: [PATCH 5/5] Apply tweaks to Thread list as per design spec (#8149) Co-authored-by: Germain Souquet --- res/css/views/right_panel/_ThreadPanel.scss | 40 ++++++-- res/css/views/rooms/_EventTile.scss | 91 ++++++++++++++----- src/components/structures/ThreadPanel.tsx | 46 +++++++--- .../context_menus/MessageContextMenu.tsx | 8 +- src/components/views/messages/TextualBody.tsx | 15 +-- src/components/views/rooms/EventTile.tsx | 41 ++++++--- src/i18n/strings/en_EN.json | 4 +- 7 files changed, 173 insertions(+), 72 deletions(-) diff --git a/res/css/views/right_panel/_ThreadPanel.scss b/res/css/views/right_panel/_ThreadPanel.scss index cb438c47068..280404a6497 100644 --- a/res/css/views/right_panel/_ThreadPanel.scss +++ b/res/css/views/right_panel/_ThreadPanel.scss @@ -20,14 +20,17 @@ limitations under the License. .mx_BaseCard_header { margin-bottom: 12px; + .mx_BaseCard_close, .mx_BaseCard_back { width: 24px; height: 24px; } + .mx_BaseCard_back { left: -4px; } + .mx_BaseCard_close { right: -4px; } @@ -66,6 +69,7 @@ limitations under the License. --size: 24px; width: var(--size); height: var(--size); + &::after { mask-size: var(--size); mask-image: url("$(res)/img/element-icons/message/overflow-large.svg"); @@ -99,11 +103,10 @@ limitations under the License. } .mx_AutoHideScrollbar { - background: #fff; background-color: $background; border-radius: 8px; - width: calc(100% - 16px); - padding-right: 16px; + width: calc(100% - 24px); + padding-right: 18px; } &.mx_ThreadView .mx_ThreadView_timelinePanelWrapper { @@ -125,13 +128,15 @@ limitations under the License. padding-right: 0; } - .mx_EventTile, .mx_GenericEventListSummary { + .mx_EventTile, + .mx_GenericEventListSummary { // Account for scrollbar when hovering padding-top: 0; .mx_ThreadInfo { position: relative; padding-right: 11px; + &::after { content: ''; display: block; @@ -157,6 +162,10 @@ limitations under the License. .mx_EventTile_e2eIcon { left: 8px; } + + &:hover .mx_EventTile_line { + box-shadow: unset !important; // don't show the verification left stroke in the thread list + } } .mx_MessageComposer { @@ -190,10 +199,6 @@ limitations under the License. float: right; } - .mx_ThreadPanel_dropdown[aria-expanded=true]::before { - transform: rotate(180deg); - } - .mx_MessageTimestamp { font-size: $font-12px; color: $secondary-content; @@ -272,19 +277,23 @@ limitations under the License. h2 { color: $primary-content; - font-weight: 600; + font-weight: $font-semi-bold; font-size: $font-18px; + margin-top: 24px; + margin-bottom: 10px; } p { font-size: $font-15px; color: $secondary-content; + margin: 10px 0; } button { border: none; background: none; color: $accent; + font-size: $font-15px; &:hover, &:active { @@ -292,6 +301,15 @@ limitations under the License. cursor: pointer; } } + + .mx_ThreadPanel_empty_tip { + font-size: $font-12px; + line-height: $font-15px; + + >b { + font-weight: $font-semi-bold; + } + } } .mx_ThreadPanel_largeIcon { @@ -317,6 +335,7 @@ limitations under the License. .mx_ContextualMenu_wrapper.mx_ThreadPanel__header { .mx_ContextualMenu { position: initial; + span:first-of-type { font-weight: $font-semi-bold; font-size: inherit; @@ -336,6 +355,7 @@ limitations under the License. left: auto; right: 22px; border-bottom-color: $quinary-content; + &::after { content: ""; border: inherit; @@ -357,10 +377,12 @@ limitations under the License. &:hover { background-color: $event-selected-color; } + &[aria-checked="true"] { :first-child { margin-left: -20px; } + :first-child::before { content: ""; width: 12px; diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 988b79b6ef1..ccd6b88d024 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -43,9 +43,11 @@ $left-gutter: 64px; right: 0; } } + .mx_EventTile_receiptSent::before { mask-image: url('$(res)/img/element-icons/circle-sent.svg'); } + .mx_EventTile_receiptSending::before { mask-image: url('$(res)/img/element-icons/circle-sending.svg'); } @@ -61,11 +63,11 @@ $left-gutter: 64px; &[data-shape=ThreadsList][data-notification]::before { content: ""; position: absolute; - width: 8px; - height: 8px; + width: 10px; + height: 10px; border-radius: 50%; - right: -16px; - top: 6px; + right: -25px; // center it in the gutter (16px margin + 4px padding + half 10px width) + top: 4px; left: auto; } @@ -79,7 +81,6 @@ $left-gutter: 64px; .mx_ThreadInfo, .mx_ThreadSummaryIcon { - margin-right: 110px; margin-left: 64px; } @@ -115,7 +116,8 @@ $left-gutter: 64px; .mx_DisambiguatedProfile { color: $primary-content; font-size: $font-14px; - display: inline-block; /* anti-zalgo, with overflow hidden */ + display: inline-block; + /* anti-zalgo, with overflow hidden */ overflow: hidden; padding-bottom: 0px; padding-top: 0px; @@ -142,7 +144,8 @@ $left-gutter: 64px; clear: both; } - .mx_EventTile_line, .mx_EventTile_reply { + .mx_EventTile_line, + .mx_EventTile_reply { position: relative; padding-left: $left-gutter; border-radius: 8px; @@ -308,11 +311,19 @@ $left-gutter: 64px; .mx_RoomView_timeline_rr_enabled { .mx_EventTile[data-layout=group] { + + .mx_ThreadInfo, + .mx_ThreadSummaryIcon, .mx_EventTile_line { /* ideally should be 100px, but 95px gives us a max thumbnail size of 800x600, which is nice */ margin-right: 110px; } + + .mx_ThreadInfo { + max-width: min(calc(100% - $left-gutter - 110px), 600px); // leave space on both left & right gutters + } } + // on ELS we need the margin to allow interaction with the expand/collapse button which is normally in the RR gutter } @@ -408,7 +419,8 @@ $left-gutter: 64px; background-repeat: no-repeat; background-size: contain; - &::before, &::after { + &::before, + &::after { content: ""; display: block; position: absolute; @@ -433,6 +445,7 @@ $left-gutter: 64px; mask-image: url('$(res)/img/e2e/warning.svg'); background-color: $alert; } + opacity: 1; } @@ -441,6 +454,7 @@ $left-gutter: 64px; mask-image: url('$(res)/img/e2e/normal.svg'); background-color: $header-panel-text-primary-color; } + opacity: 1; } @@ -479,7 +493,8 @@ $left-gutter: 64px; color: inherit; // inherit the colour from the dark or light theme by default (but not for code blocks) font-size: $font-14px; - pre, code { + pre, + code { font-family: $monospace-font-family !important; background-color: $codeblock-background-color; } @@ -492,7 +507,7 @@ $left-gutter: 64px; pre code { white-space: pre; // we want code blocks to be scrollable and not wrap - > * { + >* { display: inline; } } @@ -514,6 +529,7 @@ $left-gutter: 64px; float: left; margin: 0 0.5em 0 -1.5em; color: gray; + & span { text-align: right; display: block; @@ -547,18 +563,22 @@ $left-gutter: 64px; height: 19px; background-color: $message-action-bar-fg-color; } + .mx_EventTile_buttonBottom { top: 33px; } + .mx_EventTile_copyButton { mask-image: url($copy-button-url); } + .mx_EventTile_collapseButton { mask-size: 75%; mask-position: center; mask-repeat: no-repeat; mask-image: url("$(res)/img/element-icons/minimise-collapse.svg"); } + .mx_EventTile_expandButton { mask-size: 75%; mask-position: center; @@ -674,10 +694,13 @@ $left-gutter: 64px; } @media only screen and (max-width: 480px) { - .mx_EventTile_line, .mx_EventTile_reply { + + .mx_EventTile_line, + .mx_EventTile_reply { padding-left: 0; margin-right: 0; } + .mx_EventTile_content { margin-top: 10px; margin-right: 0; @@ -692,23 +715,28 @@ $left-gutter: 64px; mask-position: center; height: 18px; min-width: 18px; - background-color: $secondary-content; + background-color: $secondary-content !important; mask-repeat: no-repeat; mask-size: contain; } .mx_ThreadSummaryIcon { + display: inline-block; font-size: $font-12px; - color: $secondary-content; + color: $secondary-content !important; + margin-top: 8px; + margin-bottom: 8px; + &::before { vertical-align: middle; - margin-left: 8px; + margin-right: 8px; + margin-top: -2px; } } .mx_ThreadInfo { min-width: 267px; - max-width: min(calc(100% - $left-gutter - 64px), 600px); // leave space on both left & right gutters + max-width: min(calc(100% - $left-gutter), 600px); // leave space on both left & right gutters width: fit-content; height: 40px; position: relative; @@ -756,7 +784,8 @@ $left-gutter: 64px; } } - &:hover, &:focus { + &:hover, + &:focus { cursor: pointer; border-color: $quinary-content; @@ -782,6 +811,9 @@ $threadInfoLineHeight: calc(2 * $font-12px); .mx_ThreadInfo_sender { font-weight: $font-semi-bold; line-height: $threadInfoLineHeight; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; } .mx_ThreadInfo_content { @@ -792,6 +824,7 @@ $threadInfoLineHeight: calc(2 * $font-12px); font-size: $font-12px; line-height: $threadInfoLineHeight; color: $secondary-content; + flex: 1; } .mx_ThreadInfo_avatar { @@ -810,9 +843,10 @@ $threadInfoLineHeight: calc(2 * $font-12px); .mx_EventTile[data-shape=ThreadsList] { --topOffset: 20px; --leftOffset: 46px; + $borderRadius: 8px; margin: var(--topOffset) 16px var(--topOffset) 0; - border-radius: 8px; + border-radius: $borderRadius; display: flex; flex-flow: wrap; @@ -847,6 +881,7 @@ $threadInfoLineHeight: calc(2 * $font-12px); &::after { content: unset; } + margin-bottom: 0; } @@ -857,7 +892,7 @@ $threadInfoLineHeight: calc(2 * $font-12px); padding-top: 0; .mx_EventTile_avatar { - top: -4px; + top: 0; left: 0; } @@ -892,7 +927,7 @@ $threadInfoLineHeight: calc(2 * $font-12px); width: 100%; box-sizing: border-box; padding-left: var(--leftOffset) !important; - padding-bottom: 0; + border-radius: $borderRadius !important; // override 4px } .mx_MessageTimestamp { @@ -918,7 +953,7 @@ $threadInfoLineHeight: calc(2 * $font-12px); .mx_EventTile { display: flex; flex-direction: column; - padding-top: 0; + padding-top: 14px; // due to layout differences, this odd number matches the 18px padding-top of main tl events .mx_EventTile_line { padding-left: 0; @@ -973,7 +1008,7 @@ $threadInfoLineHeight: calc(2 * $font-12px); .mx_UnknownBody, .mx_MPollBody, .mx_ReplyChain_wrapper { - margin-left: 36px; + margin-left: 48px; margin-right: 8px; .mx_EventTile_content, @@ -997,16 +1032,17 @@ $threadInfoLineHeight: calc(2 * $font-12px); .mx_EventTile_senderDetails { display: flex; align-items: center; - gap: calc(6px + $selected-message-border-width); + gap: calc(14px + $selected-message-border-width); a { flex: 1; - min-width: none; + min-width: unset; max-width: 100%; display: flex; align-items: center; .mx_DisambiguatedProfile { + margin-left: 8px; flex: 1; } } @@ -1026,4 +1062,13 @@ $threadInfoLineHeight: calc(2 * $font-12px); .mx_MessageComposer_sendMessage { margin-right: 0; } + + .mx_EditMessageComposer { + margin-left: 30px !important; // align start of first letter with that of the event body + } + + .mx_EditMessageComposer_buttons { + padding-right: 11px; // align with right edge of input + margin-right: 0; // align with right edge of background + } } diff --git a/src/components/structures/ThreadPanel.tsx b/src/components/structures/ThreadPanel.tsx index 3364fdc27c2..b9f227780ea 100644 --- a/src/components/structures/ThreadPanel.tsx +++ b/src/components/structures/ThreadPanel.tsx @@ -101,7 +101,7 @@ export const ThreadPanelHeader = ({ filterOption, setFilterOption, empty }: { isSelected={opt === value} />); const contextMenu = menuDisplayed ? void; } -const EmptyThread: React.FC = ({ filterOption, showAllThreadsCallback }) => { +const EmptyThread: React.FC = ({ hasThreads, filterOption, showAllThreadsCallback }) => { + let body: JSX.Element; + if (hasThreads) { + body = <> +

+ { _t("Reply to an ongoing thread or use “%(replyInThread)s” " + + "when hovering over a message to start a new one.", { + replyInThread: _t("Reply in thread"), + }) } +

+

+ { /* Always display that paragraph to prevent layout shift when hiding the button */ } + { (filterOption === ThreadFilterType.My) + ? + : <>  + } +

+ ; + } else { + body = <> +

{ _t("Threads help keep your conversations on-topic and easy to track.") }

+

+ { _t('Tip: Use "Reply in thread" when hovering over a message.', {}, { + b: sub => { sub }, + }) } +

+ ; + } + return ; }; @@ -247,6 +266,7 @@ const ThreadPanel: React.FC = ({ timelineSet={timelineSet} showUrlPreview={false} // No URL previews at the threads list level empty={ 0} filterOption={filterOption} showAllThreadsCallback={() => setFilterOption(ThreadFilterType.All)} />} diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 8899c13a606..e0b1b7c9a8d 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -44,7 +44,6 @@ import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; import { ChevronFace, IPosition } from '../../structures/ContextMenu'; import RoomContext, { TimelineRenderingType } from '../../../contexts/RoomContext'; import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload"; -import { WidgetLayoutStore } from '../../../stores/widgets/WidgetLayoutStore'; import EndPollDialog from '../dialogs/EndPollDialog'; import { isPollEnded } from '../messages/MPollBody'; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; @@ -472,14 +471,11 @@ export default class MessageContextMenu extends React.Component timelineRenderingType === TimelineRenderingType.Thread || timelineRenderingType === TimelineRenderingType.ThreadsList ); - const isThreadRootEvent = isThread && this.props.mxEvent?.getThread()?.rootEvent === this.props.mxEvent; + const isThreadRootEvent = isThread && this.props.mxEvent.isThreadRoot; - const isMainSplitTimelineShown = !WidgetLayoutStore.instance.hasMaximisedWidget( - MatrixClientPeg.get().getRoom(mxEvent.getRoomId()), - ); const commonItemsList = ( - { (isThreadRootEvent && isMainSplitTimelineShown) && { if (this.props.highlightLink) { body = { body }; } else if (content.data && typeof content.data["org.matrix.neb.starter_link"] === "string") { - body = { body }; + body = ( + + { body } + + ); } let widgets; @@ -651,9 +656,7 @@ export default class TextualBody extends React.Component { ); } return ( -
+
{ body } { widgets }
diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index b73ec31e293..e811a375084 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -631,12 +631,22 @@ export class UnwrappedEventTile extends React.Component { } private renderThreadInfo(): React.ReactNode { + if (this.state.thread?.id === this.props.mxEvent.getId()) { + return ; + } + if (this.context.timelineRenderingType === TimelineRenderingType.Search && this.props.mxEvent.threadRootId) { + if (this.props.highlightLink) { + return ( + + { _t("From a thread") } + + ); + } + return (

{ _t("From a thread") }

); - } else if (this.state.thread?.id === this.props.mxEvent.getId()) { - return ; } } @@ -1100,6 +1110,7 @@ export class UnwrappedEventTile extends React.Component { let isContinuation = this.props.continuation; if (this.context.timelineRenderingType !== TimelineRenderingType.Room && this.context.timelineRenderingType !== TimelineRenderingType.Search && + this.context.timelineRenderingType !== TimelineRenderingType.Thread && this.props.layout !== Layout.Bubble ) { isContinuation = false; @@ -1146,16 +1157,17 @@ export class UnwrappedEventTile extends React.Component { ? undefined : this.props.mxEvent.getId(); - let avatar; - let sender; - let avatarSize; - let needsSenderProfile; + let avatar: JSX.Element; + let sender: JSX.Element; + let avatarSize: number; + let needsSenderProfile: boolean; - if (this.context.timelineRenderingType === TimelineRenderingType.Notification || - this.context.timelineRenderingType === TimelineRenderingType.ThreadsList - ) { + if (this.context.timelineRenderingType === TimelineRenderingType.Notification) { avatarSize = 24; needsSenderProfile = true; + } else if (this.context.timelineRenderingType === TimelineRenderingType.ThreadsList) { + avatarSize = 36; + needsSenderProfile = true; } else if (tileHandler === 'messages.RoomCreate' || isBubbleMessage) { avatarSize = 0; needsSenderProfile = false; @@ -1364,7 +1376,8 @@ export class UnwrappedEventTile extends React.Component {
,
- {
,
{ avatar } - - { sender } - + { sender }
,
{ replyChain } @@ -1417,7 +1428,9 @@ export class UnwrappedEventTile extends React.Component { isSeeingThroughMessageHiddenForModeration={isSeeingThroughMessageHiddenForModeration} /> { actionBar } - { timestamp } + + { timestamp } +
, reactionsRow, ]); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index dea47408e99..795167ac67d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3099,9 +3099,11 @@ "My threads": "My threads", "Shows all threads you've participated in": "Shows all threads you've participated in", "Show:": "Show:", - "Keep discussions organised with threads": "Keep discussions organised with threads", "Reply to an ongoing thread or use “%(replyInThread)s” when hovering over a message to start a new one.": "Reply to an ongoing thread or use “%(replyInThread)s” when hovering over a message to start a new one.", "Show all threads": "Show all threads", + "Threads help keep your conversations on-topic and easy to track.": "Threads help keep your conversations on-topic and easy to track.", + "Tip: Use \"Reply in thread\" when hovering over a message.": "Tip: Use \"Reply in thread\" when hovering over a message.", + "Keep discussions organised with threads": "Keep discussions organised with threads", "Thread": "Thread", "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.", "Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.",