Add right panel chat timeline (#7112)
Co-authored-by: J. Ryan Stinnett <jryans@gmail.com>
This commit is contained in:
parent
f5f1f18007
commit
4cbed99de3
14 changed files with 243 additions and 17 deletions
|
@ -205,6 +205,7 @@
|
||||||
@import "./views/right_panel/_PinnedMessagesCard.scss";
|
@import "./views/right_panel/_PinnedMessagesCard.scss";
|
||||||
@import "./views/right_panel/_RoomSummaryCard.scss";
|
@import "./views/right_panel/_RoomSummaryCard.scss";
|
||||||
@import "./views/right_panel/_ThreadPanel.scss";
|
@import "./views/right_panel/_ThreadPanel.scss";
|
||||||
|
@import "./views/right_panel/_TimelineCard.scss";
|
||||||
@import "./views/right_panel/_UserInfo.scss";
|
@import "./views/right_panel/_UserInfo.scss";
|
||||||
@import "./views/right_panel/_VerificationPanel.scss";
|
@import "./views/right_panel/_VerificationPanel.scss";
|
||||||
@import "./views/right_panel/_WidgetCard.scss";
|
@import "./views/right_panel/_WidgetCard.scss";
|
||||||
|
|
|
@ -144,6 +144,13 @@ $pulse-color: $alert;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_RightPanel_timelineCardButton {
|
||||||
|
&::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/feedback.svg');
|
||||||
|
mask-position: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes mx_RightPanel_indicator_pulse {
|
@keyframes mx_RightPanel_indicator_pulse {
|
||||||
0% {
|
0% {
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
|
|
36
res/css/views/right_panel/_TimelineCard.scss
Normal file
36
res/css/views/right_panel/_TimelineCard.scss
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_TimelineCard {
|
||||||
|
.mx_TimelineCard__header {
|
||||||
|
margin-left: 6px;
|
||||||
|
|
||||||
|
span:first-of-type {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 18px;
|
||||||
|
color: $secondary-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BaseCard_header {
|
||||||
|
margin: 5px 0 9px 0;
|
||||||
|
.mx_BaseCard_close {
|
||||||
|
margin: 8px;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,6 +54,7 @@ import SpaceStore from "../../stores/spaces/SpaceStore";
|
||||||
import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks';
|
import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks';
|
||||||
import { E2EStatus } from '../../utils/ShieldUtils';
|
import { E2EStatus } from '../../utils/ShieldUtils';
|
||||||
import { dispatchShowThreadsPanelEvent } from '../../dispatcher/dispatch-actions/threads';
|
import { dispatchShowThreadsPanelEvent } from '../../dispatcher/dispatch-actions/threads';
|
||||||
|
import TimelineCard from '../views/right_panel/TimelineCard';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
room?: Room; // if showing panels for a given room, this is set
|
room?: Room; // if showing panels for a given room, this is set
|
||||||
|
@ -334,7 +335,13 @@ export default class RightPanel extends React.Component<IProps, IState> {
|
||||||
panel = <PinnedMessagesCard room={this.props.room} onClose={this.onClose} />;
|
panel = <PinnedMessagesCard room={this.props.room} onClose={this.onClose} />;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case RightPanelPhases.Timeline:
|
||||||
|
if (!SettingsStore.getValue("feature_maximised_widgets")) break;
|
||||||
|
panel = <TimelineCard
|
||||||
|
room={this.props.room}
|
||||||
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
|
onClose={this.onClose} />;
|
||||||
|
break;
|
||||||
case RightPanelPhases.FilePanel:
|
case RightPanelPhases.FilePanel:
|
||||||
panel = <FilePanel roomId={roomId} resizeNotifier={this.props.resizeNotifier} onClose={this.onClose} />;
|
panel = <FilePanel roomId={roomId} resizeNotifier={this.props.resizeNotifier} onClose={this.onClose} />;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -96,6 +96,8 @@ import { dispatchShowThreadEvent } from '../../dispatcher/dispatch-actions/threa
|
||||||
import { fetchInitialEvent } from "../../utils/EventUtils";
|
import { fetchInitialEvent } from "../../utils/EventUtils";
|
||||||
import { ComposerType } from "../../dispatcher/payloads/ComposerInsertPayload";
|
import { ComposerType } from "../../dispatcher/payloads/ComposerInsertPayload";
|
||||||
import AppsDrawer from '../views/rooms/AppsDrawer';
|
import AppsDrawer from '../views/rooms/AppsDrawer';
|
||||||
|
import { SetRightPanelPhasePayload } from '../../dispatcher/payloads/SetRightPanelPhasePayload';
|
||||||
|
import { RightPanelPhases } from '../../stores/RightPanelStorePhases';
|
||||||
|
|
||||||
const DEBUG = false;
|
const DEBUG = false;
|
||||||
let debuglog = function(msg: string) {};
|
let debuglog = function(msg: string) {};
|
||||||
|
@ -327,7 +329,15 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
|
|
||||||
private onWidgetLayoutChange = () => {
|
private onWidgetLayoutChange = () => {
|
||||||
if (!this.state.room) return;
|
if (!this.state.room) return;
|
||||||
|
if (WidgetLayoutStore.instance.hasMaximisedWidget(this.state.room)) {
|
||||||
|
// Show chat in right panel when a widget is maximised
|
||||||
|
dis.dispatch<SetRightPanelPhasePayload>({
|
||||||
|
action: Action.SetRightPanelPhase,
|
||||||
|
phase: RightPanelPhases.Timeline,
|
||||||
|
});
|
||||||
|
}
|
||||||
this.checkWidgets(this.state.room);
|
this.checkWidgets(this.state.room);
|
||||||
|
this.checkRightPanel(this.state.room);
|
||||||
};
|
};
|
||||||
|
|
||||||
private checkWidgets = (room) => {
|
private checkWidgets = (room) => {
|
||||||
|
@ -345,6 +355,22 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
: MainSplitContentType.Timeline;
|
: MainSplitContentType.Timeline;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private checkRightPanel = (room) => {
|
||||||
|
// This is a hack to hide the chat. This should not be necessary once the right panel
|
||||||
|
// phase is stored per room. (need to be done after check widget so that mainSplitContentType is updated)
|
||||||
|
if (
|
||||||
|
RightPanelStore.getSharedInstance().roomPanelPhase === RightPanelPhases.Timeline &&
|
||||||
|
this.state.showRightPanel &&
|
||||||
|
!WidgetLayoutStore.instance.hasMaximisedWidget(this.state.room)
|
||||||
|
) {
|
||||||
|
// Two timelines are shown prevent this by hiding the right panel
|
||||||
|
dis.dispatch({
|
||||||
|
action: Action.ToggleRightPanel,
|
||||||
|
type: "room",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private onReadReceiptsChange = () => {
|
private onReadReceiptsChange = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
showReadReceipts: SettingsStore.getValue("showReadReceipts", this.state.roomId),
|
showReadReceipts: SettingsStore.getValue("showReadReceipts", this.state.roomId),
|
||||||
|
@ -1007,6 +1033,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
this.updateE2EStatus(room);
|
this.updateE2EStatus(room);
|
||||||
this.updatePermissions(room);
|
this.updatePermissions(room);
|
||||||
this.checkWidgets(room);
|
this.checkWidgets(room);
|
||||||
|
this.checkRightPanel(room);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
liveTimeline: room.getLiveTimeline(),
|
liveTimeline: room.getLiveTimeline(),
|
||||||
|
@ -2102,6 +2129,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const showRightPanel = this.state.room && this.state.showRightPanel;
|
const showRightPanel = this.state.room && this.state.showRightPanel;
|
||||||
|
|
||||||
const rightPanel = showRightPanel
|
const rightPanel = showRightPanel
|
||||||
? <RightPanel
|
? <RightPanel
|
||||||
room={this.state.room}
|
room={this.state.room}
|
||||||
|
|
|
@ -41,6 +41,8 @@ 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';
|
||||||
|
import RightPanelStore from '../../stores/RightPanelStore';
|
||||||
|
import SettingsStore from '../../settings/SettingsStore';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
@ -203,6 +205,18 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
||||||
event_id: this.state.thread?.id,
|
event_id: this.state.thread?.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let previousPhase = RightPanelStore.getSharedInstance().previousPhase;
|
||||||
|
if (!SettingsStore.getValue("feature_maximised_widgets")) {
|
||||||
|
previousPhase = RightPanelPhases.ThreadPanel;
|
||||||
|
}
|
||||||
|
// Make sure the previous Phase is always one of the two: Timeline or ThreadPanel
|
||||||
|
if (![RightPanelPhases.ThreadPanel, RightPanelPhases.Timeline].includes(previousPhase)) {
|
||||||
|
previousPhase = RightPanelPhases.ThreadPanel;
|
||||||
|
}
|
||||||
|
const previousPhaseLabels = {};
|
||||||
|
previousPhaseLabels[RightPanelPhases.ThreadPanel] = _t("All threads");
|
||||||
|
previousPhaseLabels[RightPanelPhases.Timeline] = _t("Chat");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RoomContext.Provider value={{
|
<RoomContext.Provider value={{
|
||||||
...this.context,
|
...this.context,
|
||||||
|
@ -213,8 +227,8 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
||||||
<BaseCard
|
<BaseCard
|
||||||
className="mx_ThreadView mx_ThreadPanel"
|
className="mx_ThreadView mx_ThreadPanel"
|
||||||
onClose={this.props.onClose}
|
onClose={this.props.onClose}
|
||||||
previousPhase={RightPanelPhases.ThreadPanel}
|
previousPhase={previousPhase}
|
||||||
previousPhaseLabel={_t("All threads")}
|
previousPhaseLabel={previousPhaseLabels[previousPhase]}
|
||||||
withoutScrollContainer={true}
|
withoutScrollContainer={true}
|
||||||
header={this.renderThreadViewHeader()}
|
header={this.renderThreadViewHeader()}
|
||||||
>
|
>
|
||||||
|
|
|
@ -476,10 +476,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private onMessageListScroll = e => {
|
private onMessageListScroll = e => {
|
||||||
if (this.props.onScroll) {
|
this.props.onScroll?.(e);
|
||||||
this.props.onScroll(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.manageReadMarkers) {
|
if (this.props.manageReadMarkers) {
|
||||||
this.doManageReadMarkers();
|
this.doManageReadMarkers();
|
||||||
}
|
}
|
||||||
|
@ -594,7 +591,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
this.setState<null>(updatedState, () => {
|
this.setState<null>(updatedState, () => {
|
||||||
this.messagePanel.current.updateTimelineMinHeight();
|
this.messagePanel.current.updateTimelineMinHeight();
|
||||||
if (callRMUpdated) {
|
if (callRMUpdated) {
|
||||||
this.props.onReadMarkerUpdated();
|
this.props.onReadMarkerUpdated?.();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -40,6 +40,7 @@ import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
||||||
import { IPosition, ChevronFace } from '../../structures/ContextMenu';
|
import { IPosition, ChevronFace } from '../../structures/ContextMenu';
|
||||||
import RoomContext, { TimelineRenderingType } from '../../../contexts/RoomContext';
|
import RoomContext, { TimelineRenderingType } from '../../../contexts/RoomContext';
|
||||||
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
|
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
|
||||||
|
import { WidgetLayoutStore } from '../../../stores/widgets/WidgetLayoutStore';
|
||||||
|
|
||||||
export function canCancel(eventStatus: EventStatus): boolean {
|
export function canCancel(eventStatus: EventStatus): boolean {
|
||||||
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
|
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
|
||||||
|
@ -404,9 +405,12 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
||||||
);
|
);
|
||||||
const isThreadRootEvent = isThread && this.props.mxEvent?.getThread()?.rootEvent === this.props.mxEvent;
|
const isThreadRootEvent = isThread && this.props.mxEvent?.getThread()?.rootEvent === this.props.mxEvent;
|
||||||
|
|
||||||
|
const isMainSplitTimelineShown = !WidgetLayoutStore.instance.hasMaximisedWidget(
|
||||||
|
MatrixClientPeg.get().getRoom(mxEvent.getRoomId()),
|
||||||
|
);
|
||||||
const commonItemsList = (
|
const commonItemsList = (
|
||||||
<IconizedContextMenuOptionList>
|
<IconizedContextMenuOptionList>
|
||||||
{ isThreadRootEvent && <IconizedContextMenuOption
|
{ (isThreadRootEvent && isMainSplitTimelineShown) && <IconizedContextMenuOption
|
||||||
iconClassName="mx_MessageContextMenu_iconViewInRoom"
|
iconClassName="mx_MessageContextMenu_iconViewInRoom"
|
||||||
label={_t("View in room")}
|
label={_t("View in room")}
|
||||||
onClick={this.viewInRoom}
|
onClick={this.viewInRoom}
|
||||||
|
|
|
@ -24,6 +24,8 @@ import { copyPlaintext } from "../../../utils/strings";
|
||||||
import { ChevronFace, ContextMenuTooltipButton } from "../../structures/ContextMenu";
|
import { ChevronFace, ContextMenuTooltipButton } from "../../structures/ContextMenu";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu";
|
import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu";
|
||||||
|
import { WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
|
||||||
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
mxEvent: MatrixEvent;
|
mxEvent: MatrixEvent;
|
||||||
|
@ -80,6 +82,9 @@ const ThreadListContextMenu: React.FC<IProps> = ({ mxEvent, permalinkCreator, on
|
||||||
}
|
}
|
||||||
}, [optionsPosition, onMenuToggle]);
|
}, [optionsPosition, onMenuToggle]);
|
||||||
|
|
||||||
|
const isMainSplitTimelineShown = !WidgetLayoutStore.instance.hasMaximisedWidget(
|
||||||
|
MatrixClientPeg.get().getRoom(mxEvent.getRoomId()),
|
||||||
|
);
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
<ContextMenuTooltipButton
|
<ContextMenuTooltipButton
|
||||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
|
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
|
||||||
|
@ -95,11 +100,12 @@ const ThreadListContextMenu: React.FC<IProps> = ({ mxEvent, permalinkCreator, on
|
||||||
{...contextMenuBelow(optionsPosition)}
|
{...contextMenuBelow(optionsPosition)}
|
||||||
>
|
>
|
||||||
<IconizedContextMenuOptionList>
|
<IconizedContextMenuOptionList>
|
||||||
<IconizedContextMenuOption
|
{ isMainSplitTimelineShown &&
|
||||||
onClick={(e) => viewInRoom(e)}
|
<IconizedContextMenuOption
|
||||||
label={_t("View in room")}
|
onClick={(e) => viewInRoom(e)}
|
||||||
iconClassName="mx_ThreadPanel_viewInRoom"
|
label={_t("View in room")}
|
||||||
/>
|
iconClassName="mx_ThreadPanel_viewInRoom"
|
||||||
|
/> }
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
onClick={(e) => copyLinkToThread(e)}
|
onClick={(e) => copyLinkToThread(e)}
|
||||||
label={_t("Copy link to thread")}
|
label={_t("Copy link to thread")}
|
||||||
|
|
|
@ -402,9 +402,9 @@ export default class AppTile extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private onMaxMinWidgetClick = (): void => {
|
private onMaxMinWidgetClick = (): void => {
|
||||||
const targetContainer =
|
const targetContainer =
|
||||||
WidgetLayoutStore.instance.isInContainer(this.props.room, this.props.app, Container.Center)
|
WidgetLayoutStore.instance.isInContainer(this.props.room, this.props.app, Container.Center)
|
||||||
? Container.Right
|
? Container.Right
|
||||||
: Container.Center;
|
: Container.Center;
|
||||||
WidgetLayoutStore.instance.moveToContainer(this.props.room, this.props.app, targetContainer);
|
WidgetLayoutStore.instance.moveToContainer(this.props.room, this.props.app, targetContainer);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,18 @@ const PinnedMessagesHeaderButton = ({ room, isHighlighted, onClick }) => {
|
||||||
</HeaderButton>;
|
</HeaderButton>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const TimelineCardHeaderButton = ({ room, isHighlighted, onClick }) => {
|
||||||
|
if (!SettingsStore.getValue("feature_maximised_widgets")) return null;
|
||||||
|
|
||||||
|
return <HeaderButton
|
||||||
|
name="timelineCardButton"
|
||||||
|
title={_t("Chat")}
|
||||||
|
isHighlighted={isHighlighted}
|
||||||
|
onClick={onClick}
|
||||||
|
analytics={["Right Panel", "Timeline Panel Button", "click"]}
|
||||||
|
/>;
|
||||||
|
};
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
room?: Room;
|
room?: Room;
|
||||||
}
|
}
|
||||||
|
@ -122,6 +134,9 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
|
||||||
// This toggles for us, if needed
|
// This toggles for us, if needed
|
||||||
this.setPhase(RightPanelPhases.PinnedMessages);
|
this.setPhase(RightPanelPhases.PinnedMessages);
|
||||||
};
|
};
|
||||||
|
private onTimelineCardClicked = () => {
|
||||||
|
this.setPhase(RightPanelPhases.Timeline);
|
||||||
|
};
|
||||||
|
|
||||||
private onThreadsPanelClicked = () => {
|
private onThreadsPanelClicked = () => {
|
||||||
if (RoomHeaderButtons.THREAD_PHASES.includes(this.state.phase)) {
|
if (RoomHeaderButtons.THREAD_PHASES.includes(this.state.phase)) {
|
||||||
|
@ -141,6 +156,11 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
|
||||||
isHighlighted={this.isPhase(RightPanelPhases.PinnedMessages)}
|
isHighlighted={this.isPhase(RightPanelPhases.PinnedMessages)}
|
||||||
onClick={this.onPinnedMessagesClicked}
|
onClick={this.onPinnedMessagesClicked}
|
||||||
/>
|
/>
|
||||||
|
<TimelineCardHeaderButton
|
||||||
|
room={this.props.room}
|
||||||
|
isHighlighted={this.isPhase(RightPanelPhases.Timeline)}
|
||||||
|
onClick={this.onTimelineCardClicked}
|
||||||
|
/>
|
||||||
{ SettingsStore.getValue("feature_thread") && <HeaderButton
|
{ SettingsStore.getValue("feature_thread") && <HeaderButton
|
||||||
name="threadsButton"
|
name="threadsButton"
|
||||||
title={_t("Threads")}
|
title={_t("Threads")}
|
||||||
|
|
103
src/components/views/right_panel/TimelineCard.tsx
Normal file
103
src/components/views/right_panel/TimelineCard.tsx
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
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 { Thread } from 'matrix-js-sdk/src/models/thread';
|
||||||
|
|
||||||
|
import BaseCard from "./BaseCard";
|
||||||
|
|
||||||
|
import ResizeNotifier from '../../../utils/ResizeNotifier';
|
||||||
|
import MessageComposer from '../rooms/MessageComposer';
|
||||||
|
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
|
||||||
|
import { Layout } from '../../../settings/enums/Layout';
|
||||||
|
import TimelinePanel from '../../structures/TimelinePanel';
|
||||||
|
import { E2EStatus } from '../../../utils/ShieldUtils';
|
||||||
|
import EditorStateTransfer from '../../../utils/EditorStateTransfer';
|
||||||
|
import RoomContext from '../../../contexts/RoomContext';
|
||||||
|
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import { replaceableComponent } from '../../../utils/replaceableComponent';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
room: Room;
|
||||||
|
onClose: () => void;
|
||||||
|
resizeNotifier: ResizeNotifier;
|
||||||
|
permalinkCreator?: RoomPermalinkCreator;
|
||||||
|
e2eStatus?: E2EStatus;
|
||||||
|
initialEvent?: MatrixEvent;
|
||||||
|
initialEventHighlighted?: boolean;
|
||||||
|
}
|
||||||
|
interface IState {
|
||||||
|
thread?: Thread;
|
||||||
|
editState?: EditorStateTransfer;
|
||||||
|
replyToEvent?: MatrixEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("structures.TimelineCard")
|
||||||
|
export default class TimelineCard extends React.Component<IProps, IState> {
|
||||||
|
static contextType = RoomContext;
|
||||||
|
|
||||||
|
constructor(props: IProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderTimelineCardHeader = (): JSX.Element => {
|
||||||
|
return <div className="mx_TimelineCard__header">
|
||||||
|
<span>{ _t("Chat") }</span>
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<BaseCard
|
||||||
|
className="mx_ThreadPanel mx_TimelineCard"
|
||||||
|
onClose={this.props.onClose}
|
||||||
|
withoutScrollContainer={true}
|
||||||
|
header={this.renderTimelineCardHeader()}
|
||||||
|
>
|
||||||
|
<TimelinePanel
|
||||||
|
showReadReceipts={false} // TODO: RR's cause issues with limited horizontal space
|
||||||
|
manageReadReceipts={true}
|
||||||
|
manageReadMarkers={false} // No RM support in the TimelineCard
|
||||||
|
sendReadReceiptOnLoad={true}
|
||||||
|
timelineSet={this.props.room.getUnfilteredTimelineSet()}
|
||||||
|
showUrlPreview={true}
|
||||||
|
layout={Layout.Group}
|
||||||
|
hideThreadedMessages={false}
|
||||||
|
hidden={false}
|
||||||
|
showReactions={true}
|
||||||
|
className="mx_RoomView_messagePanel mx_GroupLayout"
|
||||||
|
permalinkCreator={this.props.permalinkCreator}
|
||||||
|
membersLoaded={true}
|
||||||
|
editState={this.state.editState}
|
||||||
|
eventId={this.props.initialEvent?.getId()}
|
||||||
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<MessageComposer
|
||||||
|
room={this.props.room}
|
||||||
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
|
replyToEvent={this.state.replyToEvent}
|
||||||
|
permalinkCreator={this.props.permalinkCreator}
|
||||||
|
e2eStatus={this.props.e2eStatus}
|
||||||
|
compact={true}
|
||||||
|
/>
|
||||||
|
</BaseCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1886,6 +1886,7 @@
|
||||||
"Nothing pinned, yet": "Nothing pinned, yet",
|
"Nothing pinned, yet": "Nothing pinned, yet",
|
||||||
"If you have permissions, open the menu on any message and select <b>Pin</b> to stick them here.": "If you have permissions, open the menu on any message and select <b>Pin</b> to stick them here.",
|
"If you have permissions, open the menu on any message and select <b>Pin</b> to stick them here.": "If you have permissions, open the menu on any message and select <b>Pin</b> to stick them here.",
|
||||||
"Pinned messages": "Pinned messages",
|
"Pinned messages": "Pinned messages",
|
||||||
|
"Chat": "Chat",
|
||||||
"Threads": "Threads",
|
"Threads": "Threads",
|
||||||
"Room Info": "Room Info",
|
"Room Info": "Room Info",
|
||||||
"You can only pin up to %(count)s widgets|other": "You can only pin up to %(count)s widgets",
|
"You can only pin up to %(count)s widgets|other": "You can only pin up to %(count)s widgets",
|
||||||
|
|
|
@ -25,6 +25,7 @@ export enum RightPanelPhases {
|
||||||
RoomSummary = 'RoomSummary',
|
RoomSummary = 'RoomSummary',
|
||||||
Widget = 'Widget',
|
Widget = 'Widget',
|
||||||
PinnedMessages = "PinnedMessages",
|
PinnedMessages = "PinnedMessages",
|
||||||
|
Timeline = "Timeline",
|
||||||
|
|
||||||
Room3pidMemberInfo = 'Room3pidMemberInfo',
|
Room3pidMemberInfo = 'Room3pidMemberInfo',
|
||||||
// Group stuff
|
// Group stuff
|
||||||
|
@ -53,6 +54,7 @@ export const RIGHT_PANEL_PHASES_NO_ARGS = [
|
||||||
RightPanelPhases.RoomMemberList,
|
RightPanelPhases.RoomMemberList,
|
||||||
RightPanelPhases.GroupMemberList,
|
RightPanelPhases.GroupMemberList,
|
||||||
RightPanelPhases.GroupRoomList,
|
RightPanelPhases.GroupRoomList,
|
||||||
|
RightPanelPhases.Timeline,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Subset of phases visible in the Space View
|
// Subset of phases visible in the Space View
|
||||||
|
|
Loading…
Reference in a new issue