From 8baa46b0dde313b3ed4078e215a86191f6cb3d8c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 27 Apr 2022 18:10:27 +0100 Subject: [PATCH] Fix baseline misalignment of thread panel summary by deduplication (#8413) --- res/css/_components.scss | 1 + res/css/views/right_panel/_ThreadPanel.scss | 28 ++--- res/css/views/right_panel/_TimelineCard.scss | 2 +- res/css/views/rooms/_EventBubbleTile.scss | 4 +- res/css/views/rooms/_EventTile.scss | 112 +---------------- res/css/views/rooms/_IRCLayout.scss | 2 +- res/css/views/rooms/_ThreadSummary.scss | 117 ++++++++++++++++++ src/components/views/rooms/EventTile.tsx | 2 +- src/components/views/rooms/ThreadSummary.tsx | 14 +-- test/end-to-end-tests/src/usecases/threads.ts | 8 +- 10 files changed, 147 insertions(+), 143 deletions(-) create mode 100644 res/css/views/rooms/_ThreadSummary.scss diff --git a/res/css/_components.scss b/res/css/_components.scss index b222e8ce23..8757449604 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -263,6 +263,7 @@ @import "./views/rooms/_SearchBar.scss"; @import "./views/rooms/_SendMessageComposer.scss"; @import "./views/rooms/_Stickers.scss"; +@import "./views/rooms/_ThreadSummary.scss"; @import "./views/rooms/_TopUnreadMessagesBar.scss"; @import "./views/rooms/_VoiceRecordComposerTile.scss"; @import "./views/rooms/_WhoIsTypingTile.scss"; diff --git a/res/css/views/right_panel/_ThreadPanel.scss b/res/css/views/right_panel/_ThreadPanel.scss index 4b4384f1d4..87542cb667 100644 --- a/res/css/views/right_panel/_ThreadPanel.scss +++ b/res/css/views/right_panel/_ThreadPanel.scss @@ -140,7 +140,7 @@ limitations under the License. // Account for scrollbar when hovering padding-top: 0; - .mx_ThreadInfo { + .mx_ThreadSummary { position: relative; padding-right: 11px; @@ -257,28 +257,14 @@ limitations under the License. .mx_ThreadPanel_replies { margin-top: 8px; -} + display: flex; + align-items: center; + position: relative; -.mx_ThreadPanel_repliesSummary { - &::before { - content: ""; - mask-image: url('$(res)/img/element-icons/thread-summary.svg'); - mask-position: center; - display: inline-block; - height: 18px; - min-width: 18px; - background-color: currentColor; - mask-repeat: no-repeat; - mask-size: contain; - margin-right: 8px; - vertical-align: middle; + .mx_ThreadSummary_threads-amount { + color: $secondary-content; + font-size: $font-12px; } - - color: $secondary-content; - font-weight: 600; - float: left; - margin-right: 12px; - font-size: $font-12px; } .mx_ThreadPanel_viewInRoom::before { diff --git a/res/css/views/right_panel/_TimelineCard.scss b/res/css/views/right_panel/_TimelineCard.scss index 1b35d16cdd..029c5f224a 100644 --- a/res/css/views/right_panel/_TimelineCard.scss +++ b/res/css/views/right_panel/_TimelineCard.scss @@ -69,7 +69,7 @@ limitations under the License. margin-right: 8px; } - .mx_ThreadInfo { + .mx_ThreadSummary { margin-left: 36px; margin-right: 0; max-width: min(calc(100% - 36px), 600px); diff --git a/res/css/views/rooms/_EventBubbleTile.scss b/res/css/views/rooms/_EventBubbleTile.scss index 4329d37c0e..0f2ec1dc20 100644 --- a/res/css/views/rooms/_EventBubbleTile.scss +++ b/res/css/views/rooms/_EventBubbleTile.scss @@ -37,7 +37,7 @@ limitations under the License. margin-left: 49px; font-size: $font-14px; - .mx_ThreadInfo { + .mx_ThreadSummary { clear: both; width: fit-content; } @@ -172,7 +172,7 @@ limitations under the License. margin-right: 32px; } - .mx_ThreadInfo { + .mx_ThreadSummary { float: right; margin-right: calc(-1 * var(--gutterSize)); } diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 480ac08654..112e745431 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -77,7 +77,7 @@ $left-gutter: 64px; background-color: $alert; } - .mx_ThreadInfo, + .mx_ThreadSummary, .mx_ThreadSummaryIcon { margin-left: 64px; } @@ -304,7 +304,7 @@ $left-gutter: 64px; .mx_RoomView_timeline_rr_enabled { .mx_EventTile[data-layout=group] { - .mx_ThreadInfo, + .mx_ThreadSummary, .mx_ThreadSummaryIcon, .mx_EventTile_line { /* ideally should be 100px, but 95px gives us a max thumbnail size of 800x600, which is nice */ @@ -312,7 +312,7 @@ $left-gutter: 64px; min-height: $font-14px; } - .mx_ThreadInfo { + .mx_ThreadSummary { max-width: min(calc(100% - $left-gutter - 110px), 600px); // leave space on both left & right gutters } } @@ -680,8 +680,9 @@ $left-gutter: 64px; } } +.mx_ThreadPanel_replies::before, .mx_ThreadSummaryIcon::before, -.mx_ThreadInfo::before { +.mx_ThreadSummary::before { content: ""; display: inline-block; mask-image: url('$(res)/img/element-icons/thread-summary.svg'); @@ -707,113 +708,12 @@ $left-gutter: 64px; } } -.mx_ThreadInfo { - min-width: 267px; - max-width: min(calc(100% - $left-gutter), 600px); // leave space on both left & right gutters - width: fit-content; - height: 40px; - position: relative; - background-color: $system; - padding-left: 12px; - display: flex; - align-items: center; - border-radius: 8px; - padding-right: 16px; - margin-top: 8px; - font-size: $font-12px; - color: $secondary-content; - box-sizing: border-box; - justify-content: flex-start; - clear: both; - overflow: hidden; - border: 1px solid $system; // always render a border so the hover effect doesn't require a re-layout - - .mx_ThreadInfo_chevron { - position: absolute; - top: 0; - right: 0; - bottom: 0; - width: 60px; - box-sizing: border-box; - // XXX: We use `$system-transparent` instead of `transparent` to work around a Safari <15.4 bug - background: linear-gradient(270deg, $system 50%, $system-transparent 100%); - - opacity: 0; - transform: translateX(60px); - transition: all .1s ease-in-out; - - &::before { - content: ''; - position: absolute; - top: 50%; - right: 12px; - transform: translateY(-50%); - width: 12px; - height: 12px; - mask-image: url('$(res)/img/compound/chevron-right-12px.svg'); - mask-position: center; - mask-size: contain; - mask-repeat: no-repeat; - background-color: $secondary-content; - } - } - - &:hover, - &:focus { - cursor: pointer; - border-color: $quinary-content; - - .mx_ThreadInfo_chevron { - opacity: 1; - transform: translateX(0); - } - } - - &::before { - align-self: center; // v-align the threads icon - } -} - -.mx_MessagePanel_narrow .mx_ThreadInfo { +.mx_MessagePanel_narrow .mx_ThreadSummary { min-width: initial; max-width: initial; width: initial; } -$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 { - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - margin-left: 4px; - font-size: $font-12px; - line-height: $threadInfoLineHeight; - color: $secondary-content; - flex: 1; -} - -.mx_ThreadInfo_avatar { - float: left; - margin-right: 8px; -} - -.mx_ThreadInfo_threads-amount { - font-weight: $font-semi-bold; - position: relative; - padding: 0 12px 0 8px; - white-space: nowrap; - line-height: $threadInfoLineHeight; -} - .mx_EventTile[data-shape=ThreadsList] { --topOffset: 20px; --leftOffset: 46px; diff --git a/res/css/views/rooms/_IRCLayout.scss b/res/css/views/rooms/_IRCLayout.scss index 36d045610a..c09ee47676 100644 --- a/res/css/views/rooms/_IRCLayout.scss +++ b/res/css/views/rooms/_IRCLayout.scss @@ -40,7 +40,7 @@ $irc-line-height: $font-18px; margin-right: $right-padding; } - .mx_ThreadInfo { + .mx_ThreadSummary { margin-right: 0; margin-left: 0; } diff --git a/res/css/views/rooms/_ThreadSummary.scss b/res/css/views/rooms/_ThreadSummary.scss new file mode 100644 index 0000000000..cf26dda689 --- /dev/null +++ b/res/css/views/rooms/_ThreadSummary.scss @@ -0,0 +1,117 @@ +/* +Copyright 2022 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. +*/ + +$left-gutter: 64px; // From _EventTile.scss +$threadSummaryLineHeight: calc(2 * $font-12px); + +.mx_ThreadSummary { + min-width: 267px; + max-width: min(calc(100% - $left-gutter), 600px); // leave space on both left & right gutters + width: fit-content; + height: 40px; + position: relative; + background-color: $system; + padding-left: $spacing-12; + display: flex; + align-items: center; + border-radius: 8px; + padding-right: $spacing-16; + margin-top: $spacing-8; + font-size: $font-12px; + color: $secondary-content; + box-sizing: border-box; + justify-content: flex-start; + clear: both; + overflow: hidden; + border: 1px solid $system; // always render a border so the hover effect doesn't require a re-layout + + .mx_ThreadSummary_chevron { + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 60px; + box-sizing: border-box; + // XXX: We use `$system-transparent` instead of `transparent` to work around a Safari <15.4 bug + background: linear-gradient(270deg, $system 50%, $system-transparent 100%); + + opacity: 0; + transform: translateX(60px); + transition: all .1s ease-in-out; + + &::before { + content: ''; + position: absolute; + top: 50%; + right: $spacing-12; + transform: translateY(-50%); + width: 12px; + height: 12px; + mask-image: url('$(res)/img/compound/chevron-right-12px.svg'); + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + background-color: $secondary-content; + } + } + + &:hover, + &:focus { + cursor: pointer; + border-color: $quinary-content; + + .mx_ThreadSummary_chevron { + opacity: 1; + transform: translateX(0); + } + } + + &::before { + align-self: center; // v-align the threads icon + } +} + +// XXX: these classes are re-used outside of the component +.mx_ThreadSummary_threads-amount { + font-weight: $font-semi-bold; + position: relative; + padding: 0 $spacing-12 0 $spacing-8; + white-space: nowrap; + line-height: $threadSummaryLineHeight; +} + +.mx_ThreadSummary_sender { + font-weight: $font-semi-bold; + line-height: $threadSummaryLineHeight; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.mx_ThreadSummary_content { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + margin-left: $spacing-4; + font-size: $font-12px; + line-height: $threadSummaryLineHeight; + color: $secondary-content; + flex: 1; +} + +.mx_ThreadSummary_avatar { + margin-right: $spacing-8; +} diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index d71b682112..6c0bf4635e 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -517,7 +517,7 @@ export class UnwrappedEventTile extends React.Component { } return
- + { this.state.thread.length } diff --git a/src/components/views/rooms/ThreadSummary.tsx b/src/components/views/rooms/ThreadSummary.tsx index 50a74aa20d..b03b200f5c 100644 --- a/src/components/views/rooms/ThreadSummary.tsx +++ b/src/components/views/rooms/ThreadSummary.tsx @@ -48,7 +48,7 @@ const ThreadSummary = ({ mxEvent, thread }: IProps) => { return ( { showThread({ rootEvent: mxEvent, @@ -58,11 +58,11 @@ const ThreadSummary = ({ mxEvent, thread }: IProps) => { }} aria-label={_t("Open thread")} > - + { countSection } -
+
); }; @@ -96,13 +96,13 @@ export const ThreadMessagePreview = ({ thread, showDisplayname = false }: IPrevi fallbackUserId={lastReply.getSender()} width={24} height={24} - className="mx_ThreadInfo_avatar" + className="mx_ThreadSummary_avatar" /> - { showDisplayname &&
+ { showDisplayname &&
{ lastReply.sender?.name ?? lastReply.getSender() }
} -
- +
+ { preview }
diff --git a/test/end-to-end-tests/src/usecases/threads.ts b/test/end-to-end-tests/src/usecases/threads.ts index 4baaa49ce9..24ccd7064a 100644 --- a/test/end-to-end-tests/src/usecases/threads.ts +++ b/test/end-to-end-tests/src/usecases/threads.ts @@ -137,17 +137,17 @@ export async function assertTimelineThreadSummary( content: string, ): Promise { session.log.step("asserts the timeline thread summary is as expected"); - const summaries = await session.queryAll(".mx_MainSplit_timeline .mx_ThreadInfo"); + const summaries = await session.queryAll(".mx_MainSplit_timeline .mx_ThreadSummary"); const summary = summaries[summaries.length - 1]; - assert.equal(await session.innerText(await summary.$(".mx_ThreadInfo_sender")), sender); - assert.equal(await session.innerText(await summary.$(".mx_ThreadInfo_content")), content); + assert.equal(await session.innerText(await summary.$(".mx_ThreadSummary_sender")), sender); + assert.equal(await session.innerText(await summary.$(".mx_ThreadSummary_content")), content); session.log.done(); } export async function clickTimelineThreadSummary(session: ElementSession): Promise { session.log.step(`clicks the latest thread summary in the timeline`); - const summaries = await session.queryAll(".mx_MainSplit_timeline .mx_ThreadInfo"); + const summaries = await session.queryAll(".mx_MainSplit_timeline .mx_ThreadSummary"); await summaries[summaries.length - 1].click(); session.log.done();