@@ -223,7 +225,7 @@ export default class MessageComposer extends React.Component {
this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this);
this._onTombstoneClick = this._onTombstoneClick.bind(this);
this.renderPlaceholderText = this.renderPlaceholderText.bind(this);
-
+ this._dispatcherRef = null;
this.state = {
isQuoting: Boolean(RoomViewStore.getQuotingEvent()),
tombstone: this._getRoomTombstone(),
@@ -232,7 +234,20 @@ export default class MessageComposer extends React.Component {
};
}
+ onAction = (payload) => {
+ if (payload.action === 'reply_to_event') {
+ // add a timeout for the reply preview to be rendered, so
+ // that the ScrollPanel listening to the resizeNotifier can
+ // correctly measure it's new height and scroll down to keep
+ // at the bottom if it already is
+ setTimeout(() => {
+ this.props.resizeNotifier.notifyTimelineHeightChanged();
+ }, 100);
+ }
+ };
+
componentDidMount() {
+ this.dispatcherRef = dis.register(this.onAction);
MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents);
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
this._waitForOwnMember();
@@ -261,6 +276,7 @@ export default class MessageComposer extends React.Component {
if (this._roomStoreToken) {
this._roomStoreToken.remove();
}
+ dis.unregister(this.dispatcherRef);
}
_onRoomStateEvents(ev, state) {
@@ -364,6 +380,7 @@ export default class MessageComposer extends React.Component {
key="controls_input"
room={this.props.room}
placeholder={this.renderPlaceholderText()}
+ resizeNotifier={this.props.resizeNotifier}
permalinkCreator={this.props.permalinkCreator} />,
,
,
@@ -414,6 +431,7 @@ export default class MessageComposer extends React.Component {
return (
+
{ controls }
diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx
index 0d90c04e13..3274e0e49f 100644
--- a/src/components/views/rooms/RoomList.tsx
+++ b/src/components/views/rooms/RoomList.tsx
@@ -43,6 +43,8 @@ import SettingsStore from "../../../settings/SettingsStore";
import CustomRoomTagStore from "../../../stores/CustomRoomTagStore";
import { arrayFastClone, arrayHasDiff } from "../../../utils/arrays";
import { objectShallowClone, objectWithOnly } from "../../../utils/objects";
+import { IconizedContextMenuOption, IconizedContextMenuOptionList } from "../context_menus/IconizedContextMenu";
+import AccessibleButton from "../elements/AccessibleButton";
interface IProps {
onKeyDown: (ev: React.KeyboardEvent) => void;
@@ -81,6 +83,7 @@ interface ITagAesthetics {
sectionLabelRaw?: string;
addRoomLabel?: string;
onAddRoom?: (dispatcher?: Dispatcher
) => void;
+ addRoomContextMenu?: (onFinished: () => void) => React.ReactNode;
isInvite: boolean;
defaultHidden: boolean;
}
@@ -112,9 +115,30 @@ const TAG_AESTHETICS: {
sectionLabel: _td("Rooms"),
isInvite: false,
defaultHidden: false,
- addRoomLabel: _td("Create room"),
- onAddRoom: (dispatcher?: Dispatcher) => {
- (dispatcher || defaultDispatcher).dispatch({action: 'view_create_room'})
+ addRoomLabel: _td("Add room"),
+ addRoomContextMenu: (onFinished: () => void) => {
+ return
+ {
+ e.preventDefault();
+ e.stopPropagation();
+ onFinished();
+ defaultDispatcher.dispatch({action: "view_create_room"});
+ }}
+ />
+ {
+ e.preventDefault();
+ e.stopPropagation();
+ onFinished();
+ defaultDispatcher.fire(Action.ViewRoomDirectory);
+ }}
+ />
+ ;
},
},
[DefaultTagID.LowPriority]: {
@@ -255,6 +279,10 @@ export default class RoomList extends React.PureComponent {
}
};
+ private onExplore = () => {
+ dis.fire(Action.ViewRoomDirectory);
+ };
+
private renderCommunityInvites(): TemporaryTile[] {
// TODO: Put community invites in a more sensible place (not in the room list)
// See https://github.com/vector-im/element-web/issues/14456
@@ -324,6 +352,7 @@ export default class RoomList extends React.PureComponent {
label={aesthetics.sectionLabelRaw ? aesthetics.sectionLabelRaw : _t(aesthetics.sectionLabel)}
onAddRoom={aesthetics.onAddRoom}
addRoomLabel={aesthetics.addRoomLabel ? _t(aesthetics.addRoomLabel) : aesthetics.addRoomLabel}
+ addRoomContextMenu={aesthetics.addRoomContextMenu}
isMinimized={this.props.isMinimized}
onResize={this.props.onResize}
extraBadTilesThatShouldntExist={extraTiles}
@@ -335,6 +364,16 @@ export default class RoomList extends React.PureComponent {
}
public render() {
+ let explorePrompt: JSX.Element;
+ if (RoomListStore.instance.getFirstNameFilterCondition()) {
+ explorePrompt =
+
{_t("Can't see what you’re looking for?")}
+
+ {_t("Explore all public rooms")}
+
+
;
+ }
+
const sublists = this.renderSublists();
return (
@@ -346,7 +385,10 @@ export default class RoomList extends React.PureComponent {
className="mx_RoomList"
role="tree"
aria-label={_t("Rooms")}
- >{sublists}
+ >
+ {sublists}
+ {explorePrompt}
+
)}
);
diff --git a/src/components/views/rooms/RoomListNumResults.tsx b/src/components/views/rooms/RoomListNumResults.tsx
new file mode 100644
index 0000000000..fcac91a56a
--- /dev/null
+++ b/src/components/views/rooms/RoomListNumResults.tsx
@@ -0,0 +1,41 @@
+/*
+Copyright 2020 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, {useState} from "react";
+
+import { _t } from "../../../languageHandler";
+import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
+import {useEventEmitter} from "../../../hooks/useEventEmitter";
+
+const RoomListNumResults: React.FC = () => {
+ const [count, setCount] = useState
(null);
+ useEventEmitter(RoomListStore.instance, LISTS_UPDATE_EVENT, () => {
+ if (RoomListStore.instance.getFirstNameFilterCondition()) {
+ const numRooms = Object.values(RoomListStore.instance.orderedLists).flat(1).length;
+ setCount(numRooms);
+ } else {
+ setCount(null);
+ }
+ });
+
+ if (typeof count !== "number") return null;
+
+ return
+ {_t("%(count)s results", { count })}
+
;
+};
+
+export default RoomListNumResults;
diff --git a/src/components/views/rooms/RoomSublist.tsx b/src/components/views/rooms/RoomSublist.tsx
index b2337c8c22..1e7ba3f77a 100644
--- a/src/components/views/rooms/RoomSublist.tsx
+++ b/src/components/views/rooms/RoomSublist.tsx
@@ -50,6 +50,7 @@ import { arrayFastClone, arrayHasOrderChange } from "../../../utils/arrays";
import { objectExcluding, objectHasDiff } from "../../../utils/objects";
import TemporaryTile from "./TemporaryTile";
import { ListNotificationState } from "../../../stores/notifications/ListNotificationState";
+import IconizedContextMenu from "../context_menus/IconizedContextMenu";
const SHOW_N_BUTTON_HEIGHT = 28; // As defined by CSS
const RESIZE_HANDLE_HEIGHT = 4; // As defined by CSS
@@ -65,6 +66,7 @@ interface IProps {
startAsHidden: boolean;
label: string;
onAddRoom?: () => void;
+ addRoomContextMenu?: (onFinished: () => void) => React.ReactNode;
addRoomLabel: string;
isMinimized: boolean;
tagId: TagID;
@@ -87,6 +89,7 @@ type PartialDOMRect = Pick;
interface IState {
contextMenuPosition: PartialDOMRect;
+ addRoomContextMenuPosition: PartialDOMRect;
isResizing: boolean;
isExpanded: boolean; // used for the for expand of the sublist when the room list is being filtered
height: number;
@@ -112,6 +115,7 @@ export default class RoomSublist extends React.Component {
this.notificationState = RoomNotificationStateStore.instance.getListState(this.props.tagId);
this.state = {
contextMenuPosition: null,
+ addRoomContextMenuPosition: null,
isResizing: false,
isExpanded: this.isBeingFiltered ? this.isBeingFiltered : !this.layout.isCollapsed,
height: 0, // to be fixed in a moment, we need `rooms` to calculate this.
@@ -376,10 +380,21 @@ export default class RoomSublist extends React.Component {
});
};
+ private onAddRoomContextMenu = (ev: React.MouseEvent) => {
+ ev.preventDefault();
+ ev.stopPropagation();
+ const target = ev.target as HTMLButtonElement;
+ this.setState({addRoomContextMenuPosition: target.getBoundingClientRect()});
+ };
+
private onCloseMenu = () => {
this.setState({contextMenuPosition: null});
};
+ private onCloseAddRoomMenu = () => {
+ this.setState({addRoomContextMenuPosition: null});
+ };
+
private onUnreadFirstChanged = async () => {
const isUnreadFirst = RoomListStore.instance.getListOrder(this.props.tagId) === ListAlgorithm.Importance;
const newAlgorithm = isUnreadFirst ? ListAlgorithm.Natural : ListAlgorithm.Importance;
@@ -594,6 +609,18 @@ export default class RoomSublist extends React.Component {
);
+ } else if (this.state.addRoomContextMenuPosition) {
+ contextMenu = (
+