Fixes following threads design implementation review (#7100)
This commit is contained in:
parent
b8edebecc9
commit
1de9630e44
16 changed files with 280 additions and 115 deletions
|
@ -38,7 +38,6 @@ limitations under the License.
|
||||||
position: absolute;
|
position: absolute;
|
||||||
font-size: $font-14px;
|
font-size: $font-14px;
|
||||||
z-index: 5001;
|
z-index: 5001;
|
||||||
contain: content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ContextualMenu_right {
|
.mx_ContextualMenu_right {
|
||||||
|
|
|
@ -22,7 +22,7 @@ limitations under the License.
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 4px 0;
|
padding: 8px 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
contain: strict;
|
contain: strict;
|
||||||
|
|
|
@ -22,7 +22,7 @@ limitations under the License.
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
.mx_BaseCard_header {
|
.mx_BaseCard_header {
|
||||||
margin: 8px 0;
|
margin: 4px 0;
|
||||||
|
|
||||||
> h2 {
|
> h2 {
|
||||||
margin: 0 44px;
|
margin: 0 44px;
|
||||||
|
@ -40,13 +40,13 @@ limitations under the License.
|
||||||
width: 20px;
|
width: 20px;
|
||||||
margin: 12px;
|
margin: 12px;
|
||||||
top: 0;
|
top: 0;
|
||||||
border-radius: 10px;
|
border-radius: 50%;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 20px;
|
height: inherit;
|
||||||
width: 20px;
|
width: inherit;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
|
|
|
@ -18,21 +18,29 @@ limitations under the License.
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
padding-right: 0;
|
|
||||||
|
|
||||||
.mx_BaseCard_header {
|
.mx_BaseCard_header {
|
||||||
|
margin-bottom: 12px;
|
||||||
.mx_BaseCard_close,
|
.mx_BaseCard_close,
|
||||||
.mx_BaseCard_back {
|
.mx_BaseCard_back {
|
||||||
margin-top: 15px;
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.mx_BaseCard_back {
|
||||||
|
left: -4px;
|
||||||
}
|
}
|
||||||
.mx_BaseCard_close {
|
.mx_BaseCard_close {
|
||||||
right: -8px;
|
right: -4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ThreadPanel__header {
|
.mx_BaseCard_back ~ .mx_ThreadPanel__header {
|
||||||
width: calc(100% - 60px);
|
width: calc(100% - 60px);
|
||||||
margin-left: 30px;
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ThreadPanel__header {
|
||||||
|
width: calc(100% - 30px);
|
||||||
|
height: 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
@ -47,13 +55,23 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_AccessibleButton {
|
.mx_AccessibleButton {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: $primary-content;
|
color: $secondary-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MessageActionBar_optionsButton {
|
.mx_MessageActionBar_optionsButton {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_MessageActionBar_maskButton {
|
||||||
|
--size: 24px;
|
||||||
|
width: var(--size);
|
||||||
|
height: var(--size);
|
||||||
|
&::after {
|
||||||
|
mask-size: var(--size);
|
||||||
|
mask-image: url("$(res)/img/element-icons/message/overflow-large.svg");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mx_ContextualMenu_wrapper {
|
.mx_ContextualMenu_wrapper {
|
||||||
// It's added here due to some weird error if I pass it directly in the style, even though it's a numeric value, so it's being passed 0 instead.
|
// It's added here due to some weird error if I pass it directly in the style, even though it's a numeric value, so it's being passed 0 instead.
|
||||||
// The error: react_devtools_backend.js:2526 Warning: `NaN` is an invalid value for the `top` css style property.
|
// The error: react_devtools_backend.js:2526 Warning: `NaN` is an invalid value for the `top` css style property.
|
||||||
|
@ -70,6 +88,25 @@ limitations under the License.
|
||||||
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: $secondary-content;
|
color: $secondary-content;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
|
||||||
|
border: 1px solid $quinary-content;
|
||||||
|
box-shadow: 0px 1px 3px rgba(23, 25, 28, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ContextualMenu_chevron_top {
|
||||||
|
left: auto;
|
||||||
|
right: 22px;
|
||||||
|
border-bottom-color: $quinary-content;
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
border: inherit;
|
||||||
|
border-bottom-color: $background;
|
||||||
|
position: absolute;
|
||||||
|
top: 1px;
|
||||||
|
left: -8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ThreadPanel_Header_FilterOptionItem {
|
.mx_ThreadPanel_Header_FilterOptionItem {
|
||||||
|
@ -77,31 +114,33 @@ limitations under the License.
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: visible;
|
padding: 10px 20px 10px 30px;
|
||||||
width: 100%;
|
|
||||||
padding: 20px;
|
|
||||||
padding-left: 30px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $event-selected-color;
|
background-color: $event-selected-color;
|
||||||
}
|
}
|
||||||
&[aria-selected="true"] {
|
&[aria-selected="true"] {
|
||||||
&::before {
|
:first-child {
|
||||||
|
margin-left: -20px;
|
||||||
|
}
|
||||||
|
:first-child::before {
|
||||||
content: "";
|
content: "";
|
||||||
width: 12px;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
grid-column: 1;
|
margin-right: 8px;
|
||||||
grid-row: 1;
|
|
||||||
mask-image: url("$(res)/img/feather-customised/check.svg");
|
mask-image: url("$(res)/img/feather-customised/check.svg");
|
||||||
mask-size: 100%;
|
mask-size: 100%;
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
position: absolute;
|
|
||||||
top: 22px;
|
|
||||||
left: 10px;
|
|
||||||
background-color: $primary-content;
|
background-color: $primary-content;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:last-child {
|
||||||
|
color: $secondary-content;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,24 +170,20 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AutoHideScrollbar {
|
.mx_AutoHideScrollbar {
|
||||||
border-radius: 8px;
|
background: #fff;
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomView_messageListWrapper {
|
|
||||||
background-color: $background;
|
background-color: $background;
|
||||||
padding: 8px;
|
border-radius: 8px;
|
||||||
border-radius: inherit;
|
width: calc(100% - 16px);
|
||||||
|
padding-right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ScrollPanel {
|
.mx_RoomView_MessageList {
|
||||||
.mx_RoomView_MessageList {
|
padding-left: 12px;
|
||||||
padding: 0;
|
padding-right: 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile, .mx_EventListSummary {
|
.mx_EventTile, .mx_EventListSummary {
|
||||||
// Account for scrollbar when hovering
|
// Account for scrollbar when hovering
|
||||||
width: calc(100% - 3px);
|
|
||||||
margin: 0 2px;
|
margin: 0 2px;
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
|
|
||||||
|
@ -170,19 +205,28 @@ limitations under the License.
|
||||||
.mx_DateSeparator {
|
.mx_DateSeparator {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.mx_EventTile_clamp:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile:not([data-layout=bubble]) {
|
||||||
|
.mx_EventTile_e2eIcon {
|
||||||
|
left: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MessageComposer {
|
.mx_MessageComposer {
|
||||||
background-color: $background;
|
background-color: $background;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
width: calc(100% - 8px);
|
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ThreadPanel_dropdown {
|
.mx_ThreadPanel_dropdown {
|
||||||
padding: 4px 8px;
|
padding: 3px 8px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
@ -207,6 +251,36 @@ limitations under the License.
|
||||||
.mx_ThreadPanel_dropdown[aria-expanded=true]::before {
|
.mx_ThreadPanel_dropdown[aria-expanded=true]::before {
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
font-size: $font-12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ThreadPanel_replies {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
color: $secondary-content;
|
||||||
|
font-weight: 600;
|
||||||
|
float: left;
|
||||||
|
margin-right: 12px;
|
||||||
|
font-size: $font-12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ThreadPanel_viewInRoom::before {
|
.mx_ThreadPanel_viewInRoom::before {
|
||||||
|
|
|
@ -460,6 +460,16 @@ $left-gutter: 64px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_clamp {
|
||||||
|
.mx_EventTile_body {
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mx_EventTile_content .markdown-body {
|
.mx_EventTile_content .markdown-body {
|
||||||
font-family: inherit !important;
|
font-family: inherit !important;
|
||||||
white-space: normal !important;
|
white-space: normal !important;
|
||||||
|
@ -663,7 +673,10 @@ $left-gutter: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ThreadInfo {
|
.mx_ThreadInfo {
|
||||||
height: 35px;
|
min-width: 267px;
|
||||||
|
max-width: min(calc(100% - 64px), 600px);
|
||||||
|
width: auto;
|
||||||
|
height: 40px;
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: $system;
|
background-color: $system;
|
||||||
padding-left: 12px;
|
padding-left: 12px;
|
||||||
|
@ -671,13 +684,13 @@ $left-gutter: 64px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding-right: 16px;
|
padding-right: 16px;
|
||||||
padding-top: 8px;
|
margin-top: 8px;
|
||||||
padding-bottom: 8px;
|
|
||||||
font-size: $font-12px;
|
font-size: $font-12px;
|
||||||
color: $secondary-content;
|
color: $secondary-content;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
clear: both;
|
clear: both;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -687,6 +700,44 @@ $left-gutter: 64px;
|
||||||
padding-left: 11px;
|
padding-left: 11px;
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
mask-image: url('$(res)/img/element-icons/thread-summary.svg');
|
||||||
|
mask-position: center;
|
||||||
|
height: 18px;
|
||||||
|
min-width: 18px;
|
||||||
|
background-color: $secondary-content;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "›";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 60px;
|
||||||
|
padding: 0 10px;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 39px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
text-align: right;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
background: linear-gradient(270deg, $system 52.6%, transparent 100%);
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(20px);
|
||||||
|
transition: all .1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover::after {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ThreadInfo_content {
|
.mx_ThreadInfo_content {
|
||||||
|
@ -703,15 +754,6 @@ $left-gutter: 64px;
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ThreadInfo_thread-icon {
|
|
||||||
mask-image: url('$(res)/img/element-icons/thread-summary.svg');
|
|
||||||
mask-position: center;
|
|
||||||
height: 16px;
|
|
||||||
min-width: 16px;
|
|
||||||
background-color: $secondary-content;
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
mask-size: contain;
|
|
||||||
}
|
|
||||||
.mx_ThreadInfo_threads-amount {
|
.mx_ThreadInfo_threads-amount {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -720,10 +762,10 @@ $left-gutter: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile[data-shape=thread_list] {
|
.mx_EventTile[data-shape=thread_list] {
|
||||||
--topOffset: 24px;
|
--topOffset: 20px;
|
||||||
--leftOffset: 46px;
|
--leftOffset: 46px;
|
||||||
|
|
||||||
margin: var(--topOffset) 0;
|
margin: var(--topOffset) 16px var(--topOffset) 0;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -819,6 +861,7 @@ $left-gutter: 64px;
|
||||||
left: auto;
|
left: auto;
|
||||||
right: 2px !important;
|
right: 2px !important;
|
||||||
top: 1px !important;
|
top: 1px !important;
|
||||||
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ReactionsRow {
|
.mx_ReactionsRow {
|
||||||
|
@ -830,7 +873,8 @@ $left-gutter: 64px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_content {
|
.mx_EventTile_content,
|
||||||
|
.mx_RedactedBody {
|
||||||
margin-left: 36px;
|
margin-left: 36px;
|
||||||
margin-right: 50px;
|
margin-right: 50px;
|
||||||
}
|
}
|
||||||
|
|
5
res/img/element-icons/message/overflow-large.svg
Normal file
5
res/img/element-icons/message/overflow-large.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6.66699 12C6.66699 13.1046 5.77156 14 4.66699 14C3.56242 14 2.66699 13.1046 2.66699 12C2.66699 10.8954 3.56242 10 4.66699 10C5.77156 10 6.66699 10.8954 6.66699 12Z" fill="#17191C"/>
|
||||||
|
<path d="M14 12C14 13.1046 13.1046 14 12 14C10.8954 14 10 13.1046 10 12C10 10.8954 10.8954 10 12 10C13.1046 10 14 10.8954 14 12Z" fill="#17191C"/>
|
||||||
|
<path d="M19.333 14C20.4376 14 21.333 13.1046 21.333 12C21.333 10.8954 20.4376 10 19.333 10C18.2284 10 17.333 10.8954 17.333 12C17.333 13.1046 18.2284 14 19.333 14Z" fill="#17191C"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 625 B |
|
@ -355,7 +355,9 @@ export default class RightPanel extends React.Component<IProps, IState> {
|
||||||
panel = <ThreadPanel
|
panel = <ThreadPanel
|
||||||
roomId={roomId}
|
roomId={roomId}
|
||||||
resizeNotifier={this.props.resizeNotifier}
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
onClose={this.onClose} />;
|
onClose={this.onClose}
|
||||||
|
permalinkCreator={this.props.permalinkCreator}
|
||||||
|
/>;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RightPanelPhases.RoomSummary:
|
case RightPanelPhases.RoomSummary:
|
||||||
|
|
|
@ -20,24 +20,24 @@ import { EventTimelineSet } from 'matrix-js-sdk/src/models/event-timeline-set';
|
||||||
import { Room } from 'matrix-js-sdk/src/models/room';
|
import { Room } from 'matrix-js-sdk/src/models/room';
|
||||||
|
|
||||||
import BaseCard from "../views/right_panel/BaseCard";
|
import BaseCard from "../views/right_panel/BaseCard";
|
||||||
import { RightPanelPhases } from "../../stores/RightPanelStorePhases";
|
|
||||||
|
|
||||||
import ResizeNotifier from '../../utils/ResizeNotifier';
|
import ResizeNotifier from '../../utils/ResizeNotifier';
|
||||||
import MatrixClientContext from '../../contexts/MatrixClientContext';
|
import MatrixClientContext from '../../contexts/MatrixClientContext';
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import { ContextMenuButton } from '../../accessibility/context_menu/ContextMenuButton';
|
import { ContextMenuButton } from '../../accessibility/context_menu/ContextMenuButton';
|
||||||
import ContextMenu, { useContextMenu } from './ContextMenu';
|
import ContextMenu, { ChevronFace, useContextMenu } from './ContextMenu';
|
||||||
import RoomContext, { TimelineRenderingType } from '../../contexts/RoomContext';
|
import RoomContext, { TimelineRenderingType } from '../../contexts/RoomContext';
|
||||||
import TimelinePanel from './TimelinePanel';
|
import TimelinePanel from './TimelinePanel';
|
||||||
import { Layout } from '../../settings/Layout';
|
import { Layout } from '../../settings/Layout';
|
||||||
import { useEventEmitter } from '../../hooks/useEventEmitter';
|
import { useEventEmitter } from '../../hooks/useEventEmitter';
|
||||||
import AccessibleButton from '../views/elements/AccessibleButton';
|
import AccessibleButton from '../views/elements/AccessibleButton';
|
||||||
import { TileShape } from '../views/rooms/EventTile';
|
import { TileShape } from '../views/rooms/EventTile';
|
||||||
|
import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
roomId: string;
|
roomId: string;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
resizeNotifier: ResizeNotifier;
|
resizeNotifier: ResizeNotifier;
|
||||||
|
permalinkCreator: RoomPermalinkCreator;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ThreadFilterType {
|
export enum ThreadFilterType {
|
||||||
|
@ -162,7 +162,13 @@ export const ThreadPanelHeader = ({ filterOption, setFilterOption }: {
|
||||||
}}
|
}}
|
||||||
isSelected={opt === value}
|
isSelected={opt === value}
|
||||||
/>);
|
/>);
|
||||||
const contextMenu = menuDisplayed ? <ContextMenu top={0} right={25} onFinished={closeMenu} managed={false}>
|
const contextMenu = menuDisplayed ? <ContextMenu
|
||||||
|
top={0}
|
||||||
|
right={25}
|
||||||
|
onFinished={closeMenu}
|
||||||
|
managed={false}
|
||||||
|
chevronFace={ChevronFace.Top}
|
||||||
|
>
|
||||||
{ contextMenuOptions }
|
{ contextMenuOptions }
|
||||||
</ContextMenu> : null;
|
</ContextMenu> : null;
|
||||||
return <div className="mx_ThreadPanel__header">
|
return <div className="mx_ThreadPanel__header">
|
||||||
|
@ -174,7 +180,7 @@ export const ThreadPanelHeader = ({ filterOption, setFilterOption }: {
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ThreadPanel: React.FC<IProps> = ({ roomId, onClose }) => {
|
const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) => {
|
||||||
const mxClient = useContext(MatrixClientContext);
|
const mxClient = useContext(MatrixClientContext);
|
||||||
const roomContext = useContext(RoomContext);
|
const roomContext = useContext(RoomContext);
|
||||||
const room = mxClient.getRoom(roomId);
|
const room = mxClient.getRoom(roomId);
|
||||||
|
@ -200,7 +206,7 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose }) => {
|
||||||
header={<ThreadPanelHeader filterOption={filterOption} setFilterOption={setFilterOption} />}
|
header={<ThreadPanelHeader filterOption={filterOption} setFilterOption={setFilterOption} />}
|
||||||
className="mx_ThreadPanel"
|
className="mx_ThreadPanel"
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
previousPhase={RightPanelPhases.RoomSummary}
|
withoutScrollContainer={true}
|
||||||
>
|
>
|
||||||
<TimelinePanel
|
<TimelinePanel
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
@ -218,6 +224,7 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose }) => {
|
||||||
showReactions={true}
|
showReactions={true}
|
||||||
className="mx_RoomView_messagePanel mx_GroupLayout"
|
className="mx_RoomView_messagePanel mx_GroupLayout"
|
||||||
membersLoaded={true}
|
membersLoaded={true}
|
||||||
|
permalinkCreator={permalinkCreator}
|
||||||
tileShape={TileShape.ThreadPanel}
|
tileShape={TileShape.ThreadPanel}
|
||||||
/>
|
/>
|
||||||
</BaseCard>
|
</BaseCard>
|
||||||
|
|
|
@ -40,7 +40,7 @@ import RoomContext, { TimelineRenderingType } from '../../contexts/RoomContext';
|
||||||
import ContentMessages from '../../ContentMessages';
|
import ContentMessages from '../../ContentMessages';
|
||||||
import UploadBar from './UploadBar';
|
import UploadBar from './UploadBar';
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import { ThreadListContextMenu } from '../views/context_menus/ThreadListContextMenu';
|
import ThreadListContextMenu from '../views/context_menus/ThreadListContextMenu';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
@ -214,6 +214,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
||||||
className="mx_ThreadView mx_ThreadPanel"
|
className="mx_ThreadView mx_ThreadPanel"
|
||||||
onClose={this.props.onClose}
|
onClose={this.props.onClose}
|
||||||
previousPhase={RightPanelPhases.ThreadPanel}
|
previousPhase={RightPanelPhases.ThreadPanel}
|
||||||
|
previousPhaseLabel={_t("All threads")}
|
||||||
withoutScrollContainer={true}
|
withoutScrollContainer={true}
|
||||||
header={this.renderThreadViewHeader()}
|
header={this.renderThreadViewHeader()}
|
||||||
>
|
>
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src";
|
import { MatrixEvent } from "matrix-js-sdk/src";
|
||||||
import { ButtonEvent } from "../elements/AccessibleButton";
|
import { ButtonEvent } from "../elements/AccessibleButton";
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
|
@ -27,17 +27,18 @@ import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOpti
|
||||||
interface IProps {
|
interface IProps {
|
||||||
mxEvent: MatrixEvent;
|
mxEvent: MatrixEvent;
|
||||||
permalinkCreator: RoomPermalinkCreator;
|
permalinkCreator: RoomPermalinkCreator;
|
||||||
|
onMenuToggle?: (open: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const contextMenuBelow = (elementRect: DOMRect) => {
|
const contextMenuBelow = (elementRect: DOMRect) => {
|
||||||
// align the context menu's icons with the icon which opened the context menu
|
// align the context menu's icons with the icon which opened the context menu
|
||||||
const left = elementRect.left + window.pageXOffset + elementRect.width;
|
const left = elementRect.left + window.pageXOffset + elementRect.width;
|
||||||
const top = elementRect.bottom + window.pageYOffset + 17;
|
const top = elementRect.bottom + window.pageYOffset;
|
||||||
const chevronFace = ChevronFace.None;
|
const chevronFace = ChevronFace.None;
|
||||||
return { left, top, chevronFace };
|
return { left, top, chevronFace };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ThreadListContextMenu: React.FC<IProps> = ({ mxEvent, permalinkCreator }) => {
|
const ThreadListContextMenu: React.FC<IProps> = ({ mxEvent, permalinkCreator, onMenuToggle }) => {
|
||||||
const [optionsPosition, setOptionsPosition] = useState(null);
|
const [optionsPosition, setOptionsPosition] = useState(null);
|
||||||
const closeThreadOptions = useCallback(() => {
|
const closeThreadOptions = useCallback(() => {
|
||||||
setOptionsPosition(null);
|
setOptionsPosition(null);
|
||||||
|
@ -72,6 +73,12 @@ export const ThreadListContextMenu: React.FC<IProps> = ({ mxEvent, permalinkCrea
|
||||||
}
|
}
|
||||||
}, [closeThreadOptions, optionsPosition]);
|
}, [closeThreadOptions, optionsPosition]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (onMenuToggle) {
|
||||||
|
onMenuToggle(!!optionsPosition);
|
||||||
|
}
|
||||||
|
}, [optionsPosition, onMenuToggle]);
|
||||||
|
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
<ContextMenuTooltipButton
|
<ContextMenuTooltipButton
|
||||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
|
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
|
||||||
|
|
|
@ -294,7 +294,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
|
||||||
&& this.context.timelineRenderingType !== TimelineRenderingType.Thread) && (
|
&& this.context.timelineRenderingType !== TimelineRenderingType.Thread) && (
|
||||||
<RovingAccessibleTooltipButton
|
<RovingAccessibleTooltipButton
|
||||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_threadButton"
|
className="mx_MessageActionBar_maskButton mx_MessageActionBar_threadButton"
|
||||||
title={_t("Thread")}
|
title={_t("Reply in thread")}
|
||||||
onClick={this.onThreadClick}
|
onClick={this.onThreadClick}
|
||||||
key="thread"
|
key="thread"
|
||||||
/>
|
/>
|
||||||
|
@ -327,7 +327,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
|
||||||
) {
|
) {
|
||||||
toolbarOpts.unshift(<RovingAccessibleTooltipButton
|
toolbarOpts.unshift(<RovingAccessibleTooltipButton
|
||||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_threadButton"
|
className="mx_MessageActionBar_maskButton mx_MessageActionBar_threadButton"
|
||||||
title={_t("Thread")}
|
title={_t("Reply in thread")}
|
||||||
onClick={this.onThreadClick}
|
onClick={this.onThreadClick}
|
||||||
key="thread"
|
key="thread"
|
||||||
/>);
|
/>);
|
||||||
|
|
|
@ -31,6 +31,7 @@ interface IProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
withoutScrollContainer?: boolean;
|
withoutScrollContainer?: boolean;
|
||||||
previousPhase?: RightPanelPhases;
|
previousPhase?: RightPanelPhases;
|
||||||
|
previousPhaseLabel?: string;
|
||||||
closeLabel?: string;
|
closeLabel?: string;
|
||||||
onClose?(): void;
|
onClose?(): void;
|
||||||
refireParams?;
|
refireParams?;
|
||||||
|
@ -56,6 +57,7 @@ const BaseCard: React.FC<IProps> = ({
|
||||||
footer,
|
footer,
|
||||||
withoutScrollContainer,
|
withoutScrollContainer,
|
||||||
previousPhase,
|
previousPhase,
|
||||||
|
previousPhaseLabel,
|
||||||
children,
|
children,
|
||||||
refireParams,
|
refireParams,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -68,7 +70,8 @@ const BaseCard: React.FC<IProps> = ({
|
||||||
refireParams: refireParams,
|
refireParams: refireParams,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
backButton = <AccessibleButton className="mx_BaseCard_back" onClick={onBackClick} title={_t("Back")} />;
|
const label = previousPhaseLabel ?? _t("Back");
|
||||||
|
backButton = <AccessibleButton className="mx_BaseCard_back" onClick={onBackClick} title={label} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
let closeButton;
|
let closeButton;
|
||||||
|
|
|
@ -33,6 +33,7 @@ import { useSettingValue } from "../../../hooks/useSettings";
|
||||||
import { useReadPinnedEvents, usePinnedEvents } from './PinnedMessagesCard';
|
import { useReadPinnedEvents, usePinnedEvents } from './PinnedMessagesCard';
|
||||||
import { dispatchShowThreadsPanelEvent } from "../../../dispatcher/dispatch-actions/threads";
|
import { dispatchShowThreadsPanelEvent } from "../../../dispatcher/dispatch-actions/threads";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
|
|
||||||
const ROOM_INFO_PHASES = [
|
const ROOM_INFO_PHASES = [
|
||||||
RightPanelPhases.RoomSummary,
|
RightPanelPhases.RoomSummary,
|
||||||
|
@ -72,6 +73,11 @@ interface IProps {
|
||||||
|
|
||||||
@replaceableComponent("views.right_panel.RoomHeaderButtons")
|
@replaceableComponent("views.right_panel.RoomHeaderButtons")
|
||||||
export default class RoomHeaderButtons extends HeaderButtons<IProps> {
|
export default class RoomHeaderButtons extends HeaderButtons<IProps> {
|
||||||
|
private static readonly THREAD_PHASES = [
|
||||||
|
RightPanelPhases.ThreadPanel,
|
||||||
|
RightPanelPhases.ThreadView,
|
||||||
|
];
|
||||||
|
|
||||||
constructor(props: IProps) {
|
constructor(props: IProps) {
|
||||||
super(props, HeaderKind.Room);
|
super(props, HeaderKind.Room);
|
||||||
}
|
}
|
||||||
|
@ -117,6 +123,17 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
|
||||||
this.setPhase(RightPanelPhases.PinnedMessages);
|
this.setPhase(RightPanelPhases.PinnedMessages);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onThreadsPanelClicked = () => {
|
||||||
|
if (RoomHeaderButtons.THREAD_PHASES.includes(this.state.phase)) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: Action.ToggleRightPanel,
|
||||||
|
type: "room",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dispatchShowThreadsPanelEvent();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public renderButtons() {
|
public renderButtons() {
|
||||||
return <>
|
return <>
|
||||||
<PinnedMessagesHeaderButton
|
<PinnedMessagesHeaderButton
|
||||||
|
@ -127,11 +144,8 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
|
||||||
{ SettingsStore.getValue("feature_thread") && <HeaderButton
|
{ SettingsStore.getValue("feature_thread") && <HeaderButton
|
||||||
name="threadsButton"
|
name="threadsButton"
|
||||||
title={_t("Threads")}
|
title={_t("Threads")}
|
||||||
onClick={dispatchShowThreadsPanelEvent}
|
onClick={this.onThreadsPanelClicked}
|
||||||
isHighlighted={this.isPhase([
|
isHighlighted={this.isPhase(RoomHeaderButtons.THREAD_PHASES)}
|
||||||
RightPanelPhases.ThreadPanel,
|
|
||||||
RightPanelPhases.ThreadView,
|
|
||||||
])}
|
|
||||||
analytics={['Right Panel', 'Threads List Button', 'click']}
|
analytics={['Right Panel', 'Threads List Button', 'click']}
|
||||||
/> }
|
/> }
|
||||||
<HeaderButton
|
<HeaderButton
|
||||||
|
|
|
@ -48,7 +48,6 @@ import { Container, MAX_PINNED, WidgetLayoutStore } from "../../../stores/widget
|
||||||
import RoomName from "../elements/RoomName";
|
import RoomName from "../elements/RoomName";
|
||||||
import UIStore from "../../../stores/UIStore";
|
import UIStore from "../../../stores/UIStore";
|
||||||
import ExportDialog from "../dialogs/ExportDialog";
|
import ExportDialog from "../dialogs/ExportDialog";
|
||||||
import { dispatchShowThreadsPanelEvent } from "../../../dispatcher/dispatch-actions/threads";
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
@ -284,11 +283,6 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, onClose }) => {
|
||||||
<Button className="mx_RoomSummaryCard_icon_export" onClick={onRoomExportClick}>
|
<Button className="mx_RoomSummaryCard_icon_export" onClick={onRoomExportClick}>
|
||||||
{ _t("Export chat") }
|
{ _t("Export chat") }
|
||||||
</Button>
|
</Button>
|
||||||
{ SettingsStore.getValue("feature_thread") && (
|
|
||||||
<Button className="mx_RoomSummaryCard_icon_threads" onClick={dispatchShowThreadsPanelEvent}>
|
|
||||||
{ _t("Show threads") }
|
|
||||||
</Button>
|
|
||||||
) }
|
|
||||||
<Button className="mx_RoomSummaryCard_icon_share" onClick={onShareRoomClick}>
|
<Button className="mx_RoomSummaryCard_icon_share" onClick={onShareRoomClick}>
|
||||||
{ _t("Share room") }
|
{ _t("Share room") }
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -67,7 +67,7 @@ import { MediaEventHelper } from "../../../utils/MediaEventHelper";
|
||||||
import Toolbar from '../../../accessibility/Toolbar';
|
import Toolbar from '../../../accessibility/Toolbar';
|
||||||
import { POLL_START_EVENT_TYPE } from '../../../polls/consts';
|
import { POLL_START_EVENT_TYPE } from '../../../polls/consts';
|
||||||
import { RovingAccessibleTooltipButton } from '../../../accessibility/roving/RovingAccessibleTooltipButton';
|
import { RovingAccessibleTooltipButton } from '../../../accessibility/roving/RovingAccessibleTooltipButton';
|
||||||
import { ThreadListContextMenu } from '../context_menus/ThreadListContextMenu';
|
import ThreadListContextMenu from '../context_menus/ThreadListContextMenu';
|
||||||
|
|
||||||
const eventTileTypes = {
|
const eventTileTypes = {
|
||||||
[EventType.RoomMessage]: 'messages.MessageEvent',
|
[EventType.RoomMessage]: 'messages.MessageEvent',
|
||||||
|
@ -552,7 +552,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private renderThreadLastMessagePreview(): JSX.Element | null {
|
private get thread(): Thread | null {
|
||||||
if (!SettingsStore.getValue("feature_thread")) {
|
if (!SettingsStore.getValue("feature_thread")) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -570,7 +570,28 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [lastEvent] = thread.events
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderThreadPanelSummary(): JSX.Element | null {
|
||||||
|
if (!this.thread) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="mx_ThreadPanel_replies">
|
||||||
|
<span className="mx_ThreadPanel_repliesSummary">
|
||||||
|
{ this.thread.length }
|
||||||
|
</span>
|
||||||
|
{ this.renderThreadLastMessagePreview() }
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderThreadLastMessagePreview(): JSX.Element | null {
|
||||||
|
if (!this.thread) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [lastEvent] = this.thread.events
|
||||||
.filter(event => event.isThreadRelation)
|
.filter(event => event.isThreadRelation)
|
||||||
.slice(-1);
|
.slice(-1);
|
||||||
const threadMessagePreview = MessagePreviewStore.instance.generatePreviewForEvent(lastEvent);
|
const threadMessagePreview = MessagePreviewStore.instance.generatePreviewForEvent(lastEvent);
|
||||||
|
@ -590,24 +611,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderThreadInfo(): React.ReactNode {
|
private renderThreadInfo(): React.ReactNode {
|
||||||
if (!SettingsStore.getValue("feature_thread")) {
|
if (!this.thread) {
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accessing the threads value through the room due to a race condition
|
|
||||||
* that will be solved when there are proper backend support for threads
|
|
||||||
* We currently have no reliable way to discover than an event is a thread
|
|
||||||
* when we are at the sync stage
|
|
||||||
*/
|
|
||||||
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
|
||||||
const thread = room?.threads.get(this.props.mxEvent.getId());
|
|
||||||
|
|
||||||
if (thread && !thread.ready) {
|
|
||||||
thread.addEvent(this.props.mxEvent, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!thread || this.props.showThreadInfo === false || thread.length === 0) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -620,10 +624,9 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="mx_ThreadInfo_thread-icon" />
|
|
||||||
<span className="mx_ThreadInfo_threads-amount">
|
<span className="mx_ThreadInfo_threads-amount">
|
||||||
{ _t("%(count)s reply", {
|
{ _t("%(count)s reply", {
|
||||||
count: thread.length,
|
count: this.thread.length,
|
||||||
}) }
|
}) }
|
||||||
</span>
|
</span>
|
||||||
{ this.renderThreadLastMessagePreview() }
|
{ this.renderThreadLastMessagePreview() }
|
||||||
|
@ -1063,6 +1066,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
mx_EventTile_bad: isEncryptionFailure,
|
mx_EventTile_bad: isEncryptionFailure,
|
||||||
mx_EventTile_emote: msgtype === 'm.emote',
|
mx_EventTile_emote: msgtype === 'm.emote',
|
||||||
mx_EventTile_noSender: this.props.hideSender,
|
mx_EventTile_noSender: this.props.hideSender,
|
||||||
|
mx_EventTile_clamp: this.props.tileShape === TileShape.ThreadPanel,
|
||||||
});
|
});
|
||||||
|
|
||||||
// If the tile is in the Sending state, don't speak the message.
|
// If the tile is in the Sending state, don't speak the message.
|
||||||
|
@ -1161,11 +1165,16 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
|| this.state.hover
|
|| this.state.hover
|
||||||
|| this.state.actionBarFocused);
|
|| this.state.actionBarFocused);
|
||||||
|
|
||||||
|
// Thread panel shows the timestamp of the last reply in that thread
|
||||||
|
const ts = this.props.tileShape !== TileShape.ThreadPanel
|
||||||
|
? this.props.mxEvent.getTs()
|
||||||
|
: this.props.mxEvent.getThread().lastReply.getTs();
|
||||||
|
|
||||||
const timestamp = showTimestamp ?
|
const timestamp = showTimestamp ?
|
||||||
<MessageTimestamp
|
<MessageTimestamp
|
||||||
showRelative={this.props.tileShape === TileShape.ThreadPanel}
|
showRelative={this.props.tileShape === TileShape.ThreadPanel}
|
||||||
showTwelveHour={this.props.isTwelveHour}
|
showTwelveHour={this.props.isTwelveHour}
|
||||||
ts={this.props.mxEvent.getTs()}
|
ts={ts}
|
||||||
/> : null;
|
/> : null;
|
||||||
|
|
||||||
const keyRequestHelpText =
|
const keyRequestHelpText =
|
||||||
|
@ -1337,11 +1346,15 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
"data-has-reply": !!replyChain,
|
"data-has-reply": !!replyChain,
|
||||||
"onMouseEnter": () => this.setState({ hover: true }),
|
"onMouseEnter": () => this.setState({ hover: true }),
|
||||||
"onMouseLeave": () => this.setState({ hover: false }),
|
"onMouseLeave": () => this.setState({ hover: false }),
|
||||||
"onClick": () => dispatchShowThreadEvent(this.props.mxEvent),
|
|
||||||
}, <>
|
}, <>
|
||||||
{ sender }
|
{ sender }
|
||||||
{ avatar }
|
{ avatar }
|
||||||
<div className={lineClasses} key="mx_EventTile_line">
|
<div
|
||||||
|
className={lineClasses}
|
||||||
|
onClick={() => dispatchShowThreadEvent(this.props.mxEvent)}
|
||||||
|
key="mx_EventTile_line"
|
||||||
|
>
|
||||||
{ linkedTimestamp }
|
{ linkedTimestamp }
|
||||||
{ this.renderE2EPadlock() }
|
{ this.renderE2EPadlock() }
|
||||||
{ replyChain }
|
{ replyChain }
|
||||||
|
@ -1359,19 +1372,21 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
tileShape={this.props.tileShape}
|
tileShape={this.props.tileShape}
|
||||||
/>
|
/>
|
||||||
{ keyRequestInfo }
|
{ keyRequestInfo }
|
||||||
<Toolbar className="mx_MessageActionBar" aria-label={_t("Message Actions")} aria-live="off">
|
{ this.renderThreadPanelSummary() }
|
||||||
<RovingAccessibleTooltipButton
|
|
||||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_threadButton"
|
|
||||||
title={_t("Thread")}
|
|
||||||
onClick={() => dispatchShowThreadEvent(this.props.mxEvent)}
|
|
||||||
key="thread"
|
|
||||||
/>
|
|
||||||
<ThreadListContextMenu
|
|
||||||
mxEvent={this.props.mxEvent}
|
|
||||||
permalinkCreator={this.props.permalinkCreator} />
|
|
||||||
</Toolbar>
|
|
||||||
{ this.renderThreadLastMessagePreview() }
|
|
||||||
</div>
|
</div>
|
||||||
|
<Toolbar className="mx_MessageActionBar" aria-label={_t("Message Actions")} aria-live="off">
|
||||||
|
<RovingAccessibleTooltipButton
|
||||||
|
className="mx_MessageActionBar_maskButton mx_MessageActionBar_threadButton"
|
||||||
|
title={_t("Reply in thread")}
|
||||||
|
onClick={() => dispatchShowThreadEvent(this.props.mxEvent)}
|
||||||
|
key="thread"
|
||||||
|
/>
|
||||||
|
<ThreadListContextMenu
|
||||||
|
mxEvent={this.props.mxEvent}
|
||||||
|
permalinkCreator={this.props.permalinkCreator}
|
||||||
|
onMenuToggle={this.onActionBarFocusChange}
|
||||||
|
/>
|
||||||
|
</Toolbar>
|
||||||
{ msgOption }
|
{ msgOption }
|
||||||
</>)
|
</>)
|
||||||
);
|
);
|
||||||
|
|
|
@ -1573,7 +1573,7 @@
|
||||||
"Key request sent.": "Key request sent.",
|
"Key request sent.": "Key request sent.",
|
||||||
"<requestLink>Re-request encryption keys</requestLink> from your other sessions.": "<requestLink>Re-request encryption keys</requestLink> from your other sessions.",
|
"<requestLink>Re-request encryption keys</requestLink> from your other sessions.": "<requestLink>Re-request encryption keys</requestLink> from your other sessions.",
|
||||||
"Message Actions": "Message Actions",
|
"Message Actions": "Message Actions",
|
||||||
"Thread": "Thread",
|
"Reply in thread": "Reply in thread",
|
||||||
"This message cannot be decrypted": "This message cannot be decrypted",
|
"This message cannot be decrypted": "This message cannot be decrypted",
|
||||||
"Encrypted by an unverified session": "Encrypted by an unverified session",
|
"Encrypted by an unverified session": "Encrypted by an unverified session",
|
||||||
"Unencrypted": "Unencrypted",
|
"Unencrypted": "Unencrypted",
|
||||||
|
@ -1864,7 +1864,6 @@
|
||||||
"%(count)s people|one": "%(count)s person",
|
"%(count)s people|one": "%(count)s person",
|
||||||
"Show files": "Show files",
|
"Show files": "Show files",
|
||||||
"Export chat": "Export chat",
|
"Export chat": "Export chat",
|
||||||
"Show threads": "Show threads",
|
|
||||||
"Share room": "Share room",
|
"Share room": "Share room",
|
||||||
"Room settings": "Room settings",
|
"Room settings": "Room settings",
|
||||||
"Trusted": "Trusted",
|
"Trusted": "Trusted",
|
||||||
|
@ -3012,6 +3011,7 @@
|
||||||
"All threads": "All threads",
|
"All threads": "All threads",
|
||||||
"Shows all threads from current room": "Shows all threads from current room",
|
"Shows all threads from current room": "Shows all threads from current room",
|
||||||
"Show:": "Show:",
|
"Show:": "Show:",
|
||||||
|
"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 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.",
|
"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.",
|
||||||
"Failed to load timeline position": "Failed to load timeline position",
|
"Failed to load timeline position": "Failed to load timeline position",
|
||||||
|
|
Loading…
Reference in a new issue