From 6868478044e8eb4eccfa15b279d9193499907891 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 8 Jul 2021 11:30:56 +0200 Subject: [PATCH 01/16] Add threaded messaging feature flag --- src/i18n/strings/en_EN.json | 1 + src/settings/Settings.tsx | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index bbf6954435..eb82c1b533 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -808,6 +808,7 @@ "Render LaTeX maths in messages": "Render LaTeX maths in messages", "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.", "Message Pinning": "Message Pinning", + "Threaded messaging": "Threaded messaging", "Custom user status messages": "Custom user status messages", "Group & filter rooms by custom tags (refresh to apply changes)": "Group & filter rooms by custom tags (refresh to apply changes)", "Render simple counters in room header": "Render simple counters in room header", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 1751eddb2c..2224d1bbd5 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -239,6 +239,12 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_FEATURE, default: false, }, + "feature_threading": { + isFeature: true, + displayName: _td("Threaded messaging"), + supportedLevels: LEVELS_FEATURE, + default: false, + }, "feature_custom_status": { isFeature: true, displayName: _td("Custom user status messages"), From cf3117bd57ee298fa84cc0b0dea6eba7804abc4a Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 8 Jul 2021 11:55:31 +0200 Subject: [PATCH 02/16] Migrate ViewSourceEvent to TypeScript --- ...ViewSourceEvent.js => ViewSourceEvent.tsx} | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) rename src/components/views/messages/{ViewSourceEvent.js => ViewSourceEvent.tsx} (85%) diff --git a/src/components/views/messages/ViewSourceEvent.js b/src/components/views/messages/ViewSourceEvent.tsx similarity index 85% rename from src/components/views/messages/ViewSourceEvent.js rename to src/components/views/messages/ViewSourceEvent.tsx index 62454fef1a..d8b558c4a1 100644 --- a/src/components/views/messages/ViewSourceEvent.js +++ b/src/components/views/messages/ViewSourceEvent.tsx @@ -15,27 +15,29 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import classNames from 'classnames'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; +import { MatrixEvent } from '../../../../../matrix-js-sdk/src'; + +interface IProps { + mxEvent: MatrixEvent; +} + +interface IState { + expanded: boolean; +} @replaceableComponent("views.messages.ViewSourceEvent") -export default class ViewSourceEvent extends React.PureComponent { - static propTypes = { - /* the MatrixEvent to show */ - mxEvent: PropTypes.object.isRequired, - }; - - constructor(props) { +export default class ViewSourceEvent extends React.PureComponent { + constructor(props: IProps) { super(props); - this.state = { expanded: false, }; } - componentDidMount() { + public componentDidMount(): void { const { mxEvent } = this.props; const client = MatrixClientPeg.get(); @@ -46,15 +48,15 @@ export default class ViewSourceEvent extends React.PureComponent { } } - onToggle = (ev) => { + private onToggle = (ev: React.MouseEvent): void => { ev.preventDefault(); const { expanded } = this.state; this.setState({ expanded: !expanded, }); - } + }; - render() { + public render(): React.ReactNode { const { mxEvent } = this.props; const { expanded } = this.state; From d9718027892e0932ebf84ab10652c5d616e79f7b Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 10 Aug 2021 14:30:12 +0200 Subject: [PATCH 03/16] Create ThreadView phase in RightPanel --- res/css/views/messages/_MessageActionBar.scss | 4 + src/components/structures/RightPanel.tsx | 9 ++ src/components/structures/ThreadView.tsx | 90 +++++++++++++++++++ .../views/messages/MessageActionBar.js | 33 +++++-- src/i18n/strings/en_EN.json | 1 + src/stores/RightPanelStorePhases.ts | 3 + 6 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 src/components/structures/ThreadView.tsx diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index 69f3c672b7..509ded8ee8 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -92,6 +92,10 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/room/message-bar/reply.svg'); } +.mx_MessageActionBar_threadButton::after { + mask-image: url('$(res)/img/element-icons/room/files.svg'); +} + .mx_MessageActionBar_editButton::after { mask-image: url('$(res)/img/element-icons/room/message-bar/edit.svg'); } diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index 95d70e913a..f2bd7db0f3 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -45,6 +45,7 @@ import GroupRoomInfo from "../views/groups/GroupRoomInfo"; import UserInfo from "../views/right_panel/UserInfo"; import ThirdPartyMemberInfo from "../views/rooms/ThirdPartyMemberInfo"; import FilePanel from "./FilePanel"; +import ThreadView from "./ThreadView"; import NotificationPanel from "./NotificationPanel"; import ResizeNotifier from "../../utils/ResizeNotifier"; import PinnedMessagesCard from "../views/right_panel/PinnedMessagesCard"; @@ -309,6 +310,14 @@ export default class RightPanel extends React.Component { panel = ; break; + case RightPanelPhases.ThreadView: + panel = ; + break; + case RightPanelPhases.RoomSummary: panel = ; break; diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx new file mode 100644 index 0000000000..8b196cb4f0 --- /dev/null +++ b/src/components/structures/ThreadView.tsx @@ -0,0 +1,90 @@ +/* +Copyright 2016 OpenMarket Ltd +Copyright 2019 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import { MatrixEvent } from 'matrix-js-sdk/src'; + +import BaseCard from "../views/right_panel/BaseCard"; +import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; +import { replaceableComponent } from "../../utils/replaceableComponent"; +import { MatrixClientPeg } from '../../MatrixClientPeg'; + +import ResizeNotifier from '../../utils/ResizeNotifier'; +import EventTile from '../views/rooms/EventTile'; +import MessageComposer from '../views/rooms/MessageComposer'; + +interface IProps { + roomId: string; + onClose: () => void; + resizeNotifier: ResizeNotifier; + mxEvent: MatrixEvent; +} + +interface IState { +} + +/* + * Component which shows the filtered file using a TimelinePanel + */ +@replaceableComponent("structures.ThreadView") +class ThreadView extends React.Component { + state = {}; + + public componentDidMount(): void {} + + public componentWillUnmount(): void {} + + public renderEventTile(event: MatrixEvent): JSX.Element { + return ; + } + + public render() { + const client = MatrixClientPeg.get(); + const room = client.getRoom(this.props.roomId); + const thread = room.getThread(this.props.mxEvent.getId()); + return ( + + { this.renderEventTile(this.props.mxEvent) } + + { thread && ( + thread.eventTimeline.map((event: MatrixEvent) => { + return this.renderEventTile(event); + }) + ) } + + + + ); + } +} + +export default ThreadView; diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index 7fe0eca697..25f3dc740f 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -23,6 +23,8 @@ import { EventStatus } from 'matrix-js-sdk/src/models/event'; import { _t } from '../../../languageHandler'; import * as sdk from '../../../index'; import dis from '../../../dispatcher/dispatcher'; +import { Action } from '../../../dispatcher/actions'; +import { RightPanelPhases } from '../../../stores/RightPanelStorePhases'; import { aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu } from '../../structures/ContextMenu'; import { isContentActionable, canEditContent } from '../../../utils/EventUtils'; import RoomContext from "../../../contexts/RoomContext"; @@ -170,6 +172,17 @@ export default class MessageActionBar extends React.PureComponent { }); }; + onThreadClick = () => { + dis.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.ThreadView, + allowClose: false, + refireParams: { + event: this.props.mxEvent, + } + }); + } + onEditClick = (ev) => { dis.dispatch({ action: 'edit_event', @@ -254,12 +267,20 @@ export default class MessageActionBar extends React.PureComponent { // The only catch is we do the reply button first so that we can make sure the react // button is the very first button without having to do length checks for `splice()`. if (this.context.canReply) { - toolbarOpts.splice(0, 0, ); + toolbarOpts.splice(0, 0, <> + + + ); } if (this.context.canReact) { toolbarOpts.splice(0, 0, Date: Tue, 17 Aug 2021 10:38:09 +0100 Subject: [PATCH 04/16] Adapt threading UI to new backend --- src/components/structures/RightPanel.tsx | 13 ++- src/components/structures/RoomView.tsx | 5 +- src/components/structures/ThreadPanel.tsx | 106 ++++++++++++++++++ src/components/structures/ThreadView.tsx | 22 +++- .../views/right_panel/RoomSummaryCard.tsx | 10 ++ src/i18n/strings/en_EN.json | 1 + src/stores/RightPanelStorePhases.ts | 1 + 7 files changed, 150 insertions(+), 8 deletions(-) create mode 100644 src/components/structures/ThreadPanel.tsx diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index f2bd7db0f3..85d3013a58 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -46,17 +46,20 @@ import UserInfo from "../views/right_panel/UserInfo"; import ThirdPartyMemberInfo from "../views/rooms/ThirdPartyMemberInfo"; import FilePanel from "./FilePanel"; import ThreadView from "./ThreadView"; +import ThreadPanel from "./ThreadPanel"; import NotificationPanel from "./NotificationPanel"; import ResizeNotifier from "../../utils/ResizeNotifier"; import PinnedMessagesCard from "../views/right_panel/PinnedMessagesCard"; import { throttle } from 'lodash'; import SpaceStore from "../../stores/SpaceStore"; +import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; interface IProps { room?: Room; // if showing panels for a given room, this is set groupId?: string; // if showing panels for a given group, this is set user?: User; // used if we know the user ahead of opening the panel resizeNotifier: ResizeNotifier; + permalinkCreator?: RoomPermalinkCreator; } interface IState { @@ -315,7 +318,15 @@ export default class RightPanel extends React.Component { roomId={roomId} resizeNotifier={this.props.resizeNotifier} onClose={this.onClose} - mxEvent={this.state.event} />; + mxEvent={this.state.event} + permalinkCreator={this.props.permalinkCreator} />; + break; + + case RightPanelPhases.ThreadPanel: + panel = ; break; case RightPanelPhases.RoomSummary: diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 474b99262d..baf557ee18 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -2052,7 +2052,10 @@ export default class RoomView extends React.Component { const showRightPanel = this.state.room && this.state.showRightPanel; const rightPanel = showRightPanel - ? + ? : null; const timelineClasses = classNames("mx_RoomView_timeline", { diff --git a/src/components/structures/ThreadPanel.tsx b/src/components/structures/ThreadPanel.tsx new file mode 100644 index 0000000000..c99246ccda --- /dev/null +++ b/src/components/structures/ThreadPanel.tsx @@ -0,0 +1,106 @@ +/* +Copyright 2016 OpenMarket Ltd +Copyright 2019 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import { MatrixEvent, Room } from 'matrix-js-sdk/src'; + +import BaseCard from "../views/right_panel/BaseCard"; +import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; +import { replaceableComponent } from "../../utils/replaceableComponent"; +import { MatrixClientPeg } from '../../MatrixClientPeg'; + +import ResizeNotifier from '../../utils/ResizeNotifier'; +import EventTile from '../views/rooms/EventTile'; +import { Thread } from '../../../../matrix-js-sdk/src/models/thread'; + +interface IProps { + roomId: string; + onClose: () => void; + resizeNotifier: ResizeNotifier; +} + +interface IState { + threads?: Thread[]; +} + +/* + * Component which shows the filtered file using a TimelinePanel + */ +@replaceableComponent("structures.ThreadView") +class ThreadView extends React.Component { + private room: Room; + + constructor(props: IProps) { + super(props); + this.room = MatrixClientPeg.get().getRoom(this.props.roomId); + } + + public componentDidMount(): void { + this.room.on("Thread.update", this.onThreadEventReceived); + this.room.on("Thread.ready", this.onThreadEventReceived); + this.updateThreads(() => { + this.state.threads.forEach(thread => { + if (!thread.ready) { + thread.fetchReplyChain(); + } + }); + }); + } + + public componentWillUnmount(): void { + this.room.removeListener("Thread.update", this.onThreadEventReceived); + this.room.removeListener("Thread.ready", this.onThreadEventReceived); + } + + public onThreadEventReceived = () => this.updateThreads(); + + public updateThreads = (callback?: () => void): void => { + this.setState({ + threads: this.room.getThreads(), + }, callback); + }; + + public renderEventTile(event: MatrixEvent): JSX.Element { + return ; + } + + public render() { + return ( + + { + this.state?.threads.map((thread: Thread) => { + if (thread.ready) { + return this.renderEventTile(thread.rootEvent); + } + }) + } + + ); + } +} + +export default ThreadView; diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index 8b196cb4f0..0f9d499884 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -26,12 +26,14 @@ import { MatrixClientPeg } from '../../MatrixClientPeg'; import ResizeNotifier from '../../utils/ResizeNotifier'; import EventTile from '../views/rooms/EventTile'; import MessageComposer from '../views/rooms/MessageComposer'; +import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; interface IProps { roomId: string; onClose: () => void; resizeNotifier: ResizeNotifier; mxEvent: MatrixEvent; + permalinkCreator?: RoomPermalinkCreator; } interface IState { @@ -44,9 +46,18 @@ interface IState { class ThreadView extends React.Component { state = {}; - public componentDidMount(): void {} + public componentDidMount(): void { + // this.props.mxEvent.getThread().on("Thread.update", this.updateThread); + this.props.mxEvent.getThread().once("Thread.ready", this.updateThread); + } - public componentWillUnmount(): void {} + public componentWillUnmount(): void { + this.props.mxEvent.getThread().removeListener("Thread.update", this.updateThread); + } + + updateThread = () => { + this.forceUpdate(); + }; public renderEventTile(event: MatrixEvent): JSX.Element { return { } public render() { - const client = MatrixClientPeg.get(); - const room = client.getRoom(this.props.roomId); - const thread = room.getThread(this.props.mxEvent.getId()); + const thread = this.props.mxEvent.getThread(); + const room = MatrixClientPeg.get().getRoom(this.props.roomId); return ( { room={room} resizeNotifier={this.props.resizeNotifier} replyToEvent={this.props.mxEvent} - permalinkCreator={null} + permalinkCreator={this.props.permalinkCreator} /> ); diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 047448d925..eb3d7499f4 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -220,6 +220,13 @@ const onRoomFilesClick = () => { }); }; +const onRoomThreadsClick = () => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.ThreadPanel, + }); +}; + const onRoomSettingsClick = () => { defaultDispatcher.dispatch({ action: "open_room_settings" }); }; @@ -273,6 +280,9 @@ const RoomSummaryCard: React.FC = ({ room, onClose }) => { + diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ca55f45619..ec28be664b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1790,6 +1790,7 @@ "%(count)s people|other": "%(count)s people", "%(count)s people|one": "%(count)s person", "Show files": "Show files", + "Show threads": "Show threads", "Share room": "Share room", "Room settings": "Room settings", "Trusted": "Trusted", diff --git a/src/stores/RightPanelStorePhases.ts b/src/stores/RightPanelStorePhases.ts index 8f41b17e47..96a585b676 100644 --- a/src/stores/RightPanelStorePhases.ts +++ b/src/stores/RightPanelStorePhases.ts @@ -40,6 +40,7 @@ export enum RightPanelPhases { // Thread stuff ThreadView = "ThreadView", + ThreadPanel = "ThreadPanel", } // These are the phases that are safe to persist (the ones that don't require additional From d1dbfbd014094554a6d3e25e37a5ff33708708f1 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 17 Aug 2021 11:10:02 +0100 Subject: [PATCH 05/16] hide thread events from the timeline --- res/css/views/rooms/_EventTile.scss | 30 +++++++++++++++ res/img/element-icons/message/thread.svg | 4 ++ src/components/structures/MessagePanel.tsx | 7 ++++ src/components/structures/ThreadView.tsx | 29 +++++++++----- src/components/structures/TimelinePanel.tsx | 4 ++ src/components/views/rooms/EventTile.tsx | 38 +++++++++++++++++++ .../views/rooms/MessageComposer.tsx | 9 ++++- 7 files changed, 110 insertions(+), 11 deletions(-) create mode 100644 res/img/element-icons/message/thread.svg diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 1c9d8e87d9..1adfc0cde3 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -670,3 +670,33 @@ $hover-select-border: 4px; margin-right: 0; } } + + +.mx_ThreadView { + + display: flex; + flex-direction: column; + + .mx_ThreadView_List { + flex: 1; + overflow: scroll; + } + + .mx_EventTile_roomName { + display: none; + } + + .mx_EventTile_line { + padding-left: 0 !important; + order: 10 !important; + } + .mx_EventTile { + width: 100%; + display: flex; + flex-direction: column; + border-bottom: 1px solid #888; + margin-top: 0; + padding-bottom: 5px; + margin-bottom: 5px; + } +} diff --git a/res/img/element-icons/message/thread.svg b/res/img/element-icons/message/thread.svg new file mode 100644 index 0000000000..b4a7cc0066 --- /dev/null +++ b/res/img/element-icons/message/thread.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index 1691d90651..15275328a6 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -173,6 +173,8 @@ interface IProps { onUnfillRequest?(backwards: boolean, scrollToken: string): void; getRelationsForEvent?(eventId: string, relationType: string, eventType: string): Relations; + + hideThreadedMessages?: boolean; } interface IState { @@ -443,6 +445,11 @@ export default class MessagePanel extends React.Component { // Always show highlighted event if (this.props.highlightedEventId === mxEv.getId()) return true; + const threadingEnabled = SettingsStore.getValue("feature_threading"); + if (threadingEnabled && mxEv.replyEventId && this.props.hideThreadedMessages === true) { + return false; + } + return !shouldHideEvent(mxEv, this.context); } diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index 0f9d499884..f34be87c95 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -24,9 +24,11 @@ import { replaceableComponent } from "../../utils/replaceableComponent"; import { MatrixClientPeg } from '../../MatrixClientPeg'; import ResizeNotifier from '../../utils/ResizeNotifier'; -import EventTile from '../views/rooms/EventTile'; +import EventTile, { TileShape } from '../views/rooms/EventTile'; import MessageComposer from '../views/rooms/MessageComposer'; import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; +import { Layout } from '../../settings/Layout'; +import TimelinePanel from './TimelinePanel'; interface IProps { roomId: string; @@ -37,6 +39,7 @@ interface IProps { } interface IState { + replyToEvent?: MatrixEvent; } /* @@ -65,6 +68,7 @@ class ThreadView extends React.Component { mxEvent={event} enableFlair={false} showReadReceipts={false} + tileShape={TileShape.FileGrid} as="div" />; } @@ -77,19 +81,24 @@ class ThreadView extends React.Component { className="mx_ThreadView" onClose={this.props.onClose} previousPhase={RightPanelPhases.RoomSummary} + withoutScrollContainer={true} > - { this.renderEventTile(this.props.mxEvent) } - - { thread && ( - thread.eventTimeline.map((event: MatrixEvent) => { - return this.renderEventTile(event); - }) - ) } - + empty} + alwaysShowTimestamps={true} + layout={Layout.Group} + hideThreadedMessages={false} + /> diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index f62676a4fc..e5fa6967dc 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -126,6 +126,8 @@ interface IProps { // callback which is called when we wish to paginate the timeline window. onPaginationRequest?(timelineWindow: TimelineWindow, direction: string, size: number): Promise; + + hideThreadedMessages?: boolean; } interface IState { @@ -214,6 +216,7 @@ class TimelinePanel extends React.Component { timelineCap: Number.MAX_VALUE, className: 'mx_RoomView_messagePanel', sendReadReceiptOnLoad: true, + hideThreadedMessages: true, }; private lastRRSentEventId: string = undefined; @@ -1511,6 +1514,7 @@ class TimelinePanel extends React.Component { showReactions={this.props.showReactions} layout={this.props.layout} enableFlair={SettingsStore.getValue(UIFeature.Flair)} + hideThreadedMessages={this.props.hideThreadedMessages} /> ); } diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 884d004551..30355e55bf 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -55,6 +55,7 @@ import ReadReceiptMarker from "./ReadReceiptMarker"; import MessageActionBar from "../messages/MessageActionBar"; import ReactionsRow from '../messages/ReactionsRow'; import { getEventDisplayInfo } from '../../../utils/EventUtils'; +import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; const eventTileTypes = { [EventType.RoomMessage]: 'messages.MessageEvent', @@ -299,6 +300,9 @@ interface IProps { // whether or not to display the sender hideSender?: boolean; + + // whether or not to display thread info + showThreadInfo?: boolean; } interface IState { @@ -451,6 +455,7 @@ export default class EventTile extends React.Component { client.on("Room.receipt", this.onRoomReceipt); this.isListeningForReceipts = true; } + this.props.mxEvent.on("Thread.update", this.forceUpdate); } // TODO: [REACT-WARNING] Replace with appropriate lifecycle event @@ -491,6 +496,38 @@ export default class EventTile extends React.Component { } } + private renderThreadInfo(): React.ReactNode { + const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); + const thread = room.getThread(this.props.mxEvent.getId()); + if (!thread || this.props.showThreadInfo === false) { + return null; + } + + const avatars = Array.from(thread.participants).map((mxId: string) => { + const member = room.getMember(mxId); + return ; + }); + + return ( +
{ + dis.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.ThreadView, + refireParams: { + event: this.props.mxEvent, + }, + }); + }} + > + + { avatars } + + { thread.length } { thread.length === 1 ? 'reply' : 'replies' } +
+ ); + } + private onRoomReceipt = (ev, room) => { // ignore events for other rooms const tileRoom = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); @@ -1167,6 +1204,7 @@ export default class EventTile extends React.Component { { keyRequestInfo } { actionBar } { this.props.layout === Layout.IRC && (reactionsRow) } + { this.renderThreadInfo() } { this.props.layout !== Layout.IRC && (reactionsRow) } { msgOption } diff --git a/src/components/views/rooms/MessageComposer.tsx b/src/components/views/rooms/MessageComposer.tsx index 8455e9aa11..bd53c9566a 100644 --- a/src/components/views/rooms/MessageComposer.tsx +++ b/src/components/views/rooms/MessageComposer.tsx @@ -183,6 +183,7 @@ interface IProps { resizeNotifier: ResizeNotifier; permalinkCreator: RoomPermalinkCreator; replyToEvent?: MatrixEvent; + showReplyPreview?: boolean; e2eStatus?: E2EStatus; } @@ -201,6 +202,10 @@ export default class MessageComposer extends React.Component { private messageComposerInput: SendMessageComposer; private voiceRecordingButton: VoiceRecordComposerTile; + static defaultProps = { + showReplyPreview: true, + }; + constructor(props) { super(props); VoiceRecordingStore.instance.on(UPDATE_EVENT, this.onVoiceStoreUpdate); @@ -454,7 +459,9 @@ export default class MessageComposer extends React.Component {
{ recordingTooltip }
- + { this.props.showReplyPreview && ( + + ) }
{ controls }
From 95f4513bd21506665646dd5bfca230add77398f6 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 17 Aug 2021 17:42:47 +0100 Subject: [PATCH 06/16] Make UI respond to thread events --- res/css/views/messages/_MessageActionBar.scss | 4 ++-- .../views/right_panel/_RoomSummaryCard.scss | 4 ++++ src/components/structures/MessagePanel.tsx | 1 + src/components/structures/ThreadPanel.tsx | 7 ------ src/components/structures/ThreadView.tsx | 23 +++++++++++++++---- .../views/right_panel/RoomSummaryCard.tsx | 2 +- src/components/views/rooms/EventTile.tsx | 20 +++++++++++++--- 7 files changed, 44 insertions(+), 17 deletions(-) diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index 509ded8ee8..3e5ab87ca9 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -89,11 +89,11 @@ limitations under the License. } .mx_MessageActionBar_replyButton::after { - mask-image: url('$(res)/img/element-icons/room/message-bar/reply.svg'); + mask-image: url('$(res)/img/element-icons/room/files.svg'); } .mx_MessageActionBar_threadButton::after { - mask-image: url('$(res)/img/element-icons/room/files.svg'); + mask-image: url('$(res)/img/element-icons/message/thread.svg'); } .mx_MessageActionBar_editButton::after { diff --git a/res/css/views/right_panel/_RoomSummaryCard.scss b/res/css/views/right_panel/_RoomSummaryCard.scss index dc7804d072..d93e593679 100644 --- a/res/css/views/right_panel/_RoomSummaryCard.scss +++ b/res/css/views/right_panel/_RoomSummaryCard.scss @@ -232,6 +232,10 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/room/files.svg'); } +.mx_RoomSummaryCard_icon_threads::before { + mask-image: url('$(res)/img/element-icons/message/thread.svg'); +} + .mx_RoomSummaryCard_icon_share::before { mask-image: url('$(res)/img/element-icons/room/share.svg'); } diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index 15275328a6..fd716041c2 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -267,6 +267,7 @@ export default class MessagePanel extends React.Component { componentDidMount() { this.calculateRoomMembersCount(); this.props.room?.on("RoomState.members", this.calculateRoomMembersCount); + this.props.room?.getThreads().forEach(thread => thread.fetchReplyChain()); this.isMounted = true; } diff --git a/src/components/structures/ThreadPanel.tsx b/src/components/structures/ThreadPanel.tsx index c99246ccda..f0b85e9723 100644 --- a/src/components/structures/ThreadPanel.tsx +++ b/src/components/structures/ThreadPanel.tsx @@ -52,13 +52,6 @@ class ThreadView extends React.Component { public componentDidMount(): void { this.room.on("Thread.update", this.onThreadEventReceived); this.room.on("Thread.ready", this.onThreadEventReceived); - this.updateThreads(() => { - this.state.threads.forEach(thread => { - if (!thread.ready) { - thread.fetchReplyChain(); - } - }); - }); } public componentWillUnmount(): void { diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index f34be87c95..7767e5929e 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -29,6 +29,7 @@ import MessageComposer from '../views/rooms/MessageComposer'; import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; import { Layout } from '../../settings/Layout'; import TimelinePanel from './TimelinePanel'; +import { Thread } from '../../../../matrix-js-sdk/src/models/thread'; interface IProps { roomId: string; @@ -40,6 +41,7 @@ interface IProps { interface IState { replyToEvent?: MatrixEvent; + thread?: Thread; } /* @@ -50,16 +52,29 @@ class ThreadView extends React.Component { state = {}; public componentDidMount(): void { - // this.props.mxEvent.getThread().on("Thread.update", this.updateThread); - this.props.mxEvent.getThread().once("Thread.ready", this.updateThread); + this.setupThread(); } public componentWillUnmount(): void { - this.props.mxEvent.getThread().removeListener("Thread.update", this.updateThread); + if (this.state.thread) { + this.state.thread.removeListener("Thread.update", this.updateThread); + this.state.thread.removeListener("Thread.ready", this.updateThread); + } } + setupThread = () => { + const thread = this.props.mxEvent.getThread(); + if (thread) { + thread.on("Thread.update", this.updateThread); + thread.once("Thread.ready", this.updateThread); + this.updateThread(); + } + }; + updateThread = () => { - this.forceUpdate(); + this.setState({ + thread: this.props.mxEvent.getThread(), + }); }; public renderEventTile(event: MatrixEvent): JSX.Element { diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index eb3d7499f4..a09fabc06d 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -280,7 +280,7 @@ const RoomSummaryCard: React.FC = ({ room, onClose }) => { - - + { SettingsStore.getValue("experimentalThreadSupport") && ( + + ) } diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 6f807f66d6..74d9fc5ca6 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -57,6 +57,7 @@ import ReactionsRow from '../messages/ReactionsRow'; import { getEventDisplayInfo } from '../../../utils/EventUtils'; import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; import { Thread } from '../../../../../matrix-js-sdk/src/models/thread'; +import SettingsStore from "../../../settings/SettingsStore"; const eventTileTypes = { [EventType.RoomMessage]: 'messages.MessageEvent', @@ -461,8 +462,10 @@ export default class EventTile extends React.Component { this.isListeningForReceipts = true; } - this.props.mxEvent.once("Thread.ready", this.updateThread); - this.props.mxEvent.on("Thread.update", this.updateThread); + if (SettingsStore.getValue("experimentalThreadSupport")) { + this.props.mxEvent.once("Thread.ready", this.updateThread); + this.props.mxEvent.on("Thread.update", this.updateThread); + } } private updateThread = (thread) => { @@ -511,6 +514,10 @@ export default class EventTile extends React.Component { } private renderThreadInfo(): React.ReactNode { + if (!SettingsStore.getValue("experimentalThreadSupport")) { + return null; + } + const thread = this.state.thread; const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); if (!thread || this.props.showThreadInfo === false) { From 30a762944af85a7134e3a4829d2f95ab53001d6c Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 20 Aug 2021 12:11:04 +0100 Subject: [PATCH 09/16] Implement a very low fidelity UI for threads --- res/css/views/rooms/_EventTile.scss | 25 ++++++++++++++++++- res/css/views/rooms/_MessageComposer.scss | 14 +++++++++++ src/components/structures/ThreadView.tsx | 1 + .../views/rooms/MessageComposer.tsx | 12 +++++++-- 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 4dd91eb7f2..4b48ec971b 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -681,6 +681,30 @@ $hover-select-border: 4px; display: flex; flex-direction: column; + .mx_ScrollPanel { + margin-top: 20px; + .mx_RoomView_MessageList { + padding: 0; + } + } + + .mx_EventTile_senderDetails { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 6px; + a { + flex: 1; + min-width: none; + max-width: 100%; + display: flex; + align-items: center; + .mx_SenderProfile { + flex: 1; + } + } + } + .mx_ThreadView_List { flex: 1; overflow: scroll; @@ -698,7 +722,6 @@ $hover-select-border: 4px; width: 100%; display: flex; flex-direction: column; - border-bottom: 1px solid #888; margin-top: 0; padding-bottom: 5px; margin-bottom: 5px; diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index 5e2eff4047..b2747f7a9b 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -340,3 +340,17 @@ limitations under the License. height: 50px; } } + +/** + * Unstable compact mode + */ + +.mx_MessageComposer.mx_MessageComposer--compact { + margin-right: 0; + .mx_MessageComposer_wrapper { + padding: 0; + } + .mx_MessageComposer_button:last-child { + margin-right: 0; + } +} diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index 7767e5929e..5d07b7feaa 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -115,6 +115,7 @@ class ThreadView extends React.Component { replyToEvent={thread?.replyToEvent} showReplyPreview={false} permalinkCreator={this.props.permalinkCreator} + compact={true} /> ); diff --git a/src/components/views/rooms/MessageComposer.tsx b/src/components/views/rooms/MessageComposer.tsx index bd53c9566a..fbf3b58570 100644 --- a/src/components/views/rooms/MessageComposer.tsx +++ b/src/components/views/rooms/MessageComposer.tsx @@ -185,6 +185,7 @@ interface IProps { replyToEvent?: MatrixEvent; showReplyPreview?: boolean; e2eStatus?: E2EStatus; + compact?: boolean; } interface IState { @@ -204,6 +205,7 @@ export default class MessageComposer extends React.Component { static defaultProps = { showReplyPreview: true, + compact: false, }; constructor(props) { @@ -367,7 +369,7 @@ export default class MessageComposer extends React.Component { render() { const controls = [ - this.state.me ? : null, + this.state.me && !this.props.compact ? : null, this.props.e2eStatus ? : null, @@ -455,8 +457,14 @@ export default class MessageComposer extends React.Component { />; } + const classes = classNames({ + "mx_MessageComposer": true, + "mx_GroupLayout": true, + "mx_MessageComposer--compact": this.props.compact, + }); + return ( -
+
{ recordingTooltip }
{ this.props.showReplyPreview && ( From 9facb0d963afa4932f5b48ff3f20aa7128bef30a Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 23 Aug 2021 14:44:44 +0100 Subject: [PATCH 10/16] Polish UI --- res/css/views/messages/_MessageActionBar.scss | 2 +- res/css/views/rooms/_EventTile.scss | 4 + src/components/structures/ThreadView.tsx | 88 +++++++++++++------ src/components/views/rooms/EventTile.tsx | 3 +- 4 files changed, 67 insertions(+), 30 deletions(-) diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index 3e5ab87ca9..c2f3c2ebca 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -89,7 +89,7 @@ limitations under the License. } .mx_MessageActionBar_replyButton::after { - mask-image: url('$(res)/img/element-icons/room/files.svg'); + mask-image: url('$(res)/img/element-icons/room/message-bar/reply.svg'); } .mx_MessageActionBar_threadButton::after { diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 4b48ec971b..1bf62d3fb1 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -676,6 +676,10 @@ $hover-select-border: 4px; } +.mx_ThreadInfo:hover { + cursor: pointer; +} + .mx_ThreadView { display: flex; diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index 5d07b7feaa..cac4054dfb 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -30,6 +30,8 @@ import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; import { Layout } from '../../settings/Layout'; import TimelinePanel from './TimelinePanel'; import { Thread } from '../../../../matrix-js-sdk/src/models/thread'; +import dis from "../../dispatcher/dispatcher"; +import { ActionPayload } from '../../dispatcher/payloads'; interface IProps { roomId: string; @@ -49,32 +51,61 @@ interface IState { */ @replaceableComponent("structures.ThreadView") class ThreadView extends React.Component { - state = {}; + private dispatcherRef: string; + + constructor(props: IProps) { + super(props); + this.state = {}; + } public componentDidMount(): void { - this.setupThread(); + this.setupThread(this.props.mxEvent); + this.dispatcherRef = dis.register(this.onAction); } public componentWillUnmount(): void { + this.teardownThread(); + dis.unregister(this.dispatcherRef); + } + + public componentDidUpdate(prevProps) { + if (prevProps.mxEvent !== this.props.mxEvent) { + this.teardownThread(); + this.setupThread(this.props.mxEvent); + } + } + + private onAction = (payload: ActionPayload): void => { + if (payload.phase == RightPanelPhases.ThreadView && payload.event) { + if (payload.event !== this.props.mxEvent) { + this.teardownThread(); + this.setupThread(payload.event); + } + } + }; + + setupThread = (mxEv: MatrixEvent) => { + const thread = mxEv.getThread(); + if (thread) { + thread.on("Thread.update", this.updateThread); + thread.once("Thread.ready", this.updateThread); + this.updateThread(thread); + } + }; + + teardownThread = () => { if (this.state.thread) { this.state.thread.removeListener("Thread.update", this.updateThread); this.state.thread.removeListener("Thread.ready", this.updateThread); } - } - - setupThread = () => { - const thread = this.props.mxEvent.getThread(); - if (thread) { - thread.on("Thread.update", this.updateThread); - thread.once("Thread.ready", this.updateThread); - this.updateThread(); - } }; - updateThread = () => { - this.setState({ - thread: this.props.mxEvent.getThread(), - }); + updateThread = (thread?: Thread) => { + if (thread) { + this.setState({ thread }); + } else { + this.forceUpdate(); + } }; public renderEventTile(event: MatrixEvent): JSX.Element { @@ -89,7 +120,6 @@ class ThreadView extends React.Component { } public render() { - const thread = this.props.mxEvent.getThread(); const room = MatrixClientPeg.get().getRoom(this.props.roomId); return ( { previousPhase={RightPanelPhases.RoomSummary} withoutScrollContainer={true} > - empty
} - alwaysShowTimestamps={true} - layout={Layout.Group} - hideThreadedMessages={false} - /> + { this.state.thread && ( + empty
} + alwaysShowTimestamps={true} + layout={Layout.Group} + hideThreadedMessages={false} + /> + ) } { return (
{ dis.dispatch({ action: Action.SetRightPanelPhase, @@ -544,7 +545,7 @@ export default class EventTile extends React.Component { { avatars } - { thread.length } { thread.length === 1 ? 'reply' : 'replies' } + { thread.length - 1 } { thread.length === 2 ? 'reply' : 'replies' }
); } From ef51a46d247b1ed673532632ac1cfaf5445c46ec Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 23 Aug 2021 14:55:14 +0100 Subject: [PATCH 11/16] Fix linting --- src/components/views/messages/MessageActionBar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index 29212449d1..83e063ab8c 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -180,7 +180,7 @@ export default class MessageActionBar extends React.PureComponent { allowClose: false, refireParams: { event: this.props.mxEvent, - } + }, }); } @@ -282,7 +282,7 @@ export default class MessageActionBar extends React.PureComponent { onClick={this.onThreadClick} key="thread" /> - )} + ) } ); } if (this.context.canReact) { From 34da07f1f9f314b21441e5cf4f2568a00d38f283 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 23 Aug 2021 17:31:23 +0100 Subject: [PATCH 12/16] Pass room to ThreadView over roomId --- src/components/structures/RightPanel.tsx | 2 +- src/components/structures/ThreadPanel.tsx | 2 +- src/components/structures/ThreadView.tsx | 18 +++++++++++++----- src/components/views/rooms/EventTile.tsx | 2 +- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index 85d3013a58..67634c63d2 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -315,7 +315,7 @@ export default class RightPanel extends React.Component { case RightPanelPhases.ThreadView: panel = void; resizeNotifier: ResizeNotifier; mxEvent: MatrixEvent; @@ -73,6 +75,13 @@ class ThreadView extends React.Component { this.teardownThread(); this.setupThread(this.props.mxEvent); } + + if (prevProps.room !== this.props.room) { + dis.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.RoomSummary, + }); + } } private onAction = (payload: ActionPayload): void => { @@ -120,7 +129,6 @@ class ThreadView extends React.Component { } public render() { - const room = MatrixClientPeg.get().getRoom(this.props.roomId); return ( { /> ) } Date: Tue, 24 Aug 2021 09:09:28 +0100 Subject: [PATCH 13/16] PR feedback --- .stylelintrc.js | 1 + res/css/views/rooms/_EventTile.scss | 6 ++++-- res/css/views/rooms/_MessageComposer.scss | 2 ++ src/MatrixClientPeg.ts | 2 +- src/components/structures/MessagePanel.tsx | 7 ++++--- src/components/structures/ThreadPanel.tsx | 18 ++++++------------ src/components/structures/ThreadView.tsx | 19 ++++++------------- .../views/messages/MessageActionBar.js | 2 +- .../views/right_panel/RoomSummaryCard.tsx | 2 +- src/components/views/rooms/EventTile.tsx | 4 ++-- src/settings/Settings.tsx | 2 +- 11 files changed, 29 insertions(+), 36 deletions(-) diff --git a/.stylelintrc.js b/.stylelintrc.js index 0e6de7000f..c044b19a63 100644 --- a/.stylelintrc.js +++ b/.stylelintrc.js @@ -17,6 +17,7 @@ module.exports = { "selector-list-comma-newline-after": null, "at-rule-no-unknown": null, "no-descending-specificity": null, + "no-empty-first-line": true, "scss/at-rule-no-unknown": [true, { // https://github.com/vector-im/element-web/issues/10544 "ignoreAtRules": ["define-mixin"], diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 1bf62d3fb1..0e77f20c49 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -643,6 +643,7 @@ $hover-select-border: 4px; // Remove some of the default tile padding so that the error is centered margin-right: 0; + .mx_EventTile_line { padding-left: 0; margin-right: 0; @@ -675,18 +676,17 @@ $hover-select-border: 4px; } } - .mx_ThreadInfo:hover { cursor: pointer; } .mx_ThreadView { - display: flex; flex-direction: column; .mx_ScrollPanel { margin-top: 20px; + .mx_RoomView_MessageList { padding: 0; } @@ -703,6 +703,7 @@ $hover-select-border: 4px; max-width: 100%; display: flex; align-items: center; + .mx_SenderProfile { flex: 1; } @@ -722,6 +723,7 @@ $hover-select-border: 4px; padding-left: 0 !important; order: 10 !important; } + .mx_EventTile { width: 100%; display: flex; diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index b2747f7a9b..54c250fc2e 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -347,9 +347,11 @@ limitations under the License. .mx_MessageComposer.mx_MessageComposer--compact { margin-right: 0; + .mx_MessageComposer_wrapper { padding: 0; } + .mx_MessageComposer_button:last-child { margin-right: 0; } diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts index d3382a2b5e..7d0ff560b7 100644 --- a/src/MatrixClientPeg.ts +++ b/src/MatrixClientPeg.ts @@ -213,7 +213,7 @@ class MatrixClientPegClass implements IMatrixClientPeg { opts.pendingEventOrdering = PendingEventOrdering.Detached; opts.lazyLoadMembers = true; opts.clientWellKnownPollPeriod = 2 * 60 * 60; // 2 hours - opts.experimentalThreadSupport = SettingsStore.getValue("experimentalThreadSupport"); + opts.experimentalThreadSupport = SettingsStore.getValue("feature_thread"); // Connect the matrix client to the dispatcher and setting handlers MatrixActionCreators.start(this.matrixClient); diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index 49fb50814c..8bf1f5bd5f 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -267,7 +267,7 @@ export default class MessagePanel extends React.Component { componentDidMount() { this.calculateRoomMembersCount(); this.props.room?.on("RoomState.members", this.calculateRoomMembersCount); - if (SettingsStore.getValue("experimentalThreadSupport")) { + if (SettingsStore.getValue("feature_thread")) { this.props.room?.getThreads().forEach(thread => thread.fetchReplyChain()); } this.isMounted = true; @@ -448,8 +448,9 @@ export default class MessagePanel extends React.Component { // Always show highlighted event if (this.props.highlightedEventId === mxEv.getId()) return true; - const threadingEnabled = SettingsStore.getValue("experimentalThreadSupport"); - if (threadingEnabled && mxEv.replyEventId && this.props.hideThreadedMessages === true) { + if (mxEv.replyEventId + && this.props.hideThreadedMessages + && SettingsStore.getValue("feature_thread")) { return false; } diff --git a/src/components/structures/ThreadPanel.tsx b/src/components/structures/ThreadPanel.tsx index 9259dbe13b..047e527f34 100644 --- a/src/components/structures/ThreadPanel.tsx +++ b/src/components/structures/ThreadPanel.tsx @@ -1,6 +1,5 @@ /* -Copyright 2016 OpenMarket Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2021 New Vector Ltd. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -37,11 +36,8 @@ interface IState { threads?: Thread[]; } -/* - * Component which shows the filtered file using a TimelinePanel - */ @replaceableComponent("structures.ThreadView") -class ThreadView extends React.Component { +export default class ThreadPanel extends React.Component { private room: Room; constructor(props: IProps) { @@ -59,15 +55,15 @@ class ThreadView extends React.Component { this.room.removeListener("Thread.ready", this.onThreadEventReceived); } - public onThreadEventReceived = () => this.updateThreads(); + private onThreadEventReceived = () => this.updateThreads(); - public updateThreads = (callback?: () => void): void => { + private updateThreads = (callback?: () => void): void => { this.setState({ threads: this.room.getThreads(), }, callback); }; - public renderEventTile(event: MatrixEvent): JSX.Element { + private renderEventTile(event: MatrixEvent): JSX.Element { return { />; } - public render() { + public render(): JSX.Element { return ( { ); } } - -export default ThreadView; diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index 38b1c8dc08..03609c66d0 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -1,6 +1,5 @@ /* -Copyright 2016 OpenMarket Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2021 New Vector Ltd. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -22,7 +21,6 @@ import { Thread } from 'matrix-js-sdk/src/models/thread'; import BaseCard from "../views/right_panel/BaseCard"; import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; import { replaceableComponent } from "../../utils/replaceableComponent"; -import { MatrixClientPeg } from '../../MatrixClientPeg'; import ResizeNotifier from '../../utils/ResizeNotifier'; import EventTile, { TileShape } from '../views/rooms/EventTile'; @@ -48,11 +46,8 @@ interface IState { thread?: Thread; } -/* - * Component which shows the filtered file using a TimelinePanel - */ @replaceableComponent("structures.ThreadView") -class ThreadView extends React.Component { +export default class ThreadView extends React.Component { private dispatcherRef: string; constructor(props: IProps) { @@ -93,7 +88,7 @@ class ThreadView extends React.Component { } }; - setupThread = (mxEv: MatrixEvent) => { + private setupThread = (mxEv: MatrixEvent) => { const thread = mxEv.getThread(); if (thread) { thread.on("Thread.update", this.updateThread); @@ -102,14 +97,14 @@ class ThreadView extends React.Component { } }; - teardownThread = () => { + private teardownThread = () => { if (this.state.thread) { this.state.thread.removeListener("Thread.update", this.updateThread); this.state.thread.removeListener("Thread.ready", this.updateThread); } }; - updateThread = (thread?: Thread) => { + private updateThread = (thread?: Thread) => { if (thread) { this.setState({ thread }); } else { @@ -128,7 +123,7 @@ class ThreadView extends React.Component { />; } - public render() { + public render(): JSX.Element { return ( { ); } } - -export default ThreadView; diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index 83e063ab8c..cb8ea7a50d 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -275,7 +275,7 @@ export default class MessageActionBar extends React.PureComponent { onClick={this.onReplyClick} key="reply" /> - { SettingsStore.getValue("experimentalThreadSupport") && ( + { SettingsStore.getValue("feature_thread") && ( = ({ room, onClose }) => { - { SettingsStore.getValue("experimentalThreadSupport") && ( + { SettingsStore.getValue("feature_thread") && ( diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 638bef5061..935a349b10 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -462,7 +462,7 @@ export default class EventTile extends React.Component { this.isListeningForReceipts = true; } - if (SettingsStore.getValue("experimentalThreadSupport")) { + if (SettingsStore.getValue("feature_thread")) { this.props.mxEvent.once("Thread.ready", this.updateThread); this.props.mxEvent.on("Thread.update", this.updateThread); } @@ -514,7 +514,7 @@ export default class EventTile extends React.Component { } private renderThreadInfo(): React.ReactNode { - if (!SettingsStore.getValue("experimentalThreadSupport")) { + if (!SettingsStore.getValue("feature_thread")) { return null; } diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 6496231679..2dba4cf55d 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -211,7 +211,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_FEATURE, default: false, }, - "experimentalThreadSupport": { + "feature_thread": { isFeature: true, // Requires a reload as we change an option flag on the `js-sdk` // And the entire sync history needs to be parsed again From bd1aa01b67b489ba831841577d7afadcd0804dad Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 26 Aug 2021 08:19:44 +0100 Subject: [PATCH 14/16] Update copyright and method accessors --- res/css/views/rooms/_EventTile.scss | 1 + src/components/structures/ThreadPanel.tsx | 2 +- src/components/structures/ThreadView.tsx | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 5648a164e4..494f14c9bd 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -697,6 +697,7 @@ $hover-select-border: 4px; align-items: center; gap: 6px; margin-bottom: 6px; + a { flex: 1; min-width: none; diff --git a/src/components/structures/ThreadPanel.tsx b/src/components/structures/ThreadPanel.tsx index 047e527f34..a0bccfdce9 100644 --- a/src/components/structures/ThreadPanel.tsx +++ b/src/components/structures/ThreadPanel.tsx @@ -1,5 +1,5 @@ /* -Copyright 2021 New Vector Ltd. +Copyright 2021 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. diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index 03609c66d0..2e69d7b0f4 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -1,5 +1,5 @@ /* -Copyright 2021 New Vector Ltd. +Copyright 2021 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. @@ -112,7 +112,7 @@ export default class ThreadView extends React.Component { } }; - public renderEventTile(event: MatrixEvent): JSX.Element { + private renderEventTile(event: MatrixEvent): JSX.Element { return Date: Thu, 26 Aug 2021 08:45:00 +0100 Subject: [PATCH 15/16] Remove unused renderEventTile method --- src/components/structures/ThreadView.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index 2e69d7b0f4..951c821d5c 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -112,17 +112,6 @@ export default class ThreadView extends React.Component { } }; - private renderEventTile(event: MatrixEvent): JSX.Element { - return ; - } - public render(): JSX.Element { return ( Date: Fri, 27 Aug 2021 10:38:01 +0100 Subject: [PATCH 16/16] Fix imports --- src/components/structures/ThreadView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index 951c821d5c..a2595debc8 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -23,7 +23,7 @@ import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; import { replaceableComponent } from "../../utils/replaceableComponent"; import ResizeNotifier from '../../utils/ResizeNotifier'; -import EventTile, { TileShape } from '../views/rooms/EventTile'; +import { TileShape } from '../views/rooms/EventTile'; import MessageComposer from '../views/rooms/MessageComposer'; import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; import { Layout } from '../../settings/Layout';