Fix baseline misalignment of thread panel summary by deduplication (#8413)
This commit is contained in:
parent
5a5a792593
commit
8baa46b0dd
10 changed files with 147 additions and 143 deletions
|
@ -263,6 +263,7 @@
|
||||||
@import "./views/rooms/_SearchBar.scss";
|
@import "./views/rooms/_SearchBar.scss";
|
||||||
@import "./views/rooms/_SendMessageComposer.scss";
|
@import "./views/rooms/_SendMessageComposer.scss";
|
||||||
@import "./views/rooms/_Stickers.scss";
|
@import "./views/rooms/_Stickers.scss";
|
||||||
|
@import "./views/rooms/_ThreadSummary.scss";
|
||||||
@import "./views/rooms/_TopUnreadMessagesBar.scss";
|
@import "./views/rooms/_TopUnreadMessagesBar.scss";
|
||||||
@import "./views/rooms/_VoiceRecordComposerTile.scss";
|
@import "./views/rooms/_VoiceRecordComposerTile.scss";
|
||||||
@import "./views/rooms/_WhoIsTypingTile.scss";
|
@import "./views/rooms/_WhoIsTypingTile.scss";
|
||||||
|
|
|
@ -140,7 +140,7 @@ limitations under the License.
|
||||||
// Account for scrollbar when hovering
|
// Account for scrollbar when hovering
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
|
|
||||||
.mx_ThreadInfo {
|
.mx_ThreadSummary {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-right: 11px;
|
padding-right: 11px;
|
||||||
|
|
||||||
|
@ -257,28 +257,14 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_ThreadPanel_replies {
|
.mx_ThreadPanel_replies {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
.mx_ThreadPanel_repliesSummary {
|
.mx_ThreadSummary_threads-amount {
|
||||||
&::before {
|
color: $secondary-content;
|
||||||
content: "";
|
font-size: $font-12px;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
color: $secondary-content;
|
|
||||||
font-weight: 600;
|
|
||||||
float: left;
|
|
||||||
margin-right: 12px;
|
|
||||||
font-size: $font-12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ThreadPanel_viewInRoom::before {
|
.mx_ThreadPanel_viewInRoom::before {
|
||||||
|
|
|
@ -69,7 +69,7 @@ limitations under the License.
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ThreadInfo {
|
.mx_ThreadSummary {
|
||||||
margin-left: 36px;
|
margin-left: 36px;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
max-width: min(calc(100% - 36px), 600px);
|
max-width: min(calc(100% - 36px), 600px);
|
||||||
|
|
|
@ -37,7 +37,7 @@ limitations under the License.
|
||||||
margin-left: 49px;
|
margin-left: 49px;
|
||||||
font-size: $font-14px;
|
font-size: $font-14px;
|
||||||
|
|
||||||
.mx_ThreadInfo {
|
.mx_ThreadSummary {
|
||||||
clear: both;
|
clear: both;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
}
|
}
|
||||||
|
@ -172,7 +172,7 @@ limitations under the License.
|
||||||
margin-right: 32px;
|
margin-right: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ThreadInfo {
|
.mx_ThreadSummary {
|
||||||
float: right;
|
float: right;
|
||||||
margin-right: calc(-1 * var(--gutterSize));
|
margin-right: calc(-1 * var(--gutterSize));
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,7 @@ $left-gutter: 64px;
|
||||||
background-color: $alert;
|
background-color: $alert;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ThreadInfo,
|
.mx_ThreadSummary,
|
||||||
.mx_ThreadSummaryIcon {
|
.mx_ThreadSummaryIcon {
|
||||||
margin-left: 64px;
|
margin-left: 64px;
|
||||||
}
|
}
|
||||||
|
@ -304,7 +304,7 @@ $left-gutter: 64px;
|
||||||
.mx_RoomView_timeline_rr_enabled {
|
.mx_RoomView_timeline_rr_enabled {
|
||||||
.mx_EventTile[data-layout=group] {
|
.mx_EventTile[data-layout=group] {
|
||||||
|
|
||||||
.mx_ThreadInfo,
|
.mx_ThreadSummary,
|
||||||
.mx_ThreadSummaryIcon,
|
.mx_ThreadSummaryIcon,
|
||||||
.mx_EventTile_line {
|
.mx_EventTile_line {
|
||||||
/* ideally should be 100px, but 95px gives us a max thumbnail size of 800x600, which is nice */
|
/* 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;
|
min-height: $font-14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ThreadInfo {
|
.mx_ThreadSummary {
|
||||||
max-width: min(calc(100% - $left-gutter - 110px), 600px); // leave space on both left & right gutters
|
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_ThreadSummaryIcon::before,
|
||||||
.mx_ThreadInfo::before {
|
.mx_ThreadSummary::before {
|
||||||
content: "";
|
content: "";
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
mask-image: url('$(res)/img/element-icons/thread-summary.svg');
|
mask-image: url('$(res)/img/element-icons/thread-summary.svg');
|
||||||
|
@ -707,113 +708,12 @@ $left-gutter: 64px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ThreadInfo {
|
.mx_MessagePanel_narrow .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: 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 {
|
|
||||||
min-width: initial;
|
min-width: initial;
|
||||||
max-width: initial;
|
max-width: initial;
|
||||||
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] {
|
.mx_EventTile[data-shape=ThreadsList] {
|
||||||
--topOffset: 20px;
|
--topOffset: 20px;
|
||||||
--leftOffset: 46px;
|
--leftOffset: 46px;
|
||||||
|
|
|
@ -40,7 +40,7 @@ $irc-line-height: $font-18px;
|
||||||
margin-right: $right-padding;
|
margin-right: $right-padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ThreadInfo {
|
.mx_ThreadSummary {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
117
res/css/views/rooms/_ThreadSummary.scss
Normal file
117
res/css/views/rooms/_ThreadSummary.scss
Normal file
|
@ -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;
|
||||||
|
}
|
|
@ -517,7 +517,7 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="mx_ThreadPanel_replies">
|
return <div className="mx_ThreadPanel_replies">
|
||||||
<span className="mx_ThreadPanel_repliesSummary">
|
<span className="mx_ThreadSummary_threads-amount">
|
||||||
{ this.state.thread.length }
|
{ this.state.thread.length }
|
||||||
</span>
|
</span>
|
||||||
<ThreadMessagePreview thread={this.state.thread} />
|
<ThreadMessagePreview thread={this.state.thread} />
|
||||||
|
|
|
@ -48,7 +48,7 @@ const ThreadSummary = ({ mxEvent, thread }: IProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
className="mx_ThreadInfo"
|
className="mx_ThreadSummary"
|
||||||
onClick={(ev: ButtonEvent) => {
|
onClick={(ev: ButtonEvent) => {
|
||||||
showThread({
|
showThread({
|
||||||
rootEvent: mxEvent,
|
rootEvent: mxEvent,
|
||||||
|
@ -58,11 +58,11 @@ const ThreadSummary = ({ mxEvent, thread }: IProps) => {
|
||||||
}}
|
}}
|
||||||
aria-label={_t("Open thread")}
|
aria-label={_t("Open thread")}
|
||||||
>
|
>
|
||||||
<span className="mx_ThreadInfo_threads-amount">
|
<span className="mx_ThreadSummary_threads-amount">
|
||||||
{ countSection }
|
{ countSection }
|
||||||
</span>
|
</span>
|
||||||
<ThreadMessagePreview thread={thread} showDisplayname={!roomContext.narrow} />
|
<ThreadMessagePreview thread={thread} showDisplayname={!roomContext.narrow} />
|
||||||
<div className="mx_ThreadInfo_chevron" />
|
<div className="mx_ThreadSummary_chevron" />
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -96,13 +96,13 @@ export const ThreadMessagePreview = ({ thread, showDisplayname = false }: IPrevi
|
||||||
fallbackUserId={lastReply.getSender()}
|
fallbackUserId={lastReply.getSender()}
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
className="mx_ThreadInfo_avatar"
|
className="mx_ThreadSummary_avatar"
|
||||||
/>
|
/>
|
||||||
{ showDisplayname && <div className="mx_ThreadInfo_sender">
|
{ showDisplayname && <div className="mx_ThreadSummary_sender">
|
||||||
{ lastReply.sender?.name ?? lastReply.getSender() }
|
{ lastReply.sender?.name ?? lastReply.getSender() }
|
||||||
</div> }
|
</div> }
|
||||||
<div className="mx_ThreadInfo_content">
|
<div className="mx_ThreadSummary_content">
|
||||||
<span className="mx_ThreadInfo_message-preview">
|
<span className="mx_ThreadSummary_message-preview">
|
||||||
{ preview }
|
{ preview }
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -137,17 +137,17 @@ export async function assertTimelineThreadSummary(
|
||||||
content: string,
|
content: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
session.log.step("asserts the timeline thread summary is as expected");
|
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];
|
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_ThreadSummary_sender")), sender);
|
||||||
assert.equal(await session.innerText(await summary.$(".mx_ThreadInfo_content")), content);
|
assert.equal(await session.innerText(await summary.$(".mx_ThreadSummary_content")), content);
|
||||||
session.log.done();
|
session.log.done();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function clickTimelineThreadSummary(session: ElementSession): Promise<void> {
|
export async function clickTimelineThreadSummary(session: ElementSession): Promise<void> {
|
||||||
session.log.step(`clicks the latest thread summary in the timeline`);
|
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();
|
await summaries[summaries.length - 1].click();
|
||||||
|
|
||||||
session.log.done();
|
session.log.done();
|
||||||
|
|
Loading…
Reference in a new issue