Merge branch 'develop' into fayed/fix-verification-dialog

This commit is contained in:
Faye Duxovni 2021-10-05 08:48:52 -04:00
commit 651e943b31
145 changed files with 5361 additions and 1938 deletions

View file

@ -45,7 +45,7 @@ function getOrCreateContainer(): HTMLDivElement {
const ARIA_MENU_ITEM_ROLES = new Set(["menuitem", "menuitemcheckbox", "menuitemradio"]);
interface IPosition {
export interface IPosition {
top?: number;
bottom?: number;
left?: number;
@ -226,6 +226,11 @@ export class ContextMenu extends React.PureComponent<IProps, IState> {
}
};
private onClick = (ev: React.MouseEvent) => {
// Don't allow clicks to escape the context menu wrapper
ev.stopPropagation();
};
private onKeyDown = (ev: React.KeyboardEvent) => {
// don't let keyboard handling escape the context menu
ev.stopPropagation();
@ -383,6 +388,7 @@ export class ContextMenu extends React.PureComponent<IProps, IState> {
className={classNames("mx_ContextualMenu_wrapper", this.props.wrapperClassName)}
style={{ ...position, ...wrapperStyle }}
onKeyDown={this.onKeyDown}
onClick={this.onClick}
onContextMenu={this.onContextMenuPreventBubbling}
>
<div
@ -430,7 +436,11 @@ export type AboveLeftOf = IPosition & {
// Placement method for <ContextMenu /> to position context menu right-aligned and flowing to the left of elementRect,
// and either above or below: wherever there is more space (maybe this should be aboveOrBelowLeftOf?)
export const aboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None, vPadding = 0): AboveLeftOf => {
export const aboveLeftOf = (
elementRect: DOMRect,
chevronFace = ChevronFace.None,
vPadding = 0,
): AboveLeftOf => {
const menuOptions: IPosition & { chevronFace: ChevronFace } = { chevronFace };
const buttonRight = elementRect.right + window.pageXOffset;

View file

@ -42,7 +42,7 @@ import linkifyMatrix from "../../linkify-matrix";
import * as Lifecycle from '../../Lifecycle';
// LifecycleStore is not used but does listen to and dispatch actions
import '../../stores/LifecycleStore';
import PageTypes from '../../PageTypes';
import PageType from '../../PageTypes';
import createRoom, { IOpts } from "../../createRoom";
import { _t, _td, getCurrentLanguage } from '../../languageHandler';
@ -207,7 +207,7 @@ interface IState {
view: Views;
// What the LoggedInView would be showing if visible
// eslint-disable-next-line camelcase
page_type?: PageTypes;
page_type?: PageType;
// The ID of the room we're viewing. This is either populated directly
// in the case where we view a room by ID or by RoomView when it resolves
// what ID an alias points at.
@ -723,7 +723,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
break;
}
case 'view_my_groups':
this.setPage(PageTypes.MyGroups);
this.setPage(PageType.MyGroups);
this.notifyNewScreen('groups');
break;
case 'view_group':
@ -756,7 +756,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
localStorage.setItem("mx_seenSpacesBeta", "1");
// We just dispatch the page change rather than have to worry about
// what the logic is for each of these branches.
if (this.state.page_type === PageTypes.MyGroups) {
if (this.state.page_type === PageType.MyGroups) {
dis.dispatch({ action: 'view_last_screen' });
} else {
dis.dispatch({ action: 'view_my_groups' });
@ -842,7 +842,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}
};
private setPage(pageType: string) {
private setPage(pageType: PageType) {
this.setState({
page_type: pageType,
});
@ -949,7 +949,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.setState({
view: Views.LOGGED_IN,
currentRoomId: roomInfo.room_id || null,
page_type: PageTypes.RoomView,
page_type: PageType.RoomView,
threepidInvite: roomInfo.threepid_invite,
roomOobData: roomInfo.oob_data,
ready: true,
@ -977,7 +977,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
currentGroupId: groupId,
currentGroupIsNew: payload.group_is_new,
});
this.setPage(PageTypes.GroupView);
this.setPage(PageType.GroupView);
this.notifyNewScreen('group/' + groupId);
}
@ -1020,7 +1020,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
justRegistered,
currentRoomId: null,
});
this.setPage(PageTypes.HomePage);
this.setPage(PageType.HomePage);
this.notifyNewScreen('home');
ThemeController.isLogin = false;
this.themeWatcher.recheck();
@ -1038,7 +1038,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}
this.notifyNewScreen('user/' + userId);
this.setState({ currentUserId: userId });
this.setPage(PageTypes.UserView);
this.setPage(PageType.UserView);
});
}

View file

@ -48,6 +48,8 @@ import Spinner from "../views/elements/Spinner";
import TileErrorBoundary from '../views/messages/TileErrorBoundary';
import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
import EditorStateTransfer from "../../utils/EditorStateTransfer";
import { logger } from 'matrix-js-sdk/src/logger';
import { Action } from '../../dispatcher/actions';
const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
const continuedTypes = [EventType.Sticker, EventType.RoomMessage];
@ -60,7 +62,7 @@ const groupedEvents = [
// check if there is a previous event and it has the same sender as this event
// and the types are the same/is in continuedTypes and the time between them is <= CONTINUATION_MAX_INTERVAL
function shouldFormContinuation(
export function shouldFormContinuation(
prevEvent: MatrixEvent,
mxEvent: MatrixEvent,
showHiddenEvents: boolean,
@ -287,6 +289,15 @@ export default class MessagePanel extends React.Component<IProps, IState> {
ghostReadMarkers,
});
}
const pendingEditItem = this.pendingEditItem;
if (!this.props.editState && this.props.room && pendingEditItem) {
defaultDispatcher.dispatch({
action: Action.EditEvent,
event: this.props.room.findEventById(pendingEditItem),
timelineRenderingType: this.context.timelineRenderingType,
});
}
}
private calculateRoomMembersCount = (): void => {
@ -550,10 +561,14 @@ export default class MessagePanel extends React.Component<IProps, IState> {
return { nextEvent, nextTile };
}
private get roomHasPendingEdit(): string {
return this.props.room && localStorage.getItem(`mx_edit_room_${this.props.room.roomId}`);
private get pendingEditItem(): string | undefined {
try {
return localStorage.getItem(`mx_edit_room_${this.props.room.roomId}_${this.context.timelineRenderingType}`);
} catch (err) {
logger.error(err);
return undefined;
}
}
private getEventTiles(): ReactNode[] {
this.eventNodes = {};
@ -663,13 +678,6 @@ export default class MessagePanel extends React.Component<IProps, IState> {
}
}
if (!this.props.editState && this.roomHasPendingEdit) {
defaultDispatcher.dispatch({
action: "edit_event",
event: this.props.room.findEventById(this.roomHasPendingEdit),
});
}
if (grouper) {
ret.push(...grouper.getTiles());
}

View file

@ -48,8 +48,8 @@ import { Layout } from "../../settings/Layout";
import AccessibleButton from "../views/elements/AccessibleButton";
import RightPanelStore from "../../stores/RightPanelStore";
import { haveTileForEvent } from "../views/rooms/EventTile";
import RoomContext from "../../contexts/RoomContext";
import MatrixClientContext from "../../contexts/MatrixClientContext";
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
import MatrixClientContext, { withMatrixClientHOC, MatrixClientProps } from "../../contexts/MatrixClientContext";
import { E2EStatus, shieldStatusForRoom } from '../../utils/ShieldUtils';
import { Action } from "../../dispatcher/actions";
import { IMatrixClientCreds } from "../../MatrixClientPeg";
@ -78,7 +78,6 @@ import { objectHasDiff } from "../../utils/objects";
import SpaceRoomView from "./SpaceRoomView";
import { IOpts } from "../../createRoom";
import { replaceableComponent } from "../../utils/replaceableComponent";
import UIStore from "../../stores/UIStore";
import EditorStateTransfer from "../../utils/EditorStateTransfer";
import { throttle } from "lodash";
import ErrorDialog from '../views/dialogs/ErrorDialog';
@ -92,6 +91,7 @@ import TopUnreadMessagesBar from "../views/rooms/TopUnreadMessagesBar";
import SpaceStore from "../../stores/SpaceStore";
import { logger } from "matrix-js-sdk/src/logger";
import { EventTimeline } from 'matrix-js-sdk/src/models/event-timeline';
const DEBUG = false;
let debuglog = function(msg: string) {};
@ -103,7 +103,7 @@ if (DEBUG) {
debuglog = logger.log.bind(console);
}
interface IProps {
interface IRoomProps extends MatrixClientProps {
threepidInvite: IThreepidInvite;
oobData?: IOOBData;
@ -114,7 +114,7 @@ interface IProps {
onRegistered?(credentials: IMatrixClientCreds): void;
}
export interface IState {
export interface IRoomState {
room?: Room;
roomId?: string;
roomAlias?: string;
@ -158,7 +158,6 @@ export interface IState {
// used by componentDidUpdate to avoid unnecessary checks
atEndOfLiveTimelineInit: boolean;
showTopUnreadMessagesBar: boolean;
auxPanelMaxHeight?: number;
statusBarVisible: boolean;
// We load this later by asking the js-sdk to suggest a version for us.
// This object is the result of Room#getRecommendedVersion()
@ -189,10 +188,12 @@ export interface IState {
// if it did we don't want the room to be marked as read as soon as it is loaded.
wasContextSwitch?: boolean;
editState?: EditorStateTransfer;
timelineRenderingType: TimelineRenderingType;
liveTimeline?: EventTimeline;
}
@replaceableComponent("structures.RoomView")
export default class RoomView extends React.Component<IProps, IState> {
export class RoomView extends React.Component<IRoomProps, IRoomState> {
private readonly dispatcherRef: string;
private readonly roomStoreToken: EventSubscription;
private readonly rightPanelStoreToken: EventSubscription;
@ -249,6 +250,8 @@ export default class RoomView extends React.Component<IProps, IState> {
showDisplaynameChanges: true,
matrixClientIsReady: this.context && this.context.isInitialSyncComplete(),
dragCounter: 0,
timelineRenderingType: TimelineRenderingType.Room,
liveTimeline: undefined,
};
this.dispatcherRef = dis.register(this.onAction);
@ -338,7 +341,7 @@ export default class RoomView extends React.Component<IProps, IState> {
const roomId = RoomViewStore.getRoomId();
const newState: Pick<IState, any> = {
const newState: Pick<IRoomState, any> = {
roomId,
roomAlias: RoomViewStore.getRoomAlias(),
roomLoading: RoomViewStore.isRoomLoading(),
@ -565,10 +568,6 @@ export default class RoomView extends React.Component<IProps, IState> {
});
window.addEventListener('beforeunload', this.onPageUnload);
if (this.props.resizeNotifier) {
this.props.resizeNotifier.on("middlePanelResized", this.onResize);
}
this.onResize();
}
shouldComponentUpdate(nextProps, nextState) {
@ -656,9 +655,6 @@ export default class RoomView extends React.Component<IProps, IState> {
}
window.removeEventListener('beforeunload', this.onPageUnload);
if (this.props.resizeNotifier) {
this.props.resizeNotifier.removeListener("middlePanelResized", this.onResize);
}
// Remove RoomStore listener
if (this.roomStoreToken) {
@ -817,7 +813,9 @@ export default class RoomView extends React.Component<IProps, IState> {
this.onSearchClick();
break;
case "edit_event": {
case Action.EditEvent: {
// Quit early if we're trying to edit events in wrong rendering context
if (payload.timelineRenderingType !== this.state.timelineRenderingType) return;
const editState = payload.event ? new EditorStateTransfer(payload.event) : null;
this.setState({ editState }, () => {
if (payload.event) {
@ -941,6 +939,10 @@ export default class RoomView extends React.Component<IProps, IState> {
this.updateE2EStatus(room);
this.updatePermissions(room);
this.checkWidgets(room);
this.setState({
liveTimeline: room.getLiveTimeline(),
});
};
private async calculateRecommendedVersion(room: Room) {
@ -1619,28 +1621,6 @@ export default class RoomView extends React.Component<IProps, IState> {
};
}
private onResize = () => {
// It seems flexbox doesn't give us a way to constrain the auxPanel height to have
// a minimum of the height of the video element, whilst also capping it from pushing out the page
// so we have to do it via JS instead. In this implementation we cap the height by putting
// a maxHeight on the underlying remote video tag.
// header + footer + status + give us at least 120px of scrollback at all times.
let auxPanelMaxHeight = UIStore.instance.windowHeight -
(54 + // height of RoomHeader
36 + // height of the status area
51 + // minimum height of the message composer
120); // amount of desired scrollback
// XXX: this is a bit of a hack and might possibly cause the video to push out the page anyway
// but it's better than the video going missing entirely
if (auxPanelMaxHeight < 50) auxPanelMaxHeight = 50;
if (this.state.auxPanelMaxHeight !== auxPanelMaxHeight) {
this.setState({ auxPanelMaxHeight });
}
};
private onStatusBarVisible = () => {
if (this.unmounted || this.state.statusBarVisible) return;
this.setState({ statusBarVisible: true });
@ -1941,11 +1921,8 @@ export default class RoomView extends React.Component<IProps, IState> {
const auxPanel = (
<AuxPanel
room={this.state.room}
fullHeight={false}
userId={this.context.credentials.userId}
maxHeight={this.state.auxPanelMaxHeight}
showApps={this.state.showApps}
onResize={this.onResize}
resizeNotifier={this.props.resizeNotifier}
>
{ aux }
@ -2120,3 +2097,6 @@ export default class RoomView extends React.Component<IProps, IState> {
);
}
}
const RoomViewWithMatrixClient = withMatrixClientHOC(RoomView);
export default RoomViewWithMatrixClient;

View file

@ -277,8 +277,15 @@ export default class ScrollPanel extends React.Component<IProps> {
// fractional values (both too big and too small)
// for scrollTop happen on certain browsers/platforms
// when scrolled all the way down. E.g. Chrome 72 on debian.
// so check difference <= 1;
return Math.abs(sn.scrollHeight - (sn.scrollTop + sn.clientHeight)) <= 1;
//
// We therefore leave a bit of wiggle-room and assume we're at the
// bottom if the unscrolled area is less than one pixel high.
//
// non-standard DPI settings also seem to have effect here and can
// actually lead to scrollTop+clientHeight being *larger* than
// scrollHeight. (observed in element-desktop on Ubuntu 20.04)
//
return sn.scrollHeight - (sn.scrollTop + sn.clientHeight) <= 1;
};
// returns the vertical height in the given direction that can be removed from

View file

@ -446,13 +446,13 @@ export const useSpaceSummary = (space: Room): {
}));
const loadMore = useCallback(async (pageSize?: number) => {
if (!hierarchy.canLoadMore || hierarchy.noSupport) return;
if (loading || !hierarchy.canLoadMore || hierarchy.noSupport) return;
setLoading(true);
await hierarchy.load(pageSize);
setRooms(hierarchy.rooms);
setLoading(false);
}, [hierarchy]);
}, [loading, hierarchy]);
return { loading, rooms, hierarchy, loadMore };
};
@ -648,8 +648,6 @@ const SpaceHierarchy = ({
return <RovingTabIndexProvider onKeyDown={onKeyDown} handleHomeEnd handleUpDown>
{ ({ onKeyDownHandler }) => {
let content: JSX.Element;
let loader: JSX.Element;
if (loading && !rooms.length) {
content = <Spinner />;
} else {
@ -671,19 +669,20 @@ const SpaceHierarchy = ({
}}
/>
</>;
if (hierarchy.canLoadMore) {
loader = <div ref={loaderRef}>
<Spinner />
</div>;
}
} else {
} else if (!hierarchy.canLoadMore) {
results = <div className="mx_SpaceHierarchy_noResults">
<h3>{ _t("No results found") }</h3>
<div>{ _t("You may want to try a different search or check for typos.") }</div>
</div>;
}
let loader: JSX.Element;
if (hierarchy.canLoadMore) {
loader = <div ref={loaderRef}>
<Spinner />
</div>;
}
content = <>
<div className="mx_SpaceHierarchy_listHeader">
<h4>{ query.trim() ? _t("Results") : _t("Rooms and spaces") }</h4>

View file

@ -34,6 +34,8 @@ import { SetRightPanelPhasePayload } from '../../dispatcher/payloads/SetRightPan
import { Action } from '../../dispatcher/actions';
import { MatrixClientPeg } from '../../MatrixClientPeg';
import { E2EStatus } from '../../utils/ShieldUtils';
import EditorStateTransfer from '../../utils/EditorStateTransfer';
import RoomContext, { TimelineRenderingType } from '../../contexts/RoomContext';
interface IProps {
room: Room;
@ -47,10 +49,14 @@ interface IProps {
interface IState {
replyToEvent?: MatrixEvent;
thread?: Thread;
editState?: EditorStateTransfer;
}
@replaceableComponent("structures.ThreadView")
export default class ThreadView extends React.Component<IProps, IState> {
static contextType = RoomContext;
private dispatcherRef: string;
private timelinePanelRef: React.RefObject<TimelinePanel> = React.createRef();
@ -90,6 +96,23 @@ export default class ThreadView extends React.Component<IProps, IState> {
this.setupThread(payload.event);
}
}
switch (payload.action) {
case Action.EditEvent: {
// Quit early if it's not a thread context
if (payload.timelineRenderingType !== TimelineRenderingType.Thread) return;
// Quit early if that's not a thread event
if (payload.event && !payload.event.getThread()) return;
const editState = payload.event ? new EditorStateTransfer(payload.event) : null;
this.setState({ editState }, () => {
if (payload.event) {
this.timelinePanelRef.current?.scrollToEventIfNeeded(payload.event.getId());
}
});
break;
}
default:
break;
}
};
private setupThread = (mxEv: MatrixEvent) => {
@ -124,44 +147,53 @@ export default class ThreadView extends React.Component<IProps, IState> {
public render(): JSX.Element {
return (
<BaseCard
className="mx_ThreadView"
onClose={this.props.onClose}
previousPhase={RightPanelPhases.RoomSummary}
withoutScrollContainer={true}
>
{ this.state.thread && (
<TimelinePanel
ref={this.timelinePanelRef}
showReadReceipts={false} // No RR support in thread's MVP
manageReadReceipts={false} // No RR support in thread's MVP
manageReadMarkers={false} // No RM support in thread's MVP
sendReadReceiptOnLoad={false} // No RR support in thread's MVP
timelineSet={this.state?.thread?.timelineSet}
showUrlPreview={true}
tileShape={TileShape.Notif}
empty={<div>empty</div>}
alwaysShowTimestamps={true}
layout={Layout.Group}
hideThreadedMessages={false}
hidden={false}
showReactions={true}
className="mx_RoomView_messagePanel mx_GroupLayout"
<RoomContext.Provider value={{
...this.context,
timelineRenderingType: TimelineRenderingType.Thread,
liveTimeline: this.state?.thread?.timelineSet?.getLiveTimeline(),
}}>
<BaseCard
className="mx_ThreadView"
onClose={this.props.onClose}
previousPhase={RightPanelPhases.RoomSummary}
withoutScrollContainer={true}
>
{ this.state.thread && (
<TimelinePanel
ref={this.timelinePanelRef}
showReadReceipts={false} // No RR support in thread's MVP
manageReadReceipts={false} // No RR support in thread's MVP
manageReadMarkers={false} // No RM support in thread's MVP
sendReadReceiptOnLoad={false} // No RR support in thread's MVP
timelineSet={this.state?.thread?.timelineSet}
showUrlPreview={true}
tileShape={TileShape.Thread}
empty={<div>empty</div>}
alwaysShowTimestamps={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}
/>
) }
{ this.state?.thread?.timelineSet && (<MessageComposer
room={this.props.room}
resizeNotifier={this.props.resizeNotifier}
replyInThread={true}
replyToEvent={this.state?.thread?.replyToEvent}
showReplyPreview={false}
permalinkCreator={this.props.permalinkCreator}
membersLoaded={true}
/>
) }
<MessageComposer
room={this.props.room}
resizeNotifier={this.props.resizeNotifier}
replyInThread={true}
replyToEvent={this.state?.thread?.replyToEvent}
showReplyPreview={false}
permalinkCreator={this.props.permalinkCreator}
e2eStatus={this.props.e2eStatus}
compact={true}
/>
</BaseCard>
e2eStatus={this.props.e2eStatus}
compact={true}
/>) }
</BaseCard>
</RoomContext.Provider>
);
}
}