Merge branch 'develop' into gsouquet/fix-backdrop-filter
This commit is contained in:
commit
edae9a4844
736 changed files with 8122 additions and 7857 deletions
|
@ -15,14 +15,14 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { HTMLAttributes } from "react";
|
||||
import React, { HTMLAttributes, WheelEvent } from "react";
|
||||
|
||||
interface IProps extends HTMLAttributes<HTMLDivElement> {
|
||||
interface IProps extends Omit<HTMLAttributes<HTMLDivElement>, "onScroll"> {
|
||||
className?: string;
|
||||
onScroll?: () => void;
|
||||
onWheel?: () => void;
|
||||
style?: React.CSSProperties
|
||||
tabIndex?: number,
|
||||
onScroll?: (event: Event) => void;
|
||||
onWheel?: (event: WheelEvent) => void;
|
||||
style?: React.CSSProperties;
|
||||
tabIndex?: number;
|
||||
wrappedRef?: (ref: HTMLDivElement) => void;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ interface IProps {
|
|||
opacity?: number;
|
||||
}
|
||||
|
||||
|
||||
export default class BackdropPanel extends React.PureComponent<IProps> {
|
||||
private canvasRef = createRef<HTMLCanvasElement>();
|
||||
private ctx: CanvasRenderingContext2D;
|
||||
|
@ -33,7 +32,7 @@ export default class BackdropPanel extends React.PureComponent<IProps> {
|
|||
static defaultProps = {
|
||||
blur: "60px",
|
||||
opacity: .15,
|
||||
}
|
||||
};
|
||||
|
||||
public componentDidMount() {
|
||||
this.ctx = this.canvasRef.current.getContext("2d");
|
||||
|
@ -78,7 +77,7 @@ export default class BackdropPanel extends React.PureComponent<IProps> {
|
|||
resultWidth,
|
||||
resultHeight,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
public render() {
|
||||
return <canvas
|
||||
|
|
|
@ -16,13 +16,13 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {CSSProperties, RefObject, useRef, useState} from "react";
|
||||
import React, { CSSProperties, RefObject, useRef, useState } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import classNames from "classnames";
|
||||
|
||||
import {Key} from "../../Keyboard";
|
||||
import {Writeable} from "../../@types/common";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import { Key } from "../../Keyboard";
|
||||
import { Writeable } from "../../@types/common";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import UIStore from "../../stores/UIStore";
|
||||
|
||||
// Shamelessly ripped off Modal.js. There's probably a better way
|
||||
|
@ -371,7 +371,7 @@ export class ContextMenu extends React.PureComponent<IProps, IState> {
|
|||
return (
|
||||
<div
|
||||
className={classNames("mx_ContextualMenu_wrapper", this.props.wrapperClassName)}
|
||||
style={{...position, ...wrapperStyle}}
|
||||
style={{ ...position, ...wrapperStyle }}
|
||||
onKeyDown={this.onKeyDown}
|
||||
onContextMenu={this.onContextMenuPreventBubbling}
|
||||
>
|
||||
|
@ -399,7 +399,7 @@ export const toRightOf = (elementRect: Pick<DOMRect, "right" | "top" | "height">
|
|||
const left = elementRect.right + window.pageXOffset + 3;
|
||||
let top = elementRect.top + (elementRect.height / 2) + window.pageYOffset;
|
||||
top -= chevronOffset + 8; // where 8 is half the height of the chevron
|
||||
return {left, top, chevronOffset};
|
||||
return { left, top, chevronOffset };
|
||||
};
|
||||
|
||||
// Placement method for <ContextMenu /> to position context menu right-aligned and flowing to the left of elementRect,
|
||||
|
@ -498,15 +498,15 @@ export function createMenu(ElementClass, props) {
|
|||
|
||||
ReactDOM.render(menu, getOrCreateContainer());
|
||||
|
||||
return {close: onFinished};
|
||||
return { close: onFinished };
|
||||
}
|
||||
|
||||
// re-export the semantic helper components for simplicity
|
||||
export {ContextMenuButton} from "../../accessibility/context_menu/ContextMenuButton";
|
||||
export {ContextMenuTooltipButton} from "../../accessibility/context_menu/ContextMenuTooltipButton";
|
||||
export {MenuGroup} from "../../accessibility/context_menu/MenuGroup";
|
||||
export {MenuItem} from "../../accessibility/context_menu/MenuItem";
|
||||
export {MenuItemCheckbox} from "../../accessibility/context_menu/MenuItemCheckbox";
|
||||
export {MenuItemRadio} from "../../accessibility/context_menu/MenuItemRadio";
|
||||
export {StyledMenuItemCheckbox} from "../../accessibility/context_menu/StyledMenuItemCheckbox";
|
||||
export {StyledMenuItemRadio} from "../../accessibility/context_menu/StyledMenuItemRadio";
|
||||
export { ContextMenuButton } from "../../accessibility/context_menu/ContextMenuButton";
|
||||
export { ContextMenuTooltipButton } from "../../accessibility/context_menu/ContextMenuTooltipButton";
|
||||
export { MenuGroup } from "../../accessibility/context_menu/MenuGroup";
|
||||
export { MenuItem } from "../../accessibility/context_menu/MenuItem";
|
||||
export { MenuItemCheckbox } from "../../accessibility/context_menu/MenuItemCheckbox";
|
||||
export { MenuItemRadio } from "../../accessibility/context_menu/MenuItemRadio";
|
||||
export { StyledMenuItemCheckbox } from "../../accessibility/context_menu/StyledMenuItemCheckbox";
|
||||
export { StyledMenuItemRadio } from "../../accessibility/context_menu/StyledMenuItemRadio";
|
||||
|
|
|
@ -21,7 +21,7 @@ import * as sdk from '../../index';
|
|||
import dis from '../../dispatcher/dispatcher';
|
||||
import classNames from 'classnames';
|
||||
import * as FormattingUtils from '../../utils/FormattingUtils';
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("structures.CustomRoomTagPanel")
|
||||
class CustomRoomTagPanel extends React.Component {
|
||||
|
@ -34,7 +34,7 @@ class CustomRoomTagPanel extends React.Component {
|
|||
|
||||
componentDidMount() {
|
||||
this._tagStoreToken = CustomRoomTagStore.addListener(() => {
|
||||
this.setState({tags: CustomRoomTagStore.getSortedTags()});
|
||||
this.setState({ tags: CustomRoomTagStore.getSortedTags() });
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ class CustomRoomTagPanel extends React.Component {
|
|||
|
||||
class CustomRoomTagTile extends React.Component {
|
||||
onClick = () => {
|
||||
dis.dispatch({action: 'select_custom_room_tag', tag: this.props.tag.name});
|
||||
dis.dispatch({ action: 'select_custom_room_tag', tag: this.props.tag.name });
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
|
@ -22,7 +22,7 @@ import request from 'browser-request';
|
|||
import { _t } from '../../languageHandler';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import dis from '../../dispatcher/dispatcher';
|
||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||
import { MatrixClientPeg } from '../../MatrixClientPeg';
|
||||
import classnames from 'classnames';
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||
|
|
|
@ -16,37 +16,50 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {Filter} from 'matrix-js-sdk/src/filter';
|
||||
import { Filter } from 'matrix-js-sdk/src/filter';
|
||||
import { EventTimelineSet } from "matrix-js-sdk/src/models/event-timeline-set";
|
||||
import { Direction } from "matrix-js-sdk/src/models/event-timeline";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { Room } from 'matrix-js-sdk/src/models/room';
|
||||
import { TimelineWindow } from 'matrix-js-sdk/src/timeline-window';
|
||||
|
||||
import * as sdk from '../../index';
|
||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||
import { MatrixClientPeg } from '../../MatrixClientPeg';
|
||||
import EventIndexPeg from "../../indexing/EventIndexPeg";
|
||||
import { _t } from '../../languageHandler';
|
||||
import BaseCard from "../views/right_panel/BaseCard";
|
||||
import {RightPanelPhases} from "../../stores/RightPanelStorePhases";
|
||||
import DesktopBuildsNotice, {WarningKind} from "../views/elements/DesktopBuildsNotice";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import { RightPanelPhases } from "../../stores/RightPanelStorePhases";
|
||||
import DesktopBuildsNotice, { WarningKind } from "../views/elements/DesktopBuildsNotice";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
|
||||
import ResizeNotifier from '../../utils/ResizeNotifier';
|
||||
|
||||
interface IProps {
|
||||
roomId: string;
|
||||
onClose: () => void;
|
||||
resizeNotifier: ResizeNotifier;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
timelineSet: EventTimelineSet;
|
||||
}
|
||||
|
||||
/*
|
||||
* Component which shows the filtered file using a TimelinePanel
|
||||
*/
|
||||
@replaceableComponent("structures.FilePanel")
|
||||
class FilePanel extends React.Component {
|
||||
static propTypes = {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class FilePanel extends React.Component<IProps, IState> {
|
||||
// This is used to track if a decrypted event was a live event and should be
|
||||
// added to the timeline.
|
||||
decryptingEvents = new Set();
|
||||
private decryptingEvents = new Set<string>();
|
||||
public noRoom: boolean;
|
||||
|
||||
state = {
|
||||
timelineSet: null,
|
||||
};
|
||||
|
||||
onRoomTimeline = (ev, room, toStartOfTimeline, removed, data) => {
|
||||
private onRoomTimeline = (ev: MatrixEvent, room: Room, toStartOfTimeline: true, removed: true, data: any): void => {
|
||||
if (room?.roomId !== this.props?.roomId) return;
|
||||
if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return;
|
||||
|
||||
|
@ -60,7 +73,7 @@ class FilePanel extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
onEventDecrypted = (ev, err) => {
|
||||
private onEventDecrypted = (ev: MatrixEvent, err?: any): void => {
|
||||
if (ev.getRoomId() !== this.props.roomId) return;
|
||||
const eventId = ev.getId();
|
||||
|
||||
|
@ -70,7 +83,7 @@ class FilePanel extends React.Component {
|
|||
this.addEncryptedLiveEvent(ev);
|
||||
};
|
||||
|
||||
addEncryptedLiveEvent(ev, toStartOfTimeline) {
|
||||
public addEncryptedLiveEvent(ev: MatrixEvent): void {
|
||||
if (!this.state.timelineSet) return;
|
||||
|
||||
const timeline = this.state.timelineSet.getLiveTimeline();
|
||||
|
@ -84,7 +97,7 @@ class FilePanel extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
public async componentDidMount(): Promise<void> {
|
||||
const client = MatrixClientPeg.get();
|
||||
|
||||
await this.updateTimelineSet(this.props.roomId);
|
||||
|
@ -105,7 +118,7 @@ class FilePanel extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
public componentWillUnmount(): void {
|
||||
const client = MatrixClientPeg.get();
|
||||
if (client === null) return;
|
||||
|
||||
|
@ -117,7 +130,7 @@ class FilePanel extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
async fetchFileEventsServer(room) {
|
||||
public async fetchFileEventsServer(room: Room): Promise<EventTimelineSet> {
|
||||
const client = MatrixClientPeg.get();
|
||||
|
||||
const filter = new Filter(client.credentials.userId);
|
||||
|
@ -141,7 +154,11 @@ class FilePanel extends React.Component {
|
|||
return timelineSet;
|
||||
}
|
||||
|
||||
onPaginationRequest = (timelineWindow, direction, limit) => {
|
||||
private onPaginationRequest = (
|
||||
timelineWindow: TimelineWindow,
|
||||
direction: Direction,
|
||||
limit: number,
|
||||
): Promise<boolean> => {
|
||||
const client = MatrixClientPeg.get();
|
||||
const eventIndex = EventIndexPeg.get();
|
||||
const roomId = this.props.roomId;
|
||||
|
@ -159,7 +176,7 @@ class FilePanel extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
async updateTimelineSet(roomId: string) {
|
||||
public async updateTimelineSet(roomId: string): Promise<void> {
|
||||
const client = MatrixClientPeg.get();
|
||||
const room = client.getRoom(roomId);
|
||||
const eventIndex = EventIndexPeg.get();
|
||||
|
@ -195,7 +212,7 @@ class FilePanel extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
return <BaseCard
|
||||
className="mx_FilePanel mx_RoomView_messageListWrapper"
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("structures.GenericErrorPage")
|
||||
export default class GenericErrorPage extends React.PureComponent {
|
||||
|
|
|
@ -91,12 +91,12 @@ class GroupFilterPanel extends React.Component {
|
|||
onClick = e => {
|
||||
// only dispatch if its not a no-op
|
||||
if (this.state.selectedTags.length > 0) {
|
||||
dis.dispatch({action: 'deselect_tags'});
|
||||
dis.dispatch({ action: 'deselect_tags' });
|
||||
}
|
||||
};
|
||||
|
||||
onClearFilterClick = ev => {
|
||||
dis.dispatch({action: 'deselect_tags'});
|
||||
dis.dispatch({ action: 'deselect_tags' });
|
||||
};
|
||||
|
||||
renderGlobalIcon() {
|
||||
|
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||
import { MatrixClientPeg } from '../../MatrixClientPeg';
|
||||
import * as sdk from '../../index';
|
||||
import dis from '../../dispatcher/dispatcher';
|
||||
import { getHostingLink } from '../../utils/HostingLink';
|
||||
|
@ -34,13 +34,13 @@ import classnames from 'classnames';
|
|||
import GroupStore from '../../stores/GroupStore';
|
||||
import FlairStore from '../../stores/FlairStore';
|
||||
import { showGroupAddRoomDialog } from '../../GroupAddressPicker';
|
||||
import {makeGroupPermalink, makeUserPermalink} from "../../utils/permalinks/Permalinks";
|
||||
import {Group} from "matrix-js-sdk/src/models/group";
|
||||
import {sleep} from "../../utils/promise";
|
||||
import { makeGroupPermalink, makeUserPermalink } from "../../utils/permalinks/Permalinks";
|
||||
import { Group } from "matrix-js-sdk/src/models/group";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
import RightPanelStore from "../../stores/RightPanelStore";
|
||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||
import {mediaFromMxc} from "../../customisations/Media";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import { mediaFromMxc } from "../../customisations/Media";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
|
||||
const LONG_DESC_PLACEHOLDER = _td(
|
||||
`<h1>HTML for your community's page</h1>
|
||||
|
@ -115,7 +115,7 @@ class CategoryRoomList extends React.Component {
|
|||
{
|
||||
title: _t(
|
||||
"Failed to add the following rooms to the summary of %(groupId)s:",
|
||||
{groupId: this.props.groupId},
|
||||
{ groupId: this.props.groupId },
|
||||
),
|
||||
description: errorList.join(", "),
|
||||
},
|
||||
|
@ -126,12 +126,11 @@ class CategoryRoomList extends React.Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
const addButton = this.props.editing ?
|
||||
(<AccessibleButton className="mx_GroupView_featuredThings_addButton"
|
||||
onClick={this.onAddRoomsToSummaryClicked}
|
||||
>
|
||||
<TintableSvg src={require("../../../res/img/icons-create-room.svg")} width="64" height="64" />
|
||||
<img src={require("../../../res/img/icons-create-room.svg")} width="64" height="64" />
|
||||
<div className="mx_GroupView_featuredThings_addButton_label">
|
||||
{ _t('Add a Room') }
|
||||
</div>
|
||||
|
@ -195,9 +194,9 @@ class FeaturedRoom extends React.Component {
|
|||
{
|
||||
title: _t(
|
||||
"Failed to remove the room from the summary of %(groupId)s",
|
||||
{groupId: this.props.groupId},
|
||||
{ groupId: this.props.groupId },
|
||||
),
|
||||
description: _t("The room '%(roomName)s' could not be removed from the summary.", {roomName}),
|
||||
description: _t("The room '%(roomName)s' could not be removed from the summary.", { roomName }),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
@ -289,7 +288,7 @@ class RoleUserList extends React.Component {
|
|||
{
|
||||
title: _t(
|
||||
"Failed to add the following users to the summary of %(groupId)s:",
|
||||
{groupId: this.props.groupId},
|
||||
{ groupId: this.props.groupId },
|
||||
),
|
||||
description: errorList.join(", "),
|
||||
},
|
||||
|
@ -300,10 +299,9 @@ class RoleUserList extends React.Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
const addButton = this.props.editing ?
|
||||
(<AccessibleButton className="mx_GroupView_featuredThings_addButton" onClick={this.onAddUsersClicked}>
|
||||
<TintableSvg src={require("../../../res/img/icons-create-room.svg")} width="64" height="64" />
|
||||
<img src={require("../../../res/img/icons-create-room.svg")} width="64" height="64" />
|
||||
<div className="mx_GroupView_featuredThings_addButton_label">
|
||||
{ _t('Add a User') }
|
||||
</div>
|
||||
|
@ -361,9 +359,12 @@ class FeaturedUser extends React.Component {
|
|||
{
|
||||
title: _t(
|
||||
"Failed to remove a user from the summary of %(groupId)s",
|
||||
{groupId: this.props.groupId},
|
||||
{ groupId: this.props.groupId },
|
||||
),
|
||||
description: _t(
|
||||
"The user '%(displayName)s' could not be removed from the summary.",
|
||||
{ displayName },
|
||||
),
|
||||
description: _t("The user '%(displayName)s' could not be removed from the summary.", {displayName}),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
@ -470,7 +471,7 @@ export default class GroupView extends React.Component {
|
|||
// Leave settings - the user might have clicked the "Leave" button
|
||||
this._closeSettings();
|
||||
}
|
||||
this.setState({membershipBusy: false});
|
||||
this.setState({ membershipBusy: false });
|
||||
};
|
||||
|
||||
_initGroupStore(groupId, firstInit) {
|
||||
|
@ -491,7 +492,7 @@ export default class GroupView extends React.Component {
|
|||
group_id: groupId,
|
||||
},
|
||||
});
|
||||
dis.dispatch({action: 'require_registration', screen_after: {screen: `group/${groupId}`}});
|
||||
dis.dispatch({ action: 'require_registration', screen_after: { screen: `group/${groupId}` } });
|
||||
willDoOnboarding = true;
|
||||
}
|
||||
if (stateKey === GroupStore.STATE_KEY.Summary) {
|
||||
|
@ -592,7 +593,7 @@ export default class GroupView extends React.Component {
|
|||
};
|
||||
|
||||
_closeSettings = () => {
|
||||
dis.dispatch({action: 'close_settings'});
|
||||
dis.dispatch({ action: 'close_settings' });
|
||||
};
|
||||
|
||||
_onNameChange = (value) => {
|
||||
|
@ -620,7 +621,7 @@ export default class GroupView extends React.Component {
|
|||
const file = ev.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
this.setState({uploadingAvatar: true});
|
||||
this.setState({ uploadingAvatar: true });
|
||||
this._matrixClient.uploadContent(file).then((url) => {
|
||||
const newProfileForm = Object.assign(this.state.profileForm, { avatar_url: url });
|
||||
this.setState({
|
||||
|
@ -632,7 +633,7 @@ export default class GroupView extends React.Component {
|
|||
avatarChanged: true,
|
||||
});
|
||||
}).catch((e) => {
|
||||
this.setState({uploadingAvatar: false});
|
||||
this.setState({ uploadingAvatar: false });
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Failed to upload avatar image", e);
|
||||
Modal.createTrackedDialog('Failed to upload image', '', ErrorDialog, {
|
||||
|
@ -649,7 +650,7 @@ export default class GroupView extends React.Component {
|
|||
};
|
||||
|
||||
_onSaveClick = () => {
|
||||
this.setState({saving: true});
|
||||
this.setState({ saving: true });
|
||||
const savePromise = this.state.isUserPrivileged ? this._saveGroup() : Promise.resolve();
|
||||
savePromise.then((result) => {
|
||||
this.setState({
|
||||
|
@ -688,7 +689,7 @@ export default class GroupView extends React.Component {
|
|||
}
|
||||
|
||||
_onAcceptInviteClick = async () => {
|
||||
this.setState({membershipBusy: true});
|
||||
this.setState({ membershipBusy: true });
|
||||
|
||||
// Wait 500ms to prevent flashing. Do this before sending a request otherwise we risk the
|
||||
// spinner disappearing after we have fetched new group data.
|
||||
|
@ -697,7 +698,7 @@ export default class GroupView extends React.Component {
|
|||
GroupStore.acceptGroupInvite(this.props.groupId).then(() => {
|
||||
// don't reset membershipBusy here: wait for the membership change to come down the sync
|
||||
}).catch((e) => {
|
||||
this.setState({membershipBusy: false});
|
||||
this.setState({ membershipBusy: false });
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Error accepting invite', '', ErrorDialog, {
|
||||
title: _t("Error"),
|
||||
|
@ -707,7 +708,7 @@ export default class GroupView extends React.Component {
|
|||
};
|
||||
|
||||
_onRejectInviteClick = async () => {
|
||||
this.setState({membershipBusy: true});
|
||||
this.setState({ membershipBusy: true });
|
||||
|
||||
// Wait 500ms to prevent flashing. Do this before sending a request otherwise we risk the
|
||||
// spinner disappearing after we have fetched new group data.
|
||||
|
@ -716,7 +717,7 @@ export default class GroupView extends React.Component {
|
|||
GroupStore.leaveGroup(this.props.groupId).then(() => {
|
||||
// don't reset membershipBusy here: wait for the membership change to come down the sync
|
||||
}).catch((e) => {
|
||||
this.setState({membershipBusy: false});
|
||||
this.setState({ membershipBusy: false });
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Error rejecting invite', '', ErrorDialog, {
|
||||
title: _t("Error"),
|
||||
|
@ -727,11 +728,11 @@ export default class GroupView extends React.Component {
|
|||
|
||||
_onJoinClick = async () => {
|
||||
if (this._matrixClient.isGuest()) {
|
||||
dis.dispatch({action: 'require_registration', screen_after: {screen: `group/${this.props.groupId}`}});
|
||||
dis.dispatch({ action: 'require_registration', screen_after: { screen: `group/${this.props.groupId}` } });
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({membershipBusy: true});
|
||||
this.setState({ membershipBusy: true });
|
||||
|
||||
// Wait 500ms to prevent flashing. Do this before sending a request otherwise we risk the
|
||||
// spinner disappearing after we have fetched new group data.
|
||||
|
@ -740,7 +741,7 @@ export default class GroupView extends React.Component {
|
|||
GroupStore.joinGroup(this.props.groupId).then(() => {
|
||||
// don't reset membershipBusy here: wait for the membership change to come down the sync
|
||||
}).catch((e) => {
|
||||
this.setState({membershipBusy: false});
|
||||
this.setState({ membershipBusy: false });
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Error joining room', '', ErrorDialog, {
|
||||
title: _t("Error"),
|
||||
|
@ -773,7 +774,7 @@ export default class GroupView extends React.Component {
|
|||
title: _t("Leave Community"),
|
||||
description: (
|
||||
<span>
|
||||
{ _t("Leave %(groupName)s?", {groupName: this.props.groupId}) }
|
||||
{ _t("Leave %(groupName)s?", { groupName: this.props.groupId }) }
|
||||
{ warnings }
|
||||
</span>
|
||||
),
|
||||
|
@ -782,7 +783,7 @@ export default class GroupView extends React.Component {
|
|||
onFinished: async (confirmed) => {
|
||||
if (!confirmed) return;
|
||||
|
||||
this.setState({membershipBusy: true});
|
||||
this.setState({ membershipBusy: true });
|
||||
|
||||
// Wait 500ms to prevent flashing. Do this before sending a request otherwise we risk the
|
||||
// spinner disappearing after we have fetched new group data.
|
||||
|
@ -791,7 +792,7 @@ export default class GroupView extends React.Component {
|
|||
GroupStore.leaveGroup(this.props.groupId).then(() => {
|
||||
// don't reset membershipBusy here: wait for the membership change to come down the sync
|
||||
}).catch((e) => {
|
||||
this.setState({membershipBusy: false});
|
||||
this.setState({ membershipBusy: false });
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Error leaving community', '', ErrorDialog, {
|
||||
title: _t("Error"),
|
||||
|
@ -855,7 +856,6 @@ export default class GroupView extends React.Component {
|
|||
_getRoomsNode() {
|
||||
const RoomDetailList = sdk.getComponent('rooms.RoomDetailList');
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
||||
const Spinner = sdk.getComponent('elements.Spinner');
|
||||
const TooltipButton = sdk.getComponent('elements.TooltipButton');
|
||||
|
||||
|
@ -871,7 +871,7 @@ export default class GroupView extends React.Component {
|
|||
onClick={this._onAddRoomsClick}
|
||||
>
|
||||
<div className="mx_GroupView_rooms_header_addRow_button">
|
||||
<TintableSvg src={require("../../../res/img/icons-room-add.svg")} width="24" height="24" />
|
||||
<img src={require("../../../res/img/icons-room-add.svg")} width="24" height="24" />
|
||||
</div>
|
||||
<div className="mx_GroupView_rooms_header_addRow_label">
|
||||
{ _t('Add rooms to this community') }
|
||||
|
@ -1336,7 +1336,7 @@ export default class GroupView extends React.Component {
|
|||
if (this.state.error.httpStatus === 404) {
|
||||
return (
|
||||
<div className="mx_GroupView_error">
|
||||
{ _t('Community %(groupId)s not found', {groupId: this.props.groupId}) }
|
||||
{ _t('Community %(groupId)s not found', { groupId: this.props.groupId }) }
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
|
@ -1346,7 +1346,7 @@ export default class GroupView extends React.Component {
|
|||
}
|
||||
return (
|
||||
<div className="mx_GroupView_error">
|
||||
{ _t('Failed to load %(groupId)s', {groupId: this.props.groupId }) }
|
||||
{ _t('Failed to load %(groupId)s', { groupId: this.props.groupId }) }
|
||||
{ extraText }
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -15,29 +15,29 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import {useContext, useState} from "react";
|
||||
import { useContext, useState } from "react";
|
||||
|
||||
import AutoHideScrollbar from './AutoHideScrollbar';
|
||||
import {getHomePageUrl} from "../../utils/pages";
|
||||
import {_t} from "../../languageHandler";
|
||||
import { getHomePageUrl } from "../../utils/pages";
|
||||
import { _t } from "../../languageHandler";
|
||||
import SdkConfig from "../../SdkConfig";
|
||||
import * as sdk from "../../index";
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import {Action} from "../../dispatcher/actions";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import BaseAvatar from "../views/avatars/BaseAvatar";
|
||||
import {OwnProfileStore} from "../../stores/OwnProfileStore";
|
||||
import { OwnProfileStore } from "../../stores/OwnProfileStore";
|
||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||
import {UPDATE_EVENT} from "../../stores/AsyncStore";
|
||||
import {useEventEmitter} from "../../hooks/useEventEmitter";
|
||||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||
import { useEventEmitter } from "../../hooks/useEventEmitter";
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import MiniAvatarUploader, {AVATAR_SIZE} from "../views/elements/MiniAvatarUploader";
|
||||
import MiniAvatarUploader, { AVATAR_SIZE } from "../views/elements/MiniAvatarUploader";
|
||||
import Analytics from "../../Analytics";
|
||||
import CountlyAnalytics from "../../CountlyAnalytics";
|
||||
|
||||
const onClickSendDm = () => {
|
||||
Analytics.trackEvent('home_page', 'button', 'dm');
|
||||
CountlyAnalytics.instance.track("home_page_button", { button: "dm" });
|
||||
dis.dispatch({action: 'view_create_chat'});
|
||||
dis.dispatch({ action: 'view_create_chat' });
|
||||
};
|
||||
|
||||
const onClickExplore = () => {
|
||||
|
@ -49,7 +49,7 @@ const onClickExplore = () => {
|
|||
const onClickNewRoom = () => {
|
||||
Analytics.trackEvent('home_page', 'button', 'create_room');
|
||||
CountlyAnalytics.instance.track("home_page_button", { button: "create_room" });
|
||||
dis.dispatch({action: 'view_create_room'});
|
||||
dis.dispatch({ action: 'view_create_room' });
|
||||
};
|
||||
|
||||
interface IProps {
|
||||
|
@ -117,7 +117,6 @@ const HomePage: React.FC<IProps> = ({ justRegistered = false }) => {
|
|||
</React.Fragment>;
|
||||
}
|
||||
|
||||
|
||||
return <AutoHideScrollbar className="mx_HomePage mx_HomePage_default">
|
||||
<div className="mx_HomePage_default_wrapper">
|
||||
{ introSection }
|
||||
|
|
|
@ -22,7 +22,7 @@ import {
|
|||
import { _t } from "../../languageHandler";
|
||||
import { HostSignupStore } from "../../stores/HostSignupStore";
|
||||
import SdkConfig from "../../SdkConfig";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
onClick?(): void;
|
||||
|
@ -35,7 +35,7 @@ export default class HostSignupAction extends React.PureComponent<IProps, IState
|
|||
private openDialog = async () => {
|
||||
this.props.onClick?.();
|
||||
await HostSignupStore.instance.setHostSignupActive(true);
|
||||
}
|
||||
};
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const hostSignupConfig = SdkConfig.get().hostSignup;
|
||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("structures.IndicatorScrollbar")
|
||||
export default class IndicatorScrollbar extends React.Component {
|
||||
|
@ -70,7 +70,6 @@ export default class IndicatorScrollbar extends React.Component {
|
|||
this._autoHideScrollbar = autoHideScrollbar;
|
||||
}
|
||||
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const prevLen = prevProps && prevProps.children && prevProps.children.length || 0;
|
||||
const curLen = this.props.children && this.props.children.length || 0;
|
||||
|
@ -188,8 +187,8 @@ export default class IndicatorScrollbar extends React.Component {
|
|||
// eslint-disable-next-line no-unused-vars
|
||||
const { children, trackHorizontalOverflow, verticalScrollsHorizontally, ...otherProps } = this.props;
|
||||
|
||||
const leftIndicatorStyle = {left: this.state.leftIndicatorOffset};
|
||||
const rightIndicatorStyle = {right: this.state.rightIndicatorOffset};
|
||||
const leftIndicatorStyle = { left: this.state.leftIndicatorOffset };
|
||||
const rightIndicatorStyle = { right: this.state.rightIndicatorOffset };
|
||||
const leftOverflowIndicator = trackHorizontalOverflow
|
||||
? <div className="mx_IndicatorScrollbar_leftOverflowIndicator" style={leftIndicatorStyle} /> : null;
|
||||
const rightOverflowIndicator = trackHorizontalOverflow
|
||||
|
|
|
@ -15,14 +15,14 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {InteractiveAuth} from "matrix-js-sdk/src/interactive-auth";
|
||||
import React, {createRef} from 'react';
|
||||
import { InteractiveAuth } from "matrix-js-sdk/src/interactive-auth";
|
||||
import React, { createRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import getEntryComponentForLoginType from '../views/auth/InteractiveAuthEntryComponents';
|
||||
|
||||
import * as sdk from '../../index';
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
|
||||
export const ERROR_USER_CANCELLED = new Error("User cancelled auth session");
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import CustomRoomTagPanel from "./CustomRoomTagPanel";
|
|||
import dis from "../../dispatcher/dispatcher";
|
||||
import { _t } from "../../languageHandler";
|
||||
import RoomList from "../views/rooms/RoomList";
|
||||
import CallHandler from "../../CallHandler";
|
||||
import { HEADER_HEIGHT } from "../views/rooms/RoomSublist";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import UserMenu from "./UserMenu";
|
||||
|
@ -38,8 +39,8 @@ import IndicatorScrollbar from "../structures/IndicatorScrollbar";
|
|||
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
||||
import RoomListNumResults from "../views/rooms/RoomListNumResults";
|
||||
import LeftPanelWidget from "./LeftPanelWidget";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import SpaceStore, {UPDATE_SELECTED_SPACE} from "../../stores/SpaceStore";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import SpaceStore, { UPDATE_SELECTED_SPACE } from "../../stores/SpaceStore";
|
||||
import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager";
|
||||
import UIStore from "../../stores/UIStore";
|
||||
import BackdropPanel from "./BackdropPanel";
|
||||
|
@ -87,7 +88,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.updateActiveSpace);
|
||||
this.groupFilterPanelWatcherRef = SettingsStore.watchSetting("TagPanel.enableTagPanel", null, () => {
|
||||
this.setState({showGroupFilterPanel: SettingsStore.getValue("TagPanel.enableTagPanel")});
|
||||
this.setState({ showGroupFilterPanel: SettingsStore.getValue("TagPanel.enableTagPanel") });
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -120,6 +121,10 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
this.setState({ activeSpace });
|
||||
};
|
||||
|
||||
private onDialPad = () => {
|
||||
dis.fire(Action.OpenDialPad);
|
||||
};
|
||||
|
||||
private onExplore = () => {
|
||||
dis.fire(Action.ViewRoomDirectory);
|
||||
};
|
||||
|
@ -127,12 +132,12 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
private refreshStickyHeaders = () => {
|
||||
if (!this.listContainerRef.current) return; // ignore: no headers to sticky
|
||||
this.handleStickyHeaders(this.listContainerRef.current);
|
||||
}
|
||||
};
|
||||
|
||||
private onBreadcrumbsUpdate = () => {
|
||||
const newVal = BreadcrumbsStore.instance.visible;
|
||||
if (newVal !== this.state.showBreadcrumbs) {
|
||||
this.setState({showBreadcrumbs: newVal});
|
||||
this.setState({ showBreadcrumbs: newVal });
|
||||
|
||||
// Update the sticky headers too as the breadcrumbs will be popping in or out.
|
||||
if (!this.listContainerRef.current) return; // ignore: no headers to sticky
|
||||
|
@ -376,7 +381,20 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
}
|
||||
}
|
||||
|
||||
private renderSearchExplore(): React.ReactNode {
|
||||
private renderSearchDialExplore(): React.ReactNode {
|
||||
let dialPadButton = null;
|
||||
|
||||
// If we have dialer support, show a button to bring up the dial pad
|
||||
// to start a new call
|
||||
if (CallHandler.sharedInstance().getSupportsPstnProtocol()) {
|
||||
dialPadButton =
|
||||
<AccessibleTooltipButton
|
||||
className={classNames("mx_LeftPanel_dialPadButton", {})}
|
||||
onClick={this.onDialPad}
|
||||
title={_t("Open dial pad")}
|
||||
/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="mx_LeftPanel_filterContainer"
|
||||
|
@ -389,6 +407,9 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
onKeyDown={this.onKeyDown}
|
||||
onSelectRoom={this.selectRoom}
|
||||
/>
|
||||
|
||||
{dialPadButton}
|
||||
|
||||
<AccessibleTooltipButton
|
||||
className={classNames("mx_LeftPanel_exploreButton", {
|
||||
mx_LeftPanel_exploreButton_space: !!this.state.activeSpace,
|
||||
|
@ -444,7 +465,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
{leftLeftPanel}
|
||||
<aside className="mx_LeftPanel_roomListContainer">
|
||||
{this.renderHeader()}
|
||||
{this.renderSearchExplore()}
|
||||
{this.renderSearchDialExplore()}
|
||||
{this.renderBreadcrumbs()}
|
||||
<RoomListNumResults onVisibilityChange={this.refreshStickyHeaders} />
|
||||
<div className="mx_LeftPanel_roomListWrapper">
|
||||
|
|
|
@ -14,19 +14,19 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {useContext, useMemo} from "react";
|
||||
import {Resizable} from "re-resizable";
|
||||
import React, { useContext, useMemo } from "react";
|
||||
import { Resizable } from "re-resizable";
|
||||
import classNames from "classnames";
|
||||
|
||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||
import {useRovingTabIndex} from "../../accessibility/RovingTabIndex";
|
||||
import {Key} from "../../Keyboard";
|
||||
import {useLocalStorageState} from "../../hooks/useLocalStorageState";
|
||||
import { useRovingTabIndex } from "../../accessibility/RovingTabIndex";
|
||||
import { Key } from "../../Keyboard";
|
||||
import { useLocalStorageState } from "../../hooks/useLocalStorageState";
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import WidgetUtils, {IWidgetEvent} from "../../utils/WidgetUtils";
|
||||
import {useAccountData} from "../../hooks/useAccountData";
|
||||
import WidgetUtils, { IWidgetEvent } from "../../utils/WidgetUtils";
|
||||
import { useAccountData } from "../../hooks/useAccountData";
|
||||
import AppTile from "../views/elements/AppTile";
|
||||
import {useSettingValue} from "../../hooks/useSettings";
|
||||
import { useSettingValue } from "../../hooks/useSettings";
|
||||
import UIStore from "../../stores/UIStore";
|
||||
|
||||
const MIN_HEIGHT = 100;
|
||||
|
@ -62,14 +62,14 @@ const LeftPanelWidget: React.FC = () => {
|
|||
let content;
|
||||
if (expanded) {
|
||||
content = <Resizable
|
||||
size={{height} as any}
|
||||
size={{ height } as any}
|
||||
minHeight={MIN_HEIGHT}
|
||||
maxHeight={Math.min(UIStore.instance.windowHeight / 2, MAX_HEIGHT)}
|
||||
onResizeStop={(e, dir, ref, d) => {
|
||||
setHeight(height + d.height);
|
||||
}}
|
||||
handleWrapperClass="mx_LeftPanelWidget_resizerHandles"
|
||||
handleClasses={{top: "mx_LeftPanelWidget_resizerHandle"}}
|
||||
handleClasses={{ top: "mx_LeftPanelWidget_resizerHandle" }}
|
||||
className="mx_LeftPanelWidget_resizeBox"
|
||||
enable={{ top: true }}
|
||||
>
|
||||
|
|
|
@ -20,7 +20,7 @@ import * as React from 'react';
|
|||
import * as PropTypes from 'prop-types';
|
||||
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||
|
||||
import {Key} from '../../Keyboard';
|
||||
import { Key } from '../../Keyboard';
|
||||
import PageTypes from '../../PageTypes';
|
||||
import MediaDeviceHandler from '../../MediaDeviceHandler';
|
||||
import { fixupColorFonts } from '../../utils/FontManager';
|
||||
|
@ -30,7 +30,7 @@ import { IMatrixClientCreds } from '../../MatrixClientPeg';
|
|||
import SettingsStore from "../../settings/SettingsStore";
|
||||
|
||||
import ResizeHandle from '../views/elements/ResizeHandle';
|
||||
import {Resizer, CollapseDistributor} from '../../resizer';
|
||||
import { Resizer, CollapseDistributor } from '../../resizer';
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import * as KeyboardShortcuts from "../../accessibility/KeyboardShortcuts";
|
||||
import HomePage from "./HomePage";
|
||||
|
@ -48,21 +48,20 @@ import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPay
|
|||
import RoomListStore from "../../stores/room-list/RoomListStore";
|
||||
import NonUrgentToastContainer from "./NonUrgentToastContainer";
|
||||
import { ToggleRightPanelPayload } from "../../dispatcher/payloads/ToggleRightPanelPayload";
|
||||
import { IThreepidInvite } from "../../stores/ThreepidInviteStore";
|
||||
import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore";
|
||||
import Modal from "../../Modal";
|
||||
import { ICollapseConfig } from "../../resizer/distributors/collapse";
|
||||
import HostSignupContainer from '../views/host_signup/HostSignupContainer';
|
||||
import { getKeyBindingsManager, NavigationAction, RoomAction } from '../../KeyBindingsManager';
|
||||
import { IOpts } from "../../createRoom";
|
||||
import SpacePanel from "../views/spaces/SpacePanel";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import CallHandler, { CallHandlerEvent } from '../../CallHandler';
|
||||
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
||||
import AudioFeedArrayForCall from '../views/voip/AudioFeedArrayForCall';
|
||||
import { OwnProfileStore } from '../../stores/OwnProfileStore';
|
||||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||
|
||||
|
||||
// We need to fetch each pinned message individually (if we don't already have it)
|
||||
// so each pinned message may trigger a request. Limit the number per room for sanity.
|
||||
// NB. this is just for server notices rather than pinned messages in general.
|
||||
|
@ -84,14 +83,14 @@ interface IProps {
|
|||
page_type: string;
|
||||
autoJoin: boolean;
|
||||
threepidInvite?: IThreepidInvite;
|
||||
roomOobData?: object;
|
||||
roomOobData?: IOOBData;
|
||||
currentRoomId: string;
|
||||
collapseLhs: boolean;
|
||||
config: {
|
||||
piwik: {
|
||||
policyUrl: string;
|
||||
},
|
||||
[key: string]: any,
|
||||
};
|
||||
[key: string]: any;
|
||||
};
|
||||
currentUserId?: string;
|
||||
currentGroupId?: string;
|
||||
|
@ -221,7 +220,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
this.setState({
|
||||
backgroundImage: await OwnProfileStore.instance.getAvatarBitmap(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private onCallsChanged = () => {
|
||||
this.setState({
|
||||
|
@ -245,10 +244,10 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
onCollapsed: (_collapsed) => {
|
||||
collapsed = _collapsed;
|
||||
if (_collapsed) {
|
||||
dis.dispatch({action: "hide_left_panel"});
|
||||
dis.dispatch({ action: "hide_left_panel" });
|
||||
window.localStorage.setItem("mx_lhs_size", '0');
|
||||
} else {
|
||||
dis.dispatch({action: "show_left_panel"});
|
||||
dis.dispatch({ action: "show_left_panel" });
|
||||
}
|
||||
},
|
||||
onResized: (_size) => {
|
||||
|
@ -285,7 +284,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
|
||||
onAccountData = (event) => {
|
||||
if (event.getType() === "m.ignored_user_list") {
|
||||
dis.dispatch({action: "ignore_state_changed"});
|
||||
dis.dispatch({ action: "ignore_state_changed" });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -332,7 +331,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
this.setState({
|
||||
usageLimitDismissed: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_calculateServerLimitToast(syncError: IState["syncErrorData"], usageLimitEventContent?: IUsageLimit) {
|
||||
const error = syncError && syncError.error && syncError.error.errcode === "M_RESOURCE_LIMIT_EXCEEDED";
|
||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import { Resizable } from 're-resizable';
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("structures.MainSplit")
|
||||
export default class MainSplit extends React.Component {
|
||||
|
@ -73,7 +73,7 @@ export default class MainSplit extends React.Component {
|
|||
onResize={this._onResize}
|
||||
onResizeStop={this._onResizeStop}
|
||||
className="mx_RightPanel_ResizeWrapper"
|
||||
handleClasses={{left: "mx_RightPanel_ResizeHandle"}}
|
||||
handleClasses={{ left: "mx_RightPanel_ResizeHandle" }}
|
||||
>
|
||||
{ panelView }
|
||||
</Resizable>;
|
||||
|
|
|
@ -19,6 +19,8 @@ import { createClient } from "matrix-js-sdk/src/matrix";
|
|||
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
|
||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { sleep, defer, IDeferred } from "matrix-js-sdk/src/utils";
|
||||
|
||||
// focus-visible is a Polyfill for the :focus-visible CSS pseudo-attribute used by _AccessibleButton.scss
|
||||
import 'focus-visible';
|
||||
// what-input helps improve keyboard accessibility
|
||||
|
@ -34,7 +36,6 @@ import dis from "../../dispatcher/dispatcher";
|
|||
import Notifier from '../../Notifier';
|
||||
|
||||
import Modal from "../../Modal";
|
||||
import Tinter from "../../Tinter";
|
||||
import * as sdk from '../../index';
|
||||
import { showRoomInviteDialog, showStartChatInviteDialog } from '../../RoomInvite';
|
||||
import * as Rooms from '../../Rooms';
|
||||
|
@ -44,8 +45,8 @@ import * as Lifecycle from '../../Lifecycle';
|
|||
import '../../stores/LifecycleStore';
|
||||
import PageTypes from '../../PageTypes';
|
||||
|
||||
import createRoom, {IOpts} from "../../createRoom";
|
||||
import {_t, _td, getCurrentLanguage} from '../../languageHandler';
|
||||
import createRoom, { IOpts } from "../../createRoom";
|
||||
import { _t, _td, getCurrentLanguage } from '../../languageHandler';
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import ThemeController from "../../settings/controllers/ThemeController";
|
||||
import { startAnyRegistrationFlow } from "../../Registration";
|
||||
|
@ -56,7 +57,6 @@ import DMRoomMap from '../../utils/DMRoomMap';
|
|||
import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
|
||||
import { FontWatcher } from '../../settings/watchers/FontWatcher';
|
||||
import { storeRoomAliasInCache } from '../../RoomAliasCache';
|
||||
import { defer, IDeferred, sleep } from "../../utils/promise";
|
||||
import ToastStore from "../../stores/ToastStore";
|
||||
import * as StorageManager from "../../utils/StorageManager";
|
||||
import type LoggedInViewType from "./LoggedInView";
|
||||
|
@ -66,7 +66,7 @@ import {
|
|||
showToast as showAnalyticsToast,
|
||||
hideToast as hideAnalyticsToast,
|
||||
} from "../../toasts/AnalyticsToast";
|
||||
import {showToast as showNotificationsToast} from "../../toasts/DesktopNotificationsToast";
|
||||
import { showToast as showNotificationsToast } from "../../toasts/DesktopNotificationsToast";
|
||||
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
|
||||
import ErrorDialog from "../views/dialogs/ErrorDialog";
|
||||
import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotificationStateStore";
|
||||
|
@ -74,15 +74,15 @@ import { SettingLevel } from "../../settings/SettingLevel";
|
|||
import { leaveRoomBehaviour } from "../../utils/membership";
|
||||
import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityPrototypeDialog";
|
||||
import ThreepidInviteStore, { IThreepidInvite, IThreepidInviteWireFormat } from "../../stores/ThreepidInviteStore";
|
||||
import {UIFeature} from "../../settings/UIFeature";
|
||||
import { UIFeature } from "../../settings/UIFeature";
|
||||
import { CommunityPrototypeStore } from "../../stores/CommunityPrototypeStore";
|
||||
import DialPadModal from "../views/voip/DialPadModal";
|
||||
import { showToast as showMobileGuideToast } from '../../toasts/MobileGuideToast';
|
||||
import { shouldUseLoginForWelcome } from "../../utils/pages";
|
||||
import SpaceStore from "../../stores/SpaceStore";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import RoomListStore from "../../stores/room-list/RoomListStore";
|
||||
import {RoomUpdateCause} from "../../stores/room-list/models";
|
||||
import { RoomUpdateCause } from "../../stores/room-list/models";
|
||||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||
import SecurityCustomisations from "../../customisations/Security";
|
||||
|
||||
|
@ -204,7 +204,7 @@ interface IState {
|
|||
resizeNotifier: ResizeNotifier;
|
||||
serverConfig?: ValidatedServerConfig;
|
||||
ready: boolean;
|
||||
threepidInvite?: IThreepidInvite,
|
||||
threepidInvite?: IThreepidInvite;
|
||||
roomOobData?: object;
|
||||
pendingInitialSync?: boolean;
|
||||
justRegistered?: boolean;
|
||||
|
@ -283,11 +283,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
|
||||
this.pageChanging = false;
|
||||
|
||||
// check we have the right tint applied for this theme.
|
||||
// N.B. we don't call the whole of setTheme() here as we may be
|
||||
// racing with the theme CSS download finishing from index.js
|
||||
Tinter.tint();
|
||||
|
||||
// For PersistentElement
|
||||
this.state.resizeNotifier.on("middlePanelResized", this.dispatchTimelineResize);
|
||||
|
||||
|
@ -401,7 +396,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
if (SecurityCustomisations.SHOW_ENCRYPTION_SETUP_UI === false) {
|
||||
this.onLoggedIn();
|
||||
} else {
|
||||
this.setStateForNewView({view: Views.COMPLETE_SECURITY});
|
||||
this.setStateForNewView({ view: Views.COMPLETE_SECURITY });
|
||||
}
|
||||
} else if (await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")) {
|
||||
this.setStateForNewView({ view: Views.E2E_SETUP });
|
||||
|
@ -454,7 +449,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
let props = this.state.serverConfig;
|
||||
if (!props) props = this.props.serverConfig; // for unit tests
|
||||
if (!props) props = SdkConfig.get()["validated_server_config"];
|
||||
return {serverConfig: props};
|
||||
return { serverConfig: props };
|
||||
}
|
||||
|
||||
private loadSession() {
|
||||
|
@ -472,9 +467,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
if (!loadedSession) {
|
||||
// fall back to showing the welcome screen... unless we have a 3pid invite pending
|
||||
if (ThreepidInviteStore.instance.pickBestInvite()) {
|
||||
dis.dispatch({action: 'start_registration'});
|
||||
dis.dispatch({ action: 'start_registration' });
|
||||
} else {
|
||||
dis.dispatch({action: "view_welcome_page"});
|
||||
dis.dispatch({ action: "view_welcome_page" });
|
||||
}
|
||||
} else if (SettingsStore.getValue("analyticsOptIn")) {
|
||||
CountlyAnalytics.instance.enable(/* anonymous = */ false);
|
||||
|
@ -538,7 +533,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
action: 'do_after_sync_prepared',
|
||||
deferred_action: payload,
|
||||
});
|
||||
dis.dispatch({action: 'require_registration'});
|
||||
dis.dispatch({ action: 'require_registration' });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -563,11 +558,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
}
|
||||
|
||||
// redispatch the change with a more specific action
|
||||
dis.dispatch({action: 'id_server_changed'});
|
||||
dis.dispatch({ action: 'id_server_changed' });
|
||||
}
|
||||
break;
|
||||
case 'logout':
|
||||
dis.dispatch({action: "hangup_all"});
|
||||
dis.dispatch({ action: "hangup_all" });
|
||||
Lifecycle.logout();
|
||||
break;
|
||||
case 'require_registration':
|
||||
|
@ -624,7 +619,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
MatrixClientPeg.get().leave(payload.room_id).then(() => {
|
||||
modal.close();
|
||||
if (this.state.currentRoomId === payload.room_id) {
|
||||
dis.dispatch({action: 'view_home_page'});
|
||||
dis.dispatch({ action: 'view_home_page' });
|
||||
}
|
||||
}, (err) => {
|
||||
modal.close();
|
||||
|
@ -657,7 +652,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
const tabPayload = payload as OpenToTabPayload;
|
||||
const UserSettingsDialog = sdk.getComponent("dialogs.UserSettingsDialog");
|
||||
Modal.createTrackedDialog('User settings', '', UserSettingsDialog,
|
||||
{initialTabId: tabPayload.initialTabId},
|
||||
{ initialTabId: tabPayload.initialTabId },
|
||||
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
||||
|
||||
// View the welcome or home page if we need something to look at
|
||||
|
@ -668,7 +663,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
this.createRoom(payload.public, payload.defaultName);
|
||||
break;
|
||||
case 'view_create_group': {
|
||||
let CreateGroupDialog = sdk.getComponent("dialogs.CreateGroupDialog")
|
||||
let CreateGroupDialog = sdk.getComponent("dialogs.CreateGroupDialog");
|
||||
if (SettingsStore.getValue("feature_communities_v2_prototypes")) {
|
||||
CreateGroupDialog = CreateCommunityPrototypeDialog;
|
||||
}
|
||||
|
@ -727,9 +722,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
// 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) {
|
||||
dis.dispatch({action: 'view_last_screen'});
|
||||
dis.dispatch({ action: 'view_last_screen' });
|
||||
} else {
|
||||
dis.dispatch({action: 'view_my_groups'});
|
||||
dis.dispatch({ action: 'view_my_groups' });
|
||||
}
|
||||
break;
|
||||
case 'hide_left_panel':
|
||||
|
@ -770,7 +765,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
this.onLoggedOut();
|
||||
break;
|
||||
case 'will_start_client':
|
||||
this.setState({ready: false}, () => {
|
||||
this.setState({ ready: false }, () => {
|
||||
// if the client is about to start, we are, by definition, not ready.
|
||||
// Set ready to false now, then it'll be set to true when the sync
|
||||
// listener we set below fires.
|
||||
|
@ -1006,7 +1001,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
return;
|
||||
}
|
||||
this.notifyNewScreen('user/' + userId);
|
||||
this.setState({currentUserId: userId});
|
||||
this.setState({ currentUserId: userId });
|
||||
this.setPage(PageTypes.UserView);
|
||||
});
|
||||
}
|
||||
|
@ -1131,8 +1126,14 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
description: (
|
||||
<span>
|
||||
{ isSpace
|
||||
? _t("Are you sure you want to leave the space '%(spaceName)s'?", {spaceName: roomToLeave.name})
|
||||
: _t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name}) }
|
||||
? _t(
|
||||
"Are you sure you want to leave the space '%(spaceName)s'?",
|
||||
{ spaceName: roomToLeave.name },
|
||||
)
|
||||
: _t(
|
||||
"Are you sure you want to leave the room '%(roomName)s'?",
|
||||
{ roomName: roomToLeave.name },
|
||||
)}
|
||||
{ warnings }
|
||||
</span>
|
||||
),
|
||||
|
@ -1170,7 +1171,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
}).catch((err) => {
|
||||
const errCode = err.errcode || _td("unknown error code");
|
||||
Modal.createTrackedDialog("Failed to forget room", '', ErrorDialog, {
|
||||
title: _t("Failed to forget room %(errCode)s", {errCode}),
|
||||
title: _t("Failed to forget room %(errCode)s", { errCode }),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
});
|
||||
|
@ -1254,7 +1255,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
if (welcomeUserRoom === null) {
|
||||
// We didn't redirect to the welcome user room, so show
|
||||
// the homepage.
|
||||
dis.dispatch({action: 'view_home_page', justRegistered: true});
|
||||
dis.dispatch({ action: 'view_home_page', justRegistered: true });
|
||||
}
|
||||
} else if (ThreepidInviteStore.instance.pickBestInvite()) {
|
||||
// The user has a 3pid invite pending - show them that
|
||||
|
@ -1263,11 +1264,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
// HACK: This is a pretty brutal way of threading the invite back through
|
||||
// our systems, but it's the safest we have for now.
|
||||
const params = ThreepidInviteStore.instance.translateToWireFormat(threepidInvite);
|
||||
this.showScreen(`room/${threepidInvite.roomId}`, params)
|
||||
this.showScreen(`room/${threepidInvite.roomId}`, params);
|
||||
} else {
|
||||
// The user has just logged in after registering,
|
||||
// so show the homepage.
|
||||
dis.dispatch({action: 'view_home_page', justRegistered: true});
|
||||
dis.dispatch({ action: 'view_home_page', justRegistered: true });
|
||||
}
|
||||
} else {
|
||||
this.showScreenAfterLogin();
|
||||
|
@ -1303,9 +1304,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
this.viewLastRoom();
|
||||
} else {
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
dis.dispatch({action: 'view_welcome_page'});
|
||||
dis.dispatch({ action: 'view_welcome_page' });
|
||||
} else {
|
||||
dis.dispatch({action: 'view_home_page'});
|
||||
dis.dispatch({ action: 'view_home_page' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1385,15 +1386,15 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
// So dispatch directly from here. Ideally we'd use a SyncStateStore that
|
||||
// would do this dispatch and expose the sync state itself (by listening to
|
||||
// its own dispatch).
|
||||
dis.dispatch({action: 'sync_state', prevState, state});
|
||||
dis.dispatch({ action: 'sync_state', prevState, state });
|
||||
|
||||
if (state === "ERROR" || state === "RECONNECTING") {
|
||||
if (data.error instanceof InvalidStoreError) {
|
||||
Lifecycle.handleInvalidStoreError(data.error);
|
||||
}
|
||||
this.setState({syncError: data.error || true});
|
||||
this.setState({ syncError: data.error || true });
|
||||
} else if (this.state.syncError) {
|
||||
this.setState({syncError: null});
|
||||
this.setState({ syncError: null });
|
||||
}
|
||||
|
||||
this.updateStatusIndicator(state, prevState);
|
||||
|
@ -1567,16 +1568,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
key: 'verifreq_' + request.channel.transactionId,
|
||||
title: _t("Verification requested"),
|
||||
icon: "verification",
|
||||
props: {request},
|
||||
props: { request },
|
||||
component: sdk.getComponent("toasts.VerificationRequestToast"),
|
||||
priority: 90,
|
||||
});
|
||||
}
|
||||
});
|
||||
// Fire the tinter right on startup to ensure the default theme is applied
|
||||
// A later sync can/will correct the tint to be the right value for the user
|
||||
const colorScheme = SettingsStore.getValue("roomColor");
|
||||
Tinter.tint(colorScheme.primary_color, colorScheme.secondary_color);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1668,7 +1665,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
// TODO if logged in, skip SSO
|
||||
let cli = MatrixClientPeg.get();
|
||||
if (!cli) {
|
||||
const {hsUrl, isUrl} = this.props.serverConfig;
|
||||
const { hsUrl, isUrl } = this.props.serverConfig;
|
||||
cli = createClient({
|
||||
baseUrl: hsUrl,
|
||||
idBaseUrl: isUrl,
|
||||
|
@ -1792,7 +1789,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
|
||||
onAliasClick(event: MouseEvent, alias: string) {
|
||||
event.preventDefault();
|
||||
dis.dispatch({action: 'view_room', room_alias: alias});
|
||||
dis.dispatch({ action: 'view_room', room_alias: alias });
|
||||
}
|
||||
|
||||
onUserClick(event: MouseEvent, userId: string) {
|
||||
|
@ -1808,7 +1805,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
|
||||
onGroupClick(event: MouseEvent, groupId: string) {
|
||||
event.preventDefault();
|
||||
dis.dispatch({action: 'view_group', group_id: groupId});
|
||||
dis.dispatch({ action: 'view_group', group_id: groupId });
|
||||
}
|
||||
|
||||
onLogoutClick(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) {
|
||||
|
@ -1870,14 +1867,14 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
onSendEvent(roomId: string, event: MatrixEvent) {
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (!cli) {
|
||||
dis.dispatch({action: 'message_send_failed'});
|
||||
dis.dispatch({ action: 'message_send_failed' });
|
||||
return;
|
||||
}
|
||||
|
||||
cli.sendEvent(roomId, event.getType(), event.getContent()).then(() => {
|
||||
dis.dispatch({action: 'message_sent'});
|
||||
dis.dispatch({ action: 'message_sent' });
|
||||
}, (err) => {
|
||||
dis.dispatch({action: 'message_send_failed'});
|
||||
dis.dispatch({ action: 'message_send_failed' });
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1924,7 +1921,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
}
|
||||
|
||||
onServerConfigChange = (serverConfig: ValidatedServerConfig) => {
|
||||
this.setState({serverConfig});
|
||||
this.setState({ serverConfig });
|
||||
};
|
||||
|
||||
private makeRegistrationUrl = (params: {[key: string]: string}) => {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -24,7 +24,7 @@ import dis from '../../dispatcher/dispatcher';
|
|||
import AccessibleButton from '../views/elements/AccessibleButton';
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import BetaCard from "../views/beta/BetaCard";
|
||||
|
||||
@replaceableComponent("structures.MyGroups")
|
||||
|
@ -41,19 +41,19 @@ export default class MyGroups extends React.Component {
|
|||
}
|
||||
|
||||
_onCreateGroupClick = () => {
|
||||
dis.dispatch({action: 'view_create_group'});
|
||||
dis.dispatch({ action: 'view_create_group' });
|
||||
};
|
||||
|
||||
_fetch() {
|
||||
this.context.getJoinedGroups().then((result) => {
|
||||
this.setState({groups: result.groups, error: null});
|
||||
this.setState({ groups: result.groups, error: null });
|
||||
}, (err) => {
|
||||
if (err.errcode === 'M_GUEST_ACCESS_FORBIDDEN') {
|
||||
// Indicate that the guest isn't in any groups (which should be true)
|
||||
this.setState({groups: [], error: null});
|
||||
this.setState({ groups: [], error: null });
|
||||
return;
|
||||
}
|
||||
this.setState({groups: null, error: err});
|
||||
this.setState({ groups: null, error: err });
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -123,7 +123,7 @@ export default class MyGroups extends React.Component {
|
|||
</div>
|
||||
{/*<div className="mx_MyGroups_joinBox mx_MyGroups_headerCard">
|
||||
<AccessibleButton className='mx_MyGroups_headerCard_button' onClick={this._onJoinGroupClick}>
|
||||
<TintableSvg src={require("../../../res/img/icons-create-room.svg")} width="50" height="50" />
|
||||
<img src={require("../../../res/img/icons-create-room.svg")} width="50" height="50" />
|
||||
</AccessibleButton>
|
||||
<div className="mx_MyGroups_headerCard_content">
|
||||
<div className="mx_MyGroups_headerCard_header">
|
||||
|
|
|
@ -18,13 +18,13 @@ import * as React from "react";
|
|||
import { ComponentClass } from "../../@types/common";
|
||||
import NonUrgentToastStore from "../../stores/NonUrgentToastStore";
|
||||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
}
|
||||
|
||||
interface IState {
|
||||
toasts: ComponentClass[],
|
||||
toasts: ComponentClass[];
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.NonUrgentToastContainer")
|
||||
|
@ -44,7 +44,7 @@ export default class NonUrgentToastContainer extends React.PureComponent<IProps,
|
|||
}
|
||||
|
||||
private onUpdateToasts = () => {
|
||||
this.setState({toasts: NonUrgentToastStore.instance.components});
|
||||
this.setState({ toasts: NonUrgentToastStore.instance.components });
|
||||
};
|
||||
|
||||
public render() {
|
||||
|
|
|
@ -22,6 +22,7 @@ import BaseCard from "../views/right_panel/BaseCard";
|
|||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import TimelinePanel from "./TimelinePanel";
|
||||
import Spinner from "../views/elements/Spinner";
|
||||
import { TileShape } from "../views/rooms/EventTile";
|
||||
|
||||
interface IProps {
|
||||
onClose(): void;
|
||||
|
@ -48,7 +49,7 @@ export default class NotificationPanel extends React.PureComponent<IProps> {
|
|||
manageReadMarkers={false}
|
||||
timelineSet={timelineSet}
|
||||
showUrlPreview={false}
|
||||
tileShape="notif"
|
||||
tileShape={TileShape.Notif}
|
||||
empty={emptyState}
|
||||
alwaysShowTimestamps={true}
|
||||
/>
|
||||
|
|
|
@ -23,7 +23,6 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
|||
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||
|
||||
import dis from '../../dispatcher/dispatcher';
|
||||
import RateLimitedFunc from '../../ratelimitedfunc';
|
||||
import GroupStore from '../../stores/GroupStore';
|
||||
import {
|
||||
RIGHT_PANEL_PHASES_NO_ARGS,
|
||||
|
@ -48,6 +47,7 @@ import FilePanel from "./FilePanel";
|
|||
import NotificationPanel from "./NotificationPanel";
|
||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||
import PinnedMessagesCard from "../views/right_panel/PinnedMessagesCard";
|
||||
import { throttle } from 'lodash';
|
||||
|
||||
interface IProps {
|
||||
room?: Room; // if showing panels for a given room, this is set
|
||||
|
@ -73,7 +73,6 @@ interface IState {
|
|||
export default class RightPanel extends React.Component<IProps, IState> {
|
||||
static contextType = MatrixClientContext;
|
||||
|
||||
private readonly delayedUpdate: RateLimitedFunc;
|
||||
private dispatcherRef: string;
|
||||
|
||||
constructor(props, context) {
|
||||
|
@ -84,12 +83,12 @@ export default class RightPanel extends React.Component<IProps, IState> {
|
|||
isUserPrivilegedInGroup: null,
|
||||
member: this.getUserForPanel(),
|
||||
};
|
||||
|
||||
this.delayedUpdate = new RateLimitedFunc(() => {
|
||||
this.forceUpdate();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
private readonly delayedUpdate = throttle((): void => {
|
||||
this.forceUpdate();
|
||||
}, 500, { leading: true, trailing: true });
|
||||
|
||||
// Helper function to split out the logic for getPhaseFromProps() and the constructor
|
||||
// as both are called at the same time in the constructor.
|
||||
private getUserForPanel() {
|
||||
|
@ -104,7 +103,7 @@ export default class RightPanel extends React.Component<IProps, IState> {
|
|||
const userForPanel = this.getUserForPanel();
|
||||
if (this.props.groupId) {
|
||||
if (!RIGHT_PANEL_PHASES_NO_ARGS.includes(rps.groupPanelPhase)) {
|
||||
dis.dispatch({action: Action.SetRightPanelPhase, phase: RightPanelPhases.GroupMemberList});
|
||||
dis.dispatch({ action: Action.SetRightPanelPhase, phase: RightPanelPhases.GroupMemberList });
|
||||
return RightPanelPhases.GroupMemberList;
|
||||
}
|
||||
return rps.groupPanelPhase;
|
||||
|
|
|
@ -25,7 +25,7 @@ import { _t } from '../../languageHandler';
|
|||
import SdkConfig from '../../SdkConfig';
|
||||
import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils';
|
||||
import Analytics from '../../Analytics';
|
||||
import {ALL_ROOMS, IFieldType, IInstance, IProtocol, Protocols} from "../views/directory/NetworkDropdown";
|
||||
import { ALL_ROOMS, IFieldType, IInstance, IProtocol, Protocols } from "../views/directory/NetworkDropdown";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import GroupFilterOrderStore from "../../stores/GroupFilterOrderStore";
|
||||
import GroupStore from "../../stores/GroupStore";
|
||||
|
@ -34,7 +34,7 @@ import CountlyAnalytics from "../../CountlyAnalytics";
|
|||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import { mediaFromMxc } from "../../customisations/Media";
|
||||
import { IDialogProps } from "../views/dialogs/IDialogProps";
|
||||
import AccessibleButton, {ButtonEvent} from "../views/elements/AccessibleButton";
|
||||
import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
|
||||
import BaseAvatar from "../views/avatars/BaseAvatar";
|
||||
import ErrorDialog from "../views/dialogs/ErrorDialog";
|
||||
import QuestionDialog from "../views/dialogs/QuestionDialog";
|
||||
|
@ -45,7 +45,6 @@ import ScrollPanel from "./ScrollPanel";
|
|||
import Spinner from "../views/elements/Spinner";
|
||||
import { ActionPayload } from "../../dispatcher/payloads";
|
||||
|
||||
|
||||
const MAX_NAME_LENGTH = 80;
|
||||
const MAX_TOPIC_LENGTH = 800;
|
||||
|
||||
|
@ -95,7 +94,7 @@ interface IPublicRoomsRequest {
|
|||
@replaceableComponent("structures.RoomDirectory")
|
||||
export default class RoomDirectory extends React.Component<IProps, IState> {
|
||||
private readonly startTime: number;
|
||||
private unmounted = false
|
||||
private unmounted = false;
|
||||
private nextBatch: string = null;
|
||||
private filterTimeout: NodeJS.Timeout;
|
||||
private protocols: Protocols;
|
||||
|
@ -207,9 +206,9 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
|||
this.getMoreRooms();
|
||||
};
|
||||
|
||||
private getMoreRooms() {
|
||||
if (this.state.selectedCommunityId) return Promise.resolve(); // no more rooms
|
||||
if (!MatrixClientPeg.get()) return Promise.resolve();
|
||||
private getMoreRooms(): Promise<boolean> {
|
||||
if (this.state.selectedCommunityId) return Promise.resolve(false); // no more rooms
|
||||
if (!MatrixClientPeg.get()) return Promise.resolve(false);
|
||||
|
||||
this.setState({
|
||||
loading: true,
|
||||
|
@ -239,12 +238,12 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
|||
// if the filter or server has changed since this request was sent,
|
||||
// throw away the result (don't even clear the busy flag
|
||||
// since we must still have a request in flight)
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.unmounted) {
|
||||
// if we've been unmounted, we don't care either.
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.state.filterString) {
|
||||
|
@ -264,14 +263,13 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
|||
filterString != this.state.filterString ||
|
||||
roomServer != this.state.roomServer ||
|
||||
nextBatch != this.nextBatch) {
|
||||
// as above: we don't care about errors for old
|
||||
// requests either
|
||||
return;
|
||||
// as above: we don't care about errors for old requests either
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.unmounted) {
|
||||
// if we've been unmounted, we don't care either.
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
console.error("Failed to get publicRooms: %s", JSON.stringify(err));
|
||||
|
@ -300,9 +298,9 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
|||
|
||||
let desc;
|
||||
if (alias) {
|
||||
desc = _t('Delete the room address %(alias)s and remove %(name)s from the directory?', {alias, name});
|
||||
desc = _t('Delete the room address %(alias)s and remove %(name)s from the directory?', { alias, name });
|
||||
} else {
|
||||
desc = _t('Remove %(name)s from the directory?', {name: name});
|
||||
desc = _t('Remove %(name)s from the directory?', { name: name });
|
||||
}
|
||||
|
||||
Modal.createTrackedDialog('Remove from Directory', '', QuestionDialog, {
|
||||
|
@ -312,7 +310,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
|||
if (!shouldDelete) return;
|
||||
|
||||
const modal = Modal.createDialog(Spinner);
|
||||
let step = _t('remove %(name)s from the directory.', {name: name});
|
||||
let step = _t('remove %(name)s from the directory.', { name: name });
|
||||
|
||||
MatrixClientPeg.get().setRoomDirectoryVisibility(room.room_id, 'private').then(() => {
|
||||
if (!alias) return;
|
||||
|
@ -372,7 +370,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
|||
|
||||
private onFilterChange = (alias: string) => {
|
||||
this.setState({
|
||||
filterString: alias || null,
|
||||
filterString: alias || "",
|
||||
});
|
||||
|
||||
// don't send the request for a little bit,
|
||||
|
@ -391,7 +389,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
|||
private onFilterClear = () => {
|
||||
// update immediately
|
||||
this.setState({
|
||||
filterString: null,
|
||||
filterString: "",
|
||||
}, this.refreshRoomList);
|
||||
|
||||
if (this.filterTimeout) {
|
||||
|
@ -483,7 +481,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
|||
// to the directory.
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
if (!room.world_readable && !room.guest_can_join) {
|
||||
dis.dispatch({action: 'require_registration'});
|
||||
dis.dispatch({ action: 'require_registration' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -782,11 +780,11 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
|||
}
|
||||
const explanation =
|
||||
_t("If you can't find the room you're looking for, ask for an invite or <a>Create a new room</a>.", null,
|
||||
{a: sub => (
|
||||
{ a: sub => (
|
||||
<AccessibleButton kind="secondary" onClick={this.onCreateRoomClick}>
|
||||
{ sub }
|
||||
</AccessibleButton>
|
||||
)},
|
||||
) },
|
||||
);
|
||||
|
||||
const title = this.state.selectedCommunityId
|
||||
|
|
|
@ -108,22 +108,22 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
|||
};
|
||||
|
||||
private openSearch = () => {
|
||||
defaultDispatcher.dispatch({action: "show_left_panel"});
|
||||
defaultDispatcher.dispatch({action: "focus_room_filter"});
|
||||
defaultDispatcher.dispatch({ action: "show_left_panel" });
|
||||
defaultDispatcher.dispatch({ action: "focus_room_filter" });
|
||||
};
|
||||
|
||||
private onChange = () => {
|
||||
if (!this.inputRef.current) return;
|
||||
this.setState({query: this.inputRef.current.value});
|
||||
this.setState({ query: this.inputRef.current.value });
|
||||
};
|
||||
|
||||
private onFocus = (ev: React.FocusEvent<HTMLInputElement>) => {
|
||||
this.setState({focused: true});
|
||||
this.setState({ focused: true });
|
||||
ev.target.select();
|
||||
};
|
||||
|
||||
private onBlur = (ev: React.FocusEvent<HTMLInputElement>) => {
|
||||
this.setState({focused: false});
|
||||
this.setState({ focused: false });
|
||||
};
|
||||
|
||||
private onKeyDown = (ev: React.KeyboardEvent) => {
|
||||
|
|
|
@ -17,15 +17,15 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t, _td } from '../../languageHandler';
|
||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||
import { MatrixClientPeg } from '../../MatrixClientPeg';
|
||||
import Resend from '../../Resend';
|
||||
import dis from '../../dispatcher/dispatcher';
|
||||
import {messageForResourceLimitError} from '../../utils/ErrorUtils';
|
||||
import {Action} from "../../dispatcher/actions";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import {EventStatus} from "matrix-js-sdk/src/models/event";
|
||||
import { messageForResourceLimitError } from '../../utils/ErrorUtils';
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import { EventStatus } from "matrix-js-sdk/src/models/event";
|
||||
import NotificationBadge from "../views/rooms/NotificationBadge";
|
||||
import {StaticNotificationState} from "../../stores/notifications/StaticNotificationState";
|
||||
import { StaticNotificationState } from "../../stores/notifications/StaticNotificationState";
|
||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||
import InlineSpinner from "../views/elements/InlineSpinner";
|
||||
|
||||
|
@ -115,9 +115,9 @@ export default class RoomStatusBar extends React.PureComponent {
|
|||
|
||||
_onResendAllClick = () => {
|
||||
Resend.resendUnsentEvents(this.props.room).then(() => {
|
||||
this.setState({isResending: false});
|
||||
this.setState({ isResending: false });
|
||||
});
|
||||
this.setState({isResending: true});
|
||||
this.setState({ isResending: true });
|
||||
dis.fire(Action.FocusComposer);
|
||||
};
|
||||
|
||||
|
|
|
@ -37,8 +37,6 @@ import Modal from '../../Modal';
|
|||
import * as sdk from '../../index';
|
||||
import CallHandler, { PlaceCallType } from '../../CallHandler';
|
||||
import dis from '../../dispatcher/dispatcher';
|
||||
import Tinter from '../../Tinter';
|
||||
import rateLimitedFunc from '../../ratelimitedfunc';
|
||||
import * as Rooms from '../../Rooms';
|
||||
import eventSearch, { searchPagination } from '../../Searching';
|
||||
import MainSplit from './MainSplit';
|
||||
|
@ -65,7 +63,7 @@ import RoomUpgradeWarningBar from "../views/rooms/RoomUpgradeWarningBar";
|
|||
import AuxPanel from "../views/rooms/AuxPanel";
|
||||
import RoomHeader from "../views/rooms/RoomHeader";
|
||||
import { XOR } from "../../@types/common";
|
||||
import { IThreepidInvite } from "../../stores/ThreepidInviteStore";
|
||||
import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore";
|
||||
import EffectsOverlay from "../views/elements/EffectsOverlay";
|
||||
import { containsEmoji } from '../../effects/utils';
|
||||
import { CHAT_EFFECTS } from '../../effects';
|
||||
|
@ -82,6 +80,8 @@ 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";
|
||||
|
||||
const DEBUG = false;
|
||||
let debuglog = function(msg: string) {};
|
||||
|
@ -94,22 +94,8 @@ if (DEBUG) {
|
|||
}
|
||||
|
||||
interface IProps {
|
||||
threepidInvite: IThreepidInvite,
|
||||
|
||||
// Any data about the room that would normally come from the homeserver
|
||||
// but has been passed out-of-band, eg. the room name and avatar URL
|
||||
// from an email invite (a workaround for the fact that we can't
|
||||
// get this information from the HS using an email invite).
|
||||
// Fields:
|
||||
// * name (string) The room's name
|
||||
// * avatarUrl (string) The mxc:// avatar URL for the room
|
||||
// * inviterName (string) The display name of the person who
|
||||
// * invited us to the room
|
||||
oobData?: {
|
||||
name?: string;
|
||||
avatarUrl?: string;
|
||||
inviterName?: string;
|
||||
};
|
||||
threepidInvite: IThreepidInvite;
|
||||
oobData?: IOOBData;
|
||||
|
||||
resizeNotifier: ResizeNotifier;
|
||||
justCreatedOpts?: IOpts;
|
||||
|
@ -192,6 +178,7 @@ export interface IState {
|
|||
// whether or not a spaces context switch brought us here,
|
||||
// if it did we don't want the room to be marked as read as soon as it is loaded.
|
||||
wasContextSwitch?: boolean;
|
||||
editState?: EditorStateTransfer;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.RoomView")
|
||||
|
@ -285,7 +272,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
if (this.state.room) {
|
||||
this.checkWidgets(this.state.room);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private checkWidgets = (room) => {
|
||||
this.setState({
|
||||
|
@ -528,7 +515,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
} else if (room) {
|
||||
// Stop peeking because we have joined this room previously
|
||||
this.context.stopPeeking();
|
||||
this.setState({isPeeking: false});
|
||||
this.setState({ isPeeking: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -674,12 +661,8 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
);
|
||||
}
|
||||
|
||||
// cancel any pending calls to the rate_limited_funcs
|
||||
this.updateRoomMembers.cancelPendingCall();
|
||||
|
||||
// no need to do this as Dir & Settings are now overlays. It just burnt CPU.
|
||||
// console.log("Tinter.tint from RoomView.unmount");
|
||||
// Tinter.tint(); // reset colourscheme
|
||||
// cancel any pending calls to the throttled updated
|
||||
this.updateRoomMembers.cancel();
|
||||
|
||||
for (const watcher of this.settingWatchers) {
|
||||
SettingsStore.unwatchSetting(watcher);
|
||||
|
@ -696,7 +679,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
replyingToEvent: this.state.replyToEvent,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private onRightPanelStoreUpdate = () => {
|
||||
this.setState({
|
||||
|
@ -815,6 +798,36 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
case 'focus_search':
|
||||
this.onSearchClick();
|
||||
break;
|
||||
|
||||
case "edit_event": {
|
||||
const editState = payload.event ? new EditorStateTransfer(payload.event) : null;
|
||||
this.setState({ editState }, () => {
|
||||
if (payload.event) {
|
||||
this.messagePanel?.scrollToEventIfNeeded(payload.event.getId());
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case Action.ComposerInsert: {
|
||||
// re-dispatch to the correct composer
|
||||
if (this.state.editState) {
|
||||
dis.dispatch({
|
||||
...payload,
|
||||
action: "edit_composer_insert",
|
||||
});
|
||||
} else {
|
||||
dis.dispatch({
|
||||
...payload,
|
||||
action: "send_composer_insert",
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "scroll_to_bottom":
|
||||
this.messagePanel?.jumpToLiveTimeline();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -850,7 +863,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
// no change
|
||||
} else if (!shouldHideEvent(ev, this.state)) {
|
||||
this.setState((state, props) => {
|
||||
return {numUnreadMessages: state.numUnreadMessages + 1};
|
||||
return { numUnreadMessages: state.numUnreadMessages + 1 };
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -875,7 +888,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
|
||||
CHAT_EFFECTS.forEach(effect => {
|
||||
if (containsEmoji(ev.getContent(), effect.emojis) || ev.getContent().msgtype === effect.msgType) {
|
||||
dis.dispatch({action: `effects.${effect.command}`});
|
||||
dis.dispatch({ action: `effects.${effect.command}` });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -928,7 +941,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
try {
|
||||
await room.loadMembersIfNeeded();
|
||||
if (!this.unmounted) {
|
||||
this.setState({membersLoaded: true});
|
||||
this.setState({ membersLoaded: true });
|
||||
}
|
||||
} catch (err) {
|
||||
const errorMessage = `Fetching room members for ${room.roomId} failed.` +
|
||||
|
@ -956,7 +969,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
}
|
||||
}
|
||||
|
||||
private updatePreviewUrlVisibility({roomId}: Room) {
|
||||
private updatePreviewUrlVisibility({ roomId }: Room) {
|
||||
// URL Previews in E2EE rooms can be a privacy leak so use a different setting which is per-room explicit
|
||||
const key = this.context.isRoomEncrypted(roomId) ? 'urlPreviewsEnabled_e2ee' : 'urlPreviewsEnabled';
|
||||
this.setState({
|
||||
|
@ -1027,15 +1040,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
});
|
||||
}
|
||||
|
||||
private updateTint() {
|
||||
const room = this.state.room;
|
||||
if (!room) return;
|
||||
|
||||
console.log("Tinter.tint from updateTint");
|
||||
const colorScheme = SettingsStore.getValue("roomColor", room.roomId);
|
||||
Tinter.tint(colorScheme.primary_color, colorScheme.secondary_color);
|
||||
}
|
||||
|
||||
private onAccountData = (event: MatrixEvent) => {
|
||||
const type = event.getType();
|
||||
if ((type === "org.matrix.preview_urls" || type === "im.vector.web.settings") && this.state.room) {
|
||||
|
@ -1047,12 +1051,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
private onRoomAccountData = (event: MatrixEvent, room: Room) => {
|
||||
if (room.roomId == this.state.roomId) {
|
||||
const type = event.getType();
|
||||
if (type === "org.matrix.room.color_scheme") {
|
||||
const colorScheme = event.getContent();
|
||||
// XXX: we should validate the event
|
||||
console.log("Tinter.tint from onRoomAccountData");
|
||||
Tinter.tint(colorScheme.primary_color, colorScheme.secondary_color);
|
||||
} else if (type === "org.matrix.room.preview_urls" || type === "im.vector.web.settings") {
|
||||
if (type === "org.matrix.room.preview_urls" || type === "im.vector.web.settings") {
|
||||
// non-e2ee url previews are stored in legacy event type `org.matrix.room.preview_urls`
|
||||
this.updatePreviewUrlVisibility(room);
|
||||
}
|
||||
|
@ -1079,7 +1078,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
return;
|
||||
}
|
||||
|
||||
this.updateRoomMembers(member);
|
||||
this.updateRoomMembers();
|
||||
};
|
||||
|
||||
private onMyMembership = (room: Room, membership: string, oldMembership: string) => {
|
||||
|
@ -1096,15 +1095,15 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
const canReact = room.getMyMembership() === "join" && room.currentState.maySendEvent("m.reaction", me);
|
||||
const canReply = room.maySendMessage();
|
||||
|
||||
this.setState({canReact, canReply});
|
||||
this.setState({ canReact, canReply });
|
||||
}
|
||||
}
|
||||
|
||||
// rate limited because a power level change will emit an event for every member in the room.
|
||||
private updateRoomMembers = rateLimitedFunc(() => {
|
||||
private updateRoomMembers = throttle(() => {
|
||||
this.updateDMState();
|
||||
this.updateE2EStatus(this.state.room);
|
||||
}, 500);
|
||||
}, 500, { leading: true, trailing: true });
|
||||
|
||||
private checkDesktopNotifications() {
|
||||
const memberCount = this.state.room.getJoinedMemberCount() + this.state.room.getInvitedMemberCount();
|
||||
|
@ -1125,7 +1124,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
}
|
||||
}
|
||||
|
||||
private onSearchResultsFillRequest = (backwards: boolean) => {
|
||||
private onSearchResultsFillRequest = (backwards: boolean): Promise<boolean> => {
|
||||
if (!backwards) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
@ -1160,7 +1159,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
room_id: this.getRoomId(),
|
||||
},
|
||||
});
|
||||
dis.dispatch({action: 'require_registration'});
|
||||
dis.dispatch({ action: 'require_registration' });
|
||||
} else {
|
||||
Promise.resolve().then(() => {
|
||||
const signUrl = this.props.threepidInvite?.signUrl;
|
||||
|
@ -1195,13 +1194,13 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
|
||||
// We always increment the counter no matter the types, because dragging is
|
||||
// still happening. If we didn't, the drag counter would get out of sync.
|
||||
this.setState({dragCounter: this.state.dragCounter + 1});
|
||||
this.setState({ dragCounter: this.state.dragCounter + 1 });
|
||||
|
||||
// See:
|
||||
// https://docs.w3cub.com/dom/datatransfer/types
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#file
|
||||
if (ev.dataTransfer.types.includes("Files") || ev.dataTransfer.types.includes("application/x-moz-file")) {
|
||||
this.setState({draggingFile: true});
|
||||
this.setState({ draggingFile: true });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1248,9 +1247,9 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
});
|
||||
};
|
||||
|
||||
private injectSticker(url, info, text) {
|
||||
private injectSticker(url: string, info: object, text: string) {
|
||||
if (this.context.isGuest()) {
|
||||
dis.dispatch({action: 'require_registration'});
|
||||
dis.dispatch({ action: 'require_registration' });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1291,7 +1290,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
this.handleSearchResult(searchPromise);
|
||||
};
|
||||
|
||||
private handleSearchResult(searchPromise: Promise<any>) {
|
||||
private handleSearchResult(searchPromise: Promise<any>): Promise<boolean> {
|
||||
// keep a record of the current search id, so that if the search terms
|
||||
// change before we get a response, we can ignore the results.
|
||||
const localSearchId = this.searchId;
|
||||
|
@ -1304,7 +1303,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
debuglog("search complete");
|
||||
if (this.unmounted || !this.state.searching || this.searchId != localSearchId) {
|
||||
console.error("Discarding stale search results");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// postgres on synapse returns us precise details of the strings
|
||||
|
@ -1336,6 +1335,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
description: ((error && error.message) ? error.message :
|
||||
_t("Server may be unavailable, overloaded, or search timed out :(")),
|
||||
});
|
||||
return false;
|
||||
}).finally(() => {
|
||||
this.setState({
|
||||
searchInProgress: false,
|
||||
|
@ -1446,13 +1446,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
});
|
||||
};
|
||||
|
||||
private onLeaveClick = () => {
|
||||
dis.dispatch({
|
||||
action: 'leave_room',
|
||||
room_id: this.state.room.roomId,
|
||||
});
|
||||
};
|
||||
|
||||
private onForgetClick = () => {
|
||||
dis.dispatch({
|
||||
action: 'forget_room',
|
||||
|
@ -1577,7 +1570,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
|
||||
const showBar = this.messagePanel.canJumpToReadMarker();
|
||||
if (this.state.showTopUnreadMessagesBar != showBar) {
|
||||
this.setState({showTopUnreadMessagesBar: showBar});
|
||||
this.setState({ showTopUnreadMessagesBar: showBar });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1686,10 +1679,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
// otherwise react calls it with null on each update.
|
||||
private gatherTimelinePanelRef = r => {
|
||||
this.messagePanel = r;
|
||||
if (r) {
|
||||
console.log("updateTint from RoomView.gatherTimelinePanelRef");
|
||||
this.updateTint();
|
||||
}
|
||||
};
|
||||
|
||||
private getOldRoom() {
|
||||
|
@ -1708,7 +1697,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
onHiddenHighlightsClick = () => {
|
||||
const oldRoom = this.getOldRoom();
|
||||
if (!oldRoom) return;
|
||||
dis.dispatch({action: "view_room", room_id: oldRoom.roomId});
|
||||
dis.dispatch({ action: "view_room", room_id: oldRoom.roomId });
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -1916,7 +1905,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
>
|
||||
{_t(
|
||||
"You have %(count)s unread notifications in a prior version of this room.",
|
||||
{count: hiddenHighlightCount},
|
||||
{ count: hiddenHighlightCount },
|
||||
)}
|
||||
</AccessibleButton>
|
||||
);
|
||||
|
@ -2039,6 +2028,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
resizeNotifier={this.props.resizeNotifier}
|
||||
showReactions={true}
|
||||
layout={this.state.layout}
|
||||
editState={this.state.editState}
|
||||
/>);
|
||||
|
||||
let topUnreadMessagesBar = null;
|
||||
|
@ -2095,7 +2085,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
onSearchClick={this.onSearchClick}
|
||||
onSettingsClick={this.onSettingsClick}
|
||||
onForgetClick={(myMembership === "leave") ? this.onForgetClick : null}
|
||||
onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null}
|
||||
e2eStatus={this.state.e2eStatus}
|
||||
onAppsClick={this.state.hasPinnedWidgets ? this.onAppsClick : null}
|
||||
appsShown={this.state.showApps}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2015 - 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.
|
||||
|
@ -14,17 +14,17 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {createRef} from "react";
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { createRef, CSSProperties, ReactNode, SyntheticEvent, KeyboardEvent } from "react";
|
||||
import Timer from '../../utils/Timer';
|
||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import {getKeyBindingsManager, RoomAction} from "../../KeyBindingsManager";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import { getKeyBindingsManager, RoomAction } from "../../KeyBindingsManager";
|
||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||
|
||||
const DEBUG_SCROLL = false;
|
||||
|
||||
// The amount of extra scroll distance to allow prior to unfilling.
|
||||
// See _getExcessHeight.
|
||||
// See getExcessHeight.
|
||||
const UNPAGINATION_PADDING = 6000;
|
||||
// The number of milliseconds to debounce calls to onUnfillRequest, to prevent
|
||||
// many scroll events causing many unfilling requests.
|
||||
|
@ -43,6 +43,75 @@ if (DEBUG_SCROLL) {
|
|||
debuglog = function() {};
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
/* stickyBottom: if set to true, then once the user hits the bottom of
|
||||
* the list, any new children added to the list will cause the list to
|
||||
* scroll down to show the new element, rather than preserving the
|
||||
* existing view.
|
||||
*/
|
||||
stickyBottom?: boolean;
|
||||
|
||||
/* startAtBottom: if set to true, the view is assumed to start
|
||||
* scrolled to the bottom.
|
||||
* XXX: It's likely this is unnecessary and can be derived from
|
||||
* stickyBottom, but I'm adding an extra parameter to ensure
|
||||
* behaviour stays the same for other uses of ScrollPanel.
|
||||
* If so, let's remove this parameter down the line.
|
||||
*/
|
||||
startAtBottom?: boolean;
|
||||
|
||||
/* className: classnames to add to the top-level div
|
||||
*/
|
||||
className?: string;
|
||||
|
||||
/* style: styles to add to the top-level div
|
||||
*/
|
||||
style?: CSSProperties;
|
||||
|
||||
/* resizeNotifier: ResizeNotifier to know when middle column has changed size
|
||||
*/
|
||||
resizeNotifier?: ResizeNotifier;
|
||||
|
||||
/* fixedChildren: allows for children to be passed which are rendered outside
|
||||
* of the wrapper
|
||||
*/
|
||||
fixedChildren?: ReactNode;
|
||||
|
||||
/* onFillRequest(backwards): a callback which is called on scroll when
|
||||
* the user nears the start (backwards = true) or end (backwards =
|
||||
* false) of the list.
|
||||
*
|
||||
* This should return a promise; no more calls will be made until the
|
||||
* promise completes.
|
||||
*
|
||||
* The promise should resolve to true if there is more data to be
|
||||
* retrieved in this direction (in which case onFillRequest may be
|
||||
* called again immediately), or false if there is no more data in this
|
||||
* directon (at this time) - which will stop the pagination cycle until
|
||||
* the user scrolls again.
|
||||
*/
|
||||
onFillRequest?(backwards: boolean): Promise<boolean>;
|
||||
|
||||
/* onUnfillRequest(backwards): a callback which is called on scroll when
|
||||
* there are children elements that are far out of view and could be removed
|
||||
* without causing pagination to occur.
|
||||
*
|
||||
* This function should accept a boolean, which is true to indicate the back/top
|
||||
* of the panel and false otherwise, and a scroll token, which refers to the
|
||||
* first element to remove if removing from the front/bottom, and last element
|
||||
* to remove if removing from the back/top.
|
||||
*/
|
||||
onUnfillRequest?(backwards: boolean, scrollToken: string): void;
|
||||
|
||||
/* onScroll: a callback which is called whenever any scroll happens.
|
||||
*/
|
||||
onScroll?(event: Event): void;
|
||||
|
||||
/* onUserScroll: callback which is called when the user interacts with the room timeline
|
||||
*/
|
||||
onUserScroll?(event: SyntheticEvent): void;
|
||||
}
|
||||
|
||||
/* This component implements an intelligent scrolling list.
|
||||
*
|
||||
* It wraps a list of <li> children; when items are added to the start or end
|
||||
|
@ -84,97 +153,54 @@ if (DEBUG_SCROLL) {
|
|||
* offset as normal.
|
||||
*/
|
||||
|
||||
export interface IScrollState {
|
||||
stuckAtBottom: boolean;
|
||||
trackedNode?: HTMLElement;
|
||||
trackedScrollToken?: string;
|
||||
bottomOffset?: number;
|
||||
pixelOffset?: number;
|
||||
}
|
||||
|
||||
interface IPreventShrinkingState {
|
||||
offsetFromBottom: number;
|
||||
offsetNode: HTMLElement;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.ScrollPanel")
|
||||
export default class ScrollPanel extends React.Component {
|
||||
static propTypes = {
|
||||
/* stickyBottom: if set to true, then once the user hits the bottom of
|
||||
* the list, any new children added to the list will cause the list to
|
||||
* scroll down to show the new element, rather than preserving the
|
||||
* existing view.
|
||||
*/
|
||||
stickyBottom: PropTypes.bool,
|
||||
|
||||
/* startAtBottom: if set to true, the view is assumed to start
|
||||
* scrolled to the bottom.
|
||||
* XXX: It's likely this is unnecessary and can be derived from
|
||||
* stickyBottom, but I'm adding an extra parameter to ensure
|
||||
* behaviour stays the same for other uses of ScrollPanel.
|
||||
* If so, let's remove this parameter down the line.
|
||||
*/
|
||||
startAtBottom: PropTypes.bool,
|
||||
|
||||
/* onFillRequest(backwards): a callback which is called on scroll when
|
||||
* the user nears the start (backwards = true) or end (backwards =
|
||||
* false) of the list.
|
||||
*
|
||||
* This should return a promise; no more calls will be made until the
|
||||
* promise completes.
|
||||
*
|
||||
* The promise should resolve to true if there is more data to be
|
||||
* retrieved in this direction (in which case onFillRequest may be
|
||||
* called again immediately), or false if there is no more data in this
|
||||
* directon (at this time) - which will stop the pagination cycle until
|
||||
* the user scrolls again.
|
||||
*/
|
||||
onFillRequest: PropTypes.func,
|
||||
|
||||
/* onUnfillRequest(backwards): a callback which is called on scroll when
|
||||
* there are children elements that are far out of view and could be removed
|
||||
* without causing pagination to occur.
|
||||
*
|
||||
* This function should accept a boolean, which is true to indicate the back/top
|
||||
* of the panel and false otherwise, and a scroll token, which refers to the
|
||||
* first element to remove if removing from the front/bottom, and last element
|
||||
* to remove if removing from the back/top.
|
||||
*/
|
||||
onUnfillRequest: PropTypes.func,
|
||||
|
||||
/* onScroll: a callback which is called whenever any scroll happens.
|
||||
*/
|
||||
onScroll: PropTypes.func,
|
||||
|
||||
/* onUserScroll: callback which is called when the user interacts with the room timeline
|
||||
*/
|
||||
onUserScroll: PropTypes.func,
|
||||
|
||||
/* className: classnames to add to the top-level div
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
|
||||
/* style: styles to add to the top-level div
|
||||
*/
|
||||
style: PropTypes.object,
|
||||
|
||||
/* resizeNotifier: ResizeNotifier to know when middle column has changed size
|
||||
*/
|
||||
resizeNotifier: PropTypes.object,
|
||||
|
||||
/* fixedChildren: allows for children to be passed which are rendered outside
|
||||
* of the wrapper
|
||||
*/
|
||||
fixedChildren: PropTypes.node,
|
||||
};
|
||||
|
||||
export default class ScrollPanel extends React.Component<IProps> {
|
||||
static defaultProps = {
|
||||
stickyBottom: true,
|
||||
startAtBottom: true,
|
||||
onFillRequest: function(backwards) { return Promise.resolve(false); },
|
||||
onUnfillRequest: function(backwards, scrollToken) {},
|
||||
onFillRequest: function(backwards: boolean) { return Promise.resolve(false); },
|
||||
onUnfillRequest: function(backwards: boolean, scrollToken: string) {},
|
||||
onScroll: function() {},
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
private readonly pendingFillRequests: Record<"b" | "f", boolean> = {
|
||||
b: null,
|
||||
f: null,
|
||||
};
|
||||
private readonly itemlist = createRef<HTMLOListElement>();
|
||||
private unmounted = false;
|
||||
private scrollTimeout: Timer;
|
||||
private isFilling: boolean;
|
||||
private fillRequestWhileRunning: boolean;
|
||||
private scrollState: IScrollState;
|
||||
private preventShrinkingState: IPreventShrinkingState;
|
||||
private unfillDebouncer: NodeJS.Timeout;
|
||||
private bottomGrowth: number;
|
||||
private pages: number;
|
||||
private heightUpdateInProgress: boolean;
|
||||
private divScroll: HTMLDivElement;
|
||||
|
||||
this._pendingFillRequests = {b: null, f: null};
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
if (this.props.resizeNotifier) {
|
||||
this.props.resizeNotifier.on("middlePanelResizedNoisy", this.onResize);
|
||||
}
|
||||
|
||||
this.resetScrollState();
|
||||
|
||||
this._itemlist = createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -203,18 +229,18 @@ export default class ScrollPanel extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
onScroll = ev => {
|
||||
private onScroll = ev => {
|
||||
// skip scroll events caused by resizing
|
||||
if (this.props.resizeNotifier && this.props.resizeNotifier.isResizing) return;
|
||||
debuglog("onScroll", this._getScrollNode().scrollTop);
|
||||
this._scrollTimeout.restart();
|
||||
this._saveScrollState();
|
||||
debuglog("onScroll", this.getScrollNode().scrollTop);
|
||||
this.scrollTimeout.restart();
|
||||
this.saveScrollState();
|
||||
this.updatePreventShrinking();
|
||||
this.props.onScroll(ev);
|
||||
this.checkFillState();
|
||||
};
|
||||
|
||||
onResize = () => {
|
||||
private onResize = () => {
|
||||
debuglog("onResize");
|
||||
this.checkScroll();
|
||||
// update preventShrinkingState if present
|
||||
|
@ -225,11 +251,11 @@ export default class ScrollPanel extends React.Component {
|
|||
|
||||
// after an update to the contents of the panel, check that the scroll is
|
||||
// where it ought to be, and set off pagination requests if necessary.
|
||||
checkScroll = () => {
|
||||
public checkScroll = () => {
|
||||
if (this.unmounted) {
|
||||
return;
|
||||
}
|
||||
this._restoreSavedScrollState();
|
||||
this.restoreSavedScrollState();
|
||||
this.checkFillState();
|
||||
};
|
||||
|
||||
|
@ -238,8 +264,8 @@ export default class ScrollPanel extends React.Component {
|
|||
// note that this is independent of the 'stuckAtBottom' state - it is simply
|
||||
// about whether the content is scrolled down right now, irrespective of
|
||||
// whether it will stay that way when the children update.
|
||||
isAtBottom = () => {
|
||||
const sn = this._getScrollNode();
|
||||
public isAtBottom = () => {
|
||||
const sn = this.getScrollNode();
|
||||
// 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.
|
||||
|
@ -278,10 +304,10 @@ export default class ScrollPanel extends React.Component {
|
|||
// |#########| - |
|
||||
// |#########| |
|
||||
// `---------' -
|
||||
_getExcessHeight(backwards) {
|
||||
const sn = this._getScrollNode();
|
||||
const contentHeight = this._getMessagesHeight();
|
||||
const listHeight = this._getListHeight();
|
||||
private getExcessHeight(backwards: boolean): number {
|
||||
const sn = this.getScrollNode();
|
||||
const contentHeight = this.getMessagesHeight();
|
||||
const listHeight = this.getListHeight();
|
||||
const clippedHeight = contentHeight - listHeight;
|
||||
const unclippedScrollTop = sn.scrollTop + clippedHeight;
|
||||
|
||||
|
@ -293,13 +319,13 @@ export default class ScrollPanel extends React.Component {
|
|||
}
|
||||
|
||||
// check the scroll state and send out backfill requests if necessary.
|
||||
checkFillState = async (depth=0) => {
|
||||
public checkFillState = async (depth = 0): Promise<void> => {
|
||||
if (this.unmounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isFirstCall = depth === 0;
|
||||
const sn = this._getScrollNode();
|
||||
const sn = this.getScrollNode();
|
||||
|
||||
// if there is less than a screenful of messages above or below the
|
||||
// viewport, try to get some more messages.
|
||||
|
@ -330,17 +356,17 @@ export default class ScrollPanel extends React.Component {
|
|||
// do make a note when a new request comes in while already running one,
|
||||
// so we can trigger a new chain of calls once done.
|
||||
if (isFirstCall) {
|
||||
if (this._isFilling) {
|
||||
debuglog("_isFilling: not entering while request is ongoing, marking for a subsequent request");
|
||||
this._fillRequestWhileRunning = true;
|
||||
if (this.isFilling) {
|
||||
debuglog("isFilling: not entering while request is ongoing, marking for a subsequent request");
|
||||
this.fillRequestWhileRunning = true;
|
||||
return;
|
||||
}
|
||||
debuglog("_isFilling: setting");
|
||||
this._isFilling = true;
|
||||
debuglog("isFilling: setting");
|
||||
this.isFilling = true;
|
||||
}
|
||||
|
||||
const itemlist = this._itemlist.current;
|
||||
const firstTile = itemlist && itemlist.firstElementChild;
|
||||
const itemlist = this.itemlist.current;
|
||||
const firstTile = itemlist && itemlist.firstElementChild as HTMLElement;
|
||||
const contentTop = firstTile && firstTile.offsetTop;
|
||||
const fillPromises = [];
|
||||
|
||||
|
@ -348,13 +374,13 @@ export default class ScrollPanel extends React.Component {
|
|||
// try backward filling
|
||||
if (!firstTile || (sn.scrollTop - contentTop) < sn.clientHeight) {
|
||||
// need to back-fill
|
||||
fillPromises.push(this._maybeFill(depth, true));
|
||||
fillPromises.push(this.maybeFill(depth, true));
|
||||
}
|
||||
// if scrollTop gets to 2 screens from the end (so 1 screen below viewport),
|
||||
// try forward filling
|
||||
if ((sn.scrollHeight - sn.scrollTop) < sn.clientHeight * 2) {
|
||||
// need to forward-fill
|
||||
fillPromises.push(this._maybeFill(depth, false));
|
||||
fillPromises.push(this.maybeFill(depth, false));
|
||||
}
|
||||
|
||||
if (fillPromises.length) {
|
||||
|
@ -365,26 +391,26 @@ export default class ScrollPanel extends React.Component {
|
|||
}
|
||||
}
|
||||
if (isFirstCall) {
|
||||
debuglog("_isFilling: clearing");
|
||||
this._isFilling = false;
|
||||
debuglog("isFilling: clearing");
|
||||
this.isFilling = false;
|
||||
}
|
||||
|
||||
if (this._fillRequestWhileRunning) {
|
||||
this._fillRequestWhileRunning = false;
|
||||
if (this.fillRequestWhileRunning) {
|
||||
this.fillRequestWhileRunning = false;
|
||||
this.checkFillState();
|
||||
}
|
||||
};
|
||||
|
||||
// check if unfilling is possible and send an unfill request if necessary
|
||||
_checkUnfillState(backwards) {
|
||||
let excessHeight = this._getExcessHeight(backwards);
|
||||
private checkUnfillState(backwards: boolean): void {
|
||||
let excessHeight = this.getExcessHeight(backwards);
|
||||
if (excessHeight <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const origExcessHeight = excessHeight;
|
||||
|
||||
const tiles = this._itemlist.current.children;
|
||||
const tiles = this.itemlist.current.children;
|
||||
|
||||
// The scroll token of the first/last tile to be unpaginated
|
||||
let markerScrollToken = null;
|
||||
|
@ -413,11 +439,11 @@ export default class ScrollPanel extends React.Component {
|
|||
if (markerScrollToken) {
|
||||
// Use a debouncer to prevent multiple unfill calls in quick succession
|
||||
// This is to make the unfilling process less aggressive
|
||||
if (this._unfillDebouncer) {
|
||||
clearTimeout(this._unfillDebouncer);
|
||||
if (this.unfillDebouncer) {
|
||||
clearTimeout(this.unfillDebouncer);
|
||||
}
|
||||
this._unfillDebouncer = setTimeout(() => {
|
||||
this._unfillDebouncer = null;
|
||||
this.unfillDebouncer = setTimeout(() => {
|
||||
this.unfillDebouncer = null;
|
||||
debuglog("unfilling now", backwards, origExcessHeight);
|
||||
this.props.onUnfillRequest(backwards, markerScrollToken);
|
||||
}, UNFILL_REQUEST_DEBOUNCE_MS);
|
||||
|
@ -425,9 +451,9 @@ export default class ScrollPanel extends React.Component {
|
|||
}
|
||||
|
||||
// check if there is already a pending fill request. If not, set one off.
|
||||
_maybeFill(depth, backwards) {
|
||||
private maybeFill(depth: number, backwards: boolean): Promise<void> {
|
||||
const dir = backwards ? 'b' : 'f';
|
||||
if (this._pendingFillRequests[dir]) {
|
||||
if (this.pendingFillRequests[dir]) {
|
||||
debuglog("Already a "+dir+" fill in progress - not starting another");
|
||||
return;
|
||||
}
|
||||
|
@ -436,7 +462,7 @@ export default class ScrollPanel extends React.Component {
|
|||
|
||||
// onFillRequest can end up calling us recursively (via onScroll
|
||||
// events) so make sure we set this before firing off the call.
|
||||
this._pendingFillRequests[dir] = true;
|
||||
this.pendingFillRequests[dir] = true;
|
||||
|
||||
// wait 1ms before paginating, because otherwise
|
||||
// this will block the scroll event handler for +700ms
|
||||
|
@ -445,13 +471,13 @@ export default class ScrollPanel extends React.Component {
|
|||
return new Promise(resolve => setTimeout(resolve, 1)).then(() => {
|
||||
return this.props.onFillRequest(backwards);
|
||||
}).finally(() => {
|
||||
this._pendingFillRequests[dir] = false;
|
||||
this.pendingFillRequests[dir] = false;
|
||||
}).then((hasMoreResults) => {
|
||||
if (this.unmounted) {
|
||||
return;
|
||||
}
|
||||
// Unpaginate once filling is complete
|
||||
this._checkUnfillState(!backwards);
|
||||
this.checkUnfillState(!backwards);
|
||||
|
||||
debuglog(""+dir+" fill complete; hasMoreResults:"+hasMoreResults);
|
||||
if (hasMoreResults) {
|
||||
|
@ -477,7 +503,7 @@ export default class ScrollPanel extends React.Component {
|
|||
* the number of pixels the bottom of the tracked child is above the
|
||||
* bottom of the scroll panel.
|
||||
*/
|
||||
getScrollState = () => this.scrollState;
|
||||
public getScrollState = (): IScrollState => this.scrollState;
|
||||
|
||||
/* reset the saved scroll state.
|
||||
*
|
||||
|
@ -491,35 +517,35 @@ export default class ScrollPanel extends React.Component {
|
|||
* no use if no children exist yet, or if you are about to replace the
|
||||
* child list.)
|
||||
*/
|
||||
resetScrollState = () => {
|
||||
public resetScrollState = (): void => {
|
||||
this.scrollState = {
|
||||
stuckAtBottom: this.props.startAtBottom,
|
||||
};
|
||||
this._bottomGrowth = 0;
|
||||
this._pages = 0;
|
||||
this._scrollTimeout = new Timer(100);
|
||||
this._heightUpdateInProgress = false;
|
||||
this.bottomGrowth = 0;
|
||||
this.pages = 0;
|
||||
this.scrollTimeout = new Timer(100);
|
||||
this.heightUpdateInProgress = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* jump to the top of the content.
|
||||
*/
|
||||
scrollToTop = () => {
|
||||
this._getScrollNode().scrollTop = 0;
|
||||
this._saveScrollState();
|
||||
public scrollToTop = (): void => {
|
||||
this.getScrollNode().scrollTop = 0;
|
||||
this.saveScrollState();
|
||||
};
|
||||
|
||||
/**
|
||||
* jump to the bottom of the content.
|
||||
*/
|
||||
scrollToBottom = () => {
|
||||
public scrollToBottom = (): void => {
|
||||
// the easiest way to make sure that the scroll state is correctly
|
||||
// saved is to do the scroll, then save the updated state. (Calculating
|
||||
// it ourselves is hard, and we can't rely on an onScroll callback
|
||||
// happening, since there may be no user-visible change here).
|
||||
const sn = this._getScrollNode();
|
||||
const sn = this.getScrollNode();
|
||||
sn.scrollTop = sn.scrollHeight;
|
||||
this._saveScrollState();
|
||||
this.saveScrollState();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -527,18 +553,18 @@ export default class ScrollPanel extends React.Component {
|
|||
*
|
||||
* @param {number} mult: -1 to page up, +1 to page down
|
||||
*/
|
||||
scrollRelative = mult => {
|
||||
const scrollNode = this._getScrollNode();
|
||||
public scrollRelative = (mult: number): void => {
|
||||
const scrollNode = this.getScrollNode();
|
||||
const delta = mult * scrollNode.clientHeight * 0.9;
|
||||
scrollNode.scrollBy(0, delta);
|
||||
this._saveScrollState();
|
||||
this.saveScrollState();
|
||||
};
|
||||
|
||||
/**
|
||||
* Scroll up/down in response to a scroll key
|
||||
* @param {object} ev the keyboard event
|
||||
*/
|
||||
handleScrollKey = ev => {
|
||||
public handleScrollKey = (ev: KeyboardEvent) => {
|
||||
let isScrolling = false;
|
||||
const roomAction = getKeyBindingsManager().getRoomAction(ev);
|
||||
switch (roomAction) {
|
||||
|
@ -575,17 +601,17 @@ export default class ScrollPanel extends React.Component {
|
|||
* node (specifically, the bottom of it) will be positioned. If omitted, it
|
||||
* defaults to 0.
|
||||
*/
|
||||
scrollToToken = (scrollToken, pixelOffset, offsetBase) => {
|
||||
public scrollToToken = (scrollToken: string, pixelOffset: number, offsetBase: number): void => {
|
||||
pixelOffset = pixelOffset || 0;
|
||||
offsetBase = offsetBase || 0;
|
||||
|
||||
// set the trackedScrollToken so we can get the node through _getTrackedNode
|
||||
// set the trackedScrollToken so we can get the node through getTrackedNode
|
||||
this.scrollState = {
|
||||
stuckAtBottom: false,
|
||||
trackedScrollToken: scrollToken,
|
||||
};
|
||||
const trackedNode = this._getTrackedNode();
|
||||
const scrollNode = this._getScrollNode();
|
||||
const trackedNode = this.getTrackedNode();
|
||||
const scrollNode = this.getScrollNode();
|
||||
if (trackedNode) {
|
||||
// set the scrollTop to the position we want.
|
||||
// note though, that this might not succeed if the combination of offsetBase and pixelOffset
|
||||
|
@ -593,36 +619,36 @@ export default class ScrollPanel extends React.Component {
|
|||
// This because when setting the scrollTop only 10 or so events might be loaded,
|
||||
// not giving enough content below the trackedNode to scroll downwards
|
||||
// enough so it ends up in the top of the viewport.
|
||||
debuglog("scrollToken: setting scrollTop", {offsetBase, pixelOffset, offsetTop: trackedNode.offsetTop});
|
||||
debuglog("scrollToken: setting scrollTop", { offsetBase, pixelOffset, offsetTop: trackedNode.offsetTop });
|
||||
scrollNode.scrollTop = (trackedNode.offsetTop - (scrollNode.clientHeight * offsetBase)) + pixelOffset;
|
||||
this._saveScrollState();
|
||||
this.saveScrollState();
|
||||
}
|
||||
};
|
||||
|
||||
_saveScrollState() {
|
||||
private saveScrollState(): void {
|
||||
if (this.props.stickyBottom && this.isAtBottom()) {
|
||||
this.scrollState = { stuckAtBottom: true };
|
||||
debuglog("saved stuckAtBottom state");
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollNode = this._getScrollNode();
|
||||
const scrollNode = this.getScrollNode();
|
||||
const viewportBottom = scrollNode.scrollHeight - (scrollNode.scrollTop + scrollNode.clientHeight);
|
||||
|
||||
const itemlist = this._itemlist.current;
|
||||
const itemlist = this.itemlist.current;
|
||||
const messages = itemlist.children;
|
||||
let node = null;
|
||||
|
||||
// TODO: do a binary search here, as items are sorted by offsetTop
|
||||
// loop backwards, from bottom-most message (as that is the most common case)
|
||||
for (let i = messages.length-1; i >= 0; --i) {
|
||||
if (!messages[i].dataset.scrollTokens) {
|
||||
for (let i = messages.length - 1; i >= 0; --i) {
|
||||
if (!(messages[i] as HTMLElement).dataset.scrollTokens) {
|
||||
continue;
|
||||
}
|
||||
node = messages[i];
|
||||
// break at the first message (coming from the bottom)
|
||||
// that has it's offsetTop above the bottom of the viewport.
|
||||
if (this._topFromBottom(node) > viewportBottom) {
|
||||
if (this.topFromBottom(node) > viewportBottom) {
|
||||
// Use this node as the scrollToken
|
||||
break;
|
||||
}
|
||||
|
@ -634,7 +660,7 @@ export default class ScrollPanel extends React.Component {
|
|||
}
|
||||
const scrollToken = node.dataset.scrollTokens.split(',')[0];
|
||||
debuglog("saving anchored scroll state to message", node && node.innerText, scrollToken);
|
||||
const bottomOffset = this._topFromBottom(node);
|
||||
const bottomOffset = this.topFromBottom(node);
|
||||
this.scrollState = {
|
||||
stuckAtBottom: false,
|
||||
trackedNode: node,
|
||||
|
@ -644,35 +670,35 @@ export default class ScrollPanel extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
async _restoreSavedScrollState() {
|
||||
private async restoreSavedScrollState(): Promise<void> {
|
||||
const scrollState = this.scrollState;
|
||||
|
||||
if (scrollState.stuckAtBottom) {
|
||||
const sn = this._getScrollNode();
|
||||
const sn = this.getScrollNode();
|
||||
if (sn.scrollTop !== sn.scrollHeight) {
|
||||
sn.scrollTop = sn.scrollHeight;
|
||||
}
|
||||
} else if (scrollState.trackedScrollToken) {
|
||||
const itemlist = this._itemlist.current;
|
||||
const trackedNode = this._getTrackedNode();
|
||||
const itemlist = this.itemlist.current;
|
||||
const trackedNode = this.getTrackedNode();
|
||||
if (trackedNode) {
|
||||
const newBottomOffset = this._topFromBottom(trackedNode);
|
||||
const newBottomOffset = this.topFromBottom(trackedNode);
|
||||
const bottomDiff = newBottomOffset - scrollState.bottomOffset;
|
||||
this._bottomGrowth += bottomDiff;
|
||||
this.bottomGrowth += bottomDiff;
|
||||
scrollState.bottomOffset = newBottomOffset;
|
||||
const newHeight = `${this._getListHeight()}px`;
|
||||
const newHeight = `${this.getListHeight()}px`;
|
||||
if (itemlist.style.height !== newHeight) {
|
||||
itemlist.style.height = newHeight;
|
||||
}
|
||||
debuglog("balancing height because messages below viewport grew by", bottomDiff);
|
||||
}
|
||||
}
|
||||
if (!this._heightUpdateInProgress) {
|
||||
this._heightUpdateInProgress = true;
|
||||
if (!this.heightUpdateInProgress) {
|
||||
this.heightUpdateInProgress = true;
|
||||
try {
|
||||
await this._updateHeight();
|
||||
await this.updateHeight();
|
||||
} finally {
|
||||
this._heightUpdateInProgress = false;
|
||||
this.heightUpdateInProgress = false;
|
||||
}
|
||||
} else {
|
||||
debuglog("not updating height because request already in progress");
|
||||
|
@ -680,11 +706,11 @@ export default class ScrollPanel extends React.Component {
|
|||
}
|
||||
|
||||
// need a better name that also indicates this will change scrollTop? Rebalance height? Reveal content?
|
||||
async _updateHeight() {
|
||||
private async updateHeight(): Promise<void> {
|
||||
// wait until user has stopped scrolling
|
||||
if (this._scrollTimeout.isRunning()) {
|
||||
if (this.scrollTimeout.isRunning()) {
|
||||
debuglog("updateHeight waiting for scrolling to end ... ");
|
||||
await this._scrollTimeout.finished();
|
||||
await this.scrollTimeout.finished();
|
||||
} else {
|
||||
debuglog("updateHeight getting straight to business, no scrolling going on.");
|
||||
}
|
||||
|
@ -694,14 +720,14 @@ export default class ScrollPanel extends React.Component {
|
|||
return;
|
||||
}
|
||||
|
||||
const sn = this._getScrollNode();
|
||||
const itemlist = this._itemlist.current;
|
||||
const contentHeight = this._getMessagesHeight();
|
||||
const sn = this.getScrollNode();
|
||||
const itemlist = this.itemlist.current;
|
||||
const contentHeight = this.getMessagesHeight();
|
||||
const minHeight = sn.clientHeight;
|
||||
const height = Math.max(minHeight, contentHeight);
|
||||
this._pages = Math.ceil(height / PAGE_SIZE);
|
||||
this._bottomGrowth = 0;
|
||||
const newHeight = `${this._getListHeight()}px`;
|
||||
this.pages = Math.ceil(height / PAGE_SIZE);
|
||||
this.bottomGrowth = 0;
|
||||
const newHeight = `${this.getListHeight()}px`;
|
||||
|
||||
const scrollState = this.scrollState;
|
||||
if (scrollState.stuckAtBottom) {
|
||||
|
@ -713,7 +739,7 @@ export default class ScrollPanel extends React.Component {
|
|||
}
|
||||
debuglog("updateHeight to", newHeight);
|
||||
} else if (scrollState.trackedScrollToken) {
|
||||
const trackedNode = this._getTrackedNode();
|
||||
const trackedNode = this.getTrackedNode();
|
||||
// if the timeline has been reloaded
|
||||
// this can be called before scrollToBottom or whatever has been called
|
||||
// so don't do anything if the node has disappeared from
|
||||
|
@ -730,22 +756,22 @@ export default class ScrollPanel extends React.Component {
|
|||
// yield out of date values and cause a jump
|
||||
// when setting it
|
||||
sn.scrollBy(0, topDiff);
|
||||
debuglog("updateHeight to", {newHeight, topDiff});
|
||||
debuglog("updateHeight to", { newHeight, topDiff });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_getTrackedNode() {
|
||||
private getTrackedNode(): HTMLElement {
|
||||
const scrollState = this.scrollState;
|
||||
const trackedNode = scrollState.trackedNode;
|
||||
|
||||
if (!trackedNode || !trackedNode.parentElement) {
|
||||
let node;
|
||||
const messages = this._itemlist.current.children;
|
||||
const messages = this.itemlist.current.children;
|
||||
const scrollToken = scrollState.trackedScrollToken;
|
||||
|
||||
for (let i = messages.length-1; i >= 0; --i) {
|
||||
const m = messages[i];
|
||||
const m = messages[i] as HTMLElement;
|
||||
// 'data-scroll-tokens' is a DOMString of comma-separated scroll tokens
|
||||
// There might only be one scroll token
|
||||
if (m.dataset.scrollTokens &&
|
||||
|
@ -768,45 +794,45 @@ export default class ScrollPanel extends React.Component {
|
|||
return scrollState.trackedNode;
|
||||
}
|
||||
|
||||
_getListHeight() {
|
||||
return this._bottomGrowth + (this._pages * PAGE_SIZE);
|
||||
private getListHeight(): number {
|
||||
return this.bottomGrowth + (this.pages * PAGE_SIZE);
|
||||
}
|
||||
|
||||
_getMessagesHeight() {
|
||||
const itemlist = this._itemlist.current;
|
||||
const lastNode = itemlist.lastElementChild;
|
||||
private getMessagesHeight(): number {
|
||||
const itemlist = this.itemlist.current;
|
||||
const lastNode = itemlist.lastElementChild as HTMLElement;
|
||||
const lastNodeBottom = lastNode ? lastNode.offsetTop + lastNode.clientHeight : 0;
|
||||
const firstNodeTop = itemlist.firstElementChild ? itemlist.firstElementChild.offsetTop : 0;
|
||||
const firstNodeTop = itemlist.firstElementChild ? (itemlist.firstElementChild as HTMLElement).offsetTop : 0;
|
||||
// 18 is itemlist padding
|
||||
return lastNodeBottom - firstNodeTop + (18 * 2);
|
||||
}
|
||||
|
||||
_topFromBottom(node) {
|
||||
private topFromBottom(node: HTMLElement): number {
|
||||
// current capped height - distance from top = distance from bottom of container to top of tracked element
|
||||
return this._itemlist.current.clientHeight - node.offsetTop;
|
||||
return this.itemlist.current.clientHeight - node.offsetTop;
|
||||
}
|
||||
|
||||
/* get the DOM node which has the scrollTop property we care about for our
|
||||
* message panel.
|
||||
*/
|
||||
_getScrollNode() {
|
||||
private getScrollNode(): HTMLDivElement {
|
||||
if (this.unmounted) {
|
||||
// this shouldn't happen, but when it does, turn the NPE into
|
||||
// something more meaningful.
|
||||
throw new Error("ScrollPanel._getScrollNode called when unmounted");
|
||||
throw new Error("ScrollPanel.getScrollNode called when unmounted");
|
||||
}
|
||||
|
||||
if (!this._divScroll) {
|
||||
if (!this.divScroll) {
|
||||
// Likewise, we should have the ref by this point, but if not
|
||||
// turn the NPE into something meaningful.
|
||||
throw new Error("ScrollPanel._getScrollNode called before AutoHideScrollbar ref collected");
|
||||
throw new Error("ScrollPanel.getScrollNode called before AutoHideScrollbar ref collected");
|
||||
}
|
||||
|
||||
return this._divScroll;
|
||||
return this.divScroll;
|
||||
}
|
||||
|
||||
_collectScroll = divScroll => {
|
||||
this._divScroll = divScroll;
|
||||
private collectScroll = (divScroll: HTMLDivElement) => {
|
||||
this.divScroll = divScroll;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -814,15 +840,15 @@ export default class ScrollPanel extends React.Component {
|
|||
anything below it changes, by calling updatePreventShrinking, to keep
|
||||
the same minimum bottom offset, effectively preventing the timeline to shrink.
|
||||
*/
|
||||
preventShrinking = () => {
|
||||
const messageList = this._itemlist.current;
|
||||
public preventShrinking = (): void => {
|
||||
const messageList = this.itemlist.current;
|
||||
const tiles = messageList && messageList.children;
|
||||
if (!messageList) {
|
||||
return;
|
||||
}
|
||||
let lastTileNode;
|
||||
for (let i = tiles.length - 1; i >= 0; i--) {
|
||||
const node = tiles[i];
|
||||
const node = tiles[i] as HTMLElement;
|
||||
if (node.dataset.scrollTokens) {
|
||||
lastTileNode = node;
|
||||
break;
|
||||
|
@ -841,8 +867,8 @@ export default class ScrollPanel extends React.Component {
|
|||
};
|
||||
|
||||
/** Clear shrinking prevention. Used internally, and when the timeline is reloaded. */
|
||||
clearPreventShrinking = () => {
|
||||
const messageList = this._itemlist.current;
|
||||
public clearPreventShrinking = (): void => {
|
||||
const messageList = this.itemlist.current;
|
||||
const balanceElement = messageList && messageList.parentElement;
|
||||
if (balanceElement) balanceElement.style.paddingBottom = null;
|
||||
this.preventShrinkingState = null;
|
||||
|
@ -857,12 +883,12 @@ export default class ScrollPanel extends React.Component {
|
|||
from the bottom of the marked tile grows larger than
|
||||
what it was when marking.
|
||||
*/
|
||||
updatePreventShrinking = () => {
|
||||
public updatePreventShrinking = (): void => {
|
||||
if (this.preventShrinkingState) {
|
||||
const sn = this._getScrollNode();
|
||||
const sn = this.getScrollNode();
|
||||
const scrollState = this.scrollState;
|
||||
const messageList = this._itemlist.current;
|
||||
const {offsetNode, offsetFromBottom} = this.preventShrinkingState;
|
||||
const messageList = this.itemlist.current;
|
||||
const { offsetNode, offsetFromBottom } = this.preventShrinkingState;
|
||||
// element used to set paddingBottom to balance the typing notifs disappearing
|
||||
const balanceElement = messageList.parentElement;
|
||||
// if the offsetNode got unmounted, clear
|
||||
|
@ -898,13 +924,15 @@ export default class ScrollPanel extends React.Component {
|
|||
// list-style-type: none; is no longer a list
|
||||
return (
|
||||
<AutoHideScrollbar
|
||||
wrappedRef={this._collectScroll}
|
||||
wrappedRef={this.collectScroll}
|
||||
onScroll={this.onScroll}
|
||||
onWheel={this.props.onUserScroll}
|
||||
className={`mx_ScrollPanel ${this.props.className}`} style={this.props.style}>
|
||||
className={`mx_ScrollPanel ${this.props.className}`}
|
||||
style={this.props.style}
|
||||
>
|
||||
{ this.props.fixedChildren }
|
||||
<div className="mx_RoomView_messageListWrapper">
|
||||
<ol ref={this._itemlist} className="mx_RoomView_MessageList" aria-live="polite" role="list">
|
||||
<ol ref={this.itemlist} className="mx_RoomView_MessageList" aria-live="polite" role="list">
|
||||
{ this.props.children }
|
||||
</ol>
|
||||
</div>
|
|
@ -15,14 +15,14 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {createRef} from 'react';
|
||||
import React, { createRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Key } from '../../Keyboard';
|
||||
import dis from '../../dispatcher/dispatcher';
|
||||
import {throttle} from 'lodash';
|
||||
import { throttle } from 'lodash';
|
||||
import AccessibleButton from '../../components/views/elements/AccessibleButton';
|
||||
import classNames from 'classnames';
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("structures.SearchBox")
|
||||
export default class SearchBox extends React.Component {
|
||||
|
@ -89,7 +89,7 @@ export default class SearchBox extends React.Component {
|
|||
|
||||
onSearch = throttle(() => {
|
||||
this.props.onSearch(this._search.current.value);
|
||||
}, 200, {trailing: true, leading: true});
|
||||
}, 200, { trailing: true, leading: true });
|
||||
|
||||
_onKeyDown = ev => {
|
||||
switch (ev.key) {
|
||||
|
@ -101,7 +101,7 @@ export default class SearchBox extends React.Component {
|
|||
};
|
||||
|
||||
_onFocus = ev => {
|
||||
this.setState({blurred: false});
|
||||
this.setState({ blurred: false });
|
||||
ev.target.select();
|
||||
if (this.props.onFocus) {
|
||||
this.props.onFocus(ev);
|
||||
|
@ -109,7 +109,7 @@ export default class SearchBox extends React.Component {
|
|||
};
|
||||
|
||||
_onBlur = ev => {
|
||||
this.setState({blurred: true});
|
||||
this.setState({ blurred: true });
|
||||
if (this.props.onBlur) {
|
||||
this.props.onBlur(ev);
|
||||
}
|
||||
|
@ -147,7 +147,7 @@ export default class SearchBox extends React.Component {
|
|||
this.props.placeholder;
|
||||
const className = this.props.className || "";
|
||||
return (
|
||||
<div className={classNames("mx_SearchBox", "mx_textinput", {"mx_SearchBox_blurred": this.state.blurred})}>
|
||||
<div className={classNames("mx_SearchBox", "mx_textinput", { "mx_SearchBox_blurred": this.state.blurred })}>
|
||||
<input
|
||||
key="searchfield"
|
||||
type="text"
|
||||
|
|
|
@ -58,7 +58,7 @@ export interface ISpaceSummaryRoom {
|
|||
avatar_url?: string;
|
||||
guest_can_join: boolean;
|
||||
name?: string;
|
||||
num_joined_members: number
|
||||
num_joined_members: number;
|
||||
room_id: string;
|
||||
topic?: string;
|
||||
world_readable: boolean;
|
||||
|
@ -112,12 +112,12 @@ const Tile: React.FC<ITileProps> = ({
|
|||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
onViewRoomClick(false);
|
||||
}
|
||||
};
|
||||
const onJoinClick = (ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
onViewRoomClick(true);
|
||||
}
|
||||
};
|
||||
|
||||
let button;
|
||||
if (joinedRoom) {
|
||||
|
@ -137,7 +137,7 @@ const Tile: React.FC<ITileProps> = ({
|
|||
} else {
|
||||
checkbox = <TextWithTooltip
|
||||
tooltip={_t("You don't have permission")}
|
||||
onClick={ev => { ev.stopPropagation() }}
|
||||
onClick={ev => { ev.stopPropagation(); }}
|
||||
>
|
||||
<StyledCheckbox disabled={true} />
|
||||
</TextWithTooltip>;
|
||||
|
@ -340,7 +340,7 @@ export const HierarchyLevel = ({
|
|||
</Tile>
|
||||
))
|
||||
}
|
||||
</React.Fragment>
|
||||
</React.Fragment>;
|
||||
};
|
||||
|
||||
// mutate argument refreshToken to force a reload
|
||||
|
@ -635,9 +635,9 @@ const SpaceRoomDirectory: React.FC<IProps> = ({ space, onFinished, initialText }
|
|||
<div className="mx_Dialog_content">
|
||||
{ _t("If you can't find the room you're looking for, ask for an invite or <a>create a new room</a>.",
|
||||
null,
|
||||
{a: sub => {
|
||||
{ a: sub => {
|
||||
return <AccessibleButton kind="link" onClick={onCreateRoomClick}>{sub}</AccessibleButton>;
|
||||
}},
|
||||
} },
|
||||
) }
|
||||
|
||||
<SpaceHierarchy
|
||||
|
|
|
@ -14,58 +14,60 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {RefObject, useContext, useRef, useState} from "react";
|
||||
import {EventType} from "matrix-js-sdk/src/@types/event";
|
||||
import {Room} from "matrix-js-sdk/src/models/room";
|
||||
import {EventSubscription} from "fbemitter";
|
||||
import React, { RefObject, useContext, useRef, useState } from "react";
|
||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
import { Preset } from "matrix-js-sdk/src/@types/partials";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { EventSubscription } from "fbemitter";
|
||||
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import RoomAvatar from "../views/avatars/RoomAvatar";
|
||||
import {_t} from "../../languageHandler";
|
||||
import { _t } from "../../languageHandler";
|
||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||
import RoomName from "../views/elements/RoomName";
|
||||
import RoomTopic from "../views/elements/RoomTopic";
|
||||
import InlineSpinner from "../views/elements/InlineSpinner";
|
||||
import {inviteMultipleToRoom, showRoomInviteDialog} from "../../RoomInvite";
|
||||
import {useRoomMembers} from "../../hooks/useRoomMembers";
|
||||
import createRoom, {IOpts} from "../../createRoom";
|
||||
import { inviteMultipleToRoom, showRoomInviteDialog } from "../../RoomInvite";
|
||||
import { useRoomMembers } from "../../hooks/useRoomMembers";
|
||||
import createRoom, { IOpts } from "../../createRoom";
|
||||
import Field from "../views/elements/Field";
|
||||
import {useEventEmitter} from "../../hooks/useEventEmitter";
|
||||
import { useEventEmitter } from "../../hooks/useEventEmitter";
|
||||
import withValidation from "../views/elements/Validation";
|
||||
import * as Email from "../../email";
|
||||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||
import {Action} from "../../dispatcher/actions";
|
||||
import ResizeNotifier from "../../utils/ResizeNotifier"
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||
import MainSplit from './MainSplit';
|
||||
import ErrorBoundary from "../views/elements/ErrorBoundary";
|
||||
import {ActionPayload} from "../../dispatcher/payloads";
|
||||
import { ActionPayload } from "../../dispatcher/payloads";
|
||||
import RightPanel from "./RightPanel";
|
||||
import RightPanelStore from "../../stores/RightPanelStore";
|
||||
import {RightPanelPhases} from "../../stores/RightPanelStorePhases";
|
||||
import {SetRightPanelPhasePayload} from "../../dispatcher/payloads/SetRightPanelPhasePayload";
|
||||
import {useStateArray} from "../../hooks/useStateArray";
|
||||
import { RightPanelPhases } from "../../stores/RightPanelStorePhases";
|
||||
import { SetRightPanelPhasePayload } from "../../dispatcher/payloads/SetRightPanelPhasePayload";
|
||||
import { useStateArray } from "../../hooks/useStateArray";
|
||||
import SpacePublicShare from "../views/spaces/SpacePublicShare";
|
||||
import {showAddExistingRooms, showCreateNewRoom, shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space";
|
||||
import {showRoom, SpaceHierarchy} from "./SpaceRoomDirectory";
|
||||
import { shouldShowSpaceSettings, showAddExistingRooms, showCreateNewRoom, showSpaceSettings } from "../../utils/space";
|
||||
import { showRoom, SpaceHierarchy } from "./SpaceRoomDirectory";
|
||||
import MemberAvatar from "../views/avatars/MemberAvatar";
|
||||
import {useStateToggle} from "../../hooks/useStateToggle";
|
||||
import { useStateToggle } from "../../hooks/useStateToggle";
|
||||
import SpaceStore from "../../stores/SpaceStore";
|
||||
import FacePile from "../views/elements/FacePile";
|
||||
import {AddExistingToSpace} from "../views/dialogs/AddExistingToSpaceDialog";
|
||||
import {ChevronFace, ContextMenuButton, useContextMenu} from "./ContextMenu";
|
||||
import { AddExistingToSpace } from "../views/dialogs/AddExistingToSpaceDialog";
|
||||
import { ChevronFace, ContextMenuButton, useContextMenu } from "./ContextMenu";
|
||||
import IconizedContextMenu, {
|
||||
IconizedContextMenuOption,
|
||||
IconizedContextMenuOptionList,
|
||||
} from "../views/context_menus/IconizedContextMenu";
|
||||
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
||||
import {BetaPill} from "../views/beta/BetaCard";
|
||||
import { BetaPill } from "../views/beta/BetaCard";
|
||||
import { UserTab } from "../views/dialogs/UserSettingsDialog";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import Modal from "../../Modal";
|
||||
import BetaFeedbackDialog from "../views/dialogs/BetaFeedbackDialog";
|
||||
import SdkConfig from "../../SdkConfig";
|
||||
import { Preset } from "matrix-js-sdk/src/@types/partials";
|
||||
import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership";
|
||||
import { JoinRule } from "../views/settings/tabs/room/SecurityRoomSettingsTab";
|
||||
|
||||
interface IProps {
|
||||
space: Room;
|
||||
|
@ -160,7 +162,7 @@ const SpaceInfo = ({ space }) => {
|
|||
</AccessibleButton>
|
||||
) : null}
|
||||
</RoomMemberCount> }
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
const onBetaClick = () => {
|
||||
|
@ -178,6 +180,9 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
|||
|
||||
const spacesEnabled = SettingsStore.getValue("feature_spaces");
|
||||
|
||||
const cannotJoin = getEffectiveMembership(myMembership) === EffectiveMembership.Leave
|
||||
&& space.getJoinRule() !== JoinRule.Public;
|
||||
|
||||
let inviterSection;
|
||||
let joinButtons;
|
||||
if (myMembership === "join") {
|
||||
|
@ -244,7 +249,7 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
|||
setBusy(true);
|
||||
onJoinButtonClicked();
|
||||
}}
|
||||
disabled={!spacesEnabled}
|
||||
disabled={!spacesEnabled || cannotJoin}
|
||||
>
|
||||
{ _t("Join") }
|
||||
</AccessibleButton>
|
||||
|
@ -255,6 +260,30 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
|||
joinButtons = <InlineSpinner />;
|
||||
}
|
||||
|
||||
let footer;
|
||||
if (!spacesEnabled) {
|
||||
footer = <div className="mx_SpaceRoomView_preview_spaceBetaPrompt">
|
||||
{ myMembership === "join"
|
||||
? _t("To view %(spaceName)s, turn on the <a>Spaces beta</a>", {
|
||||
spaceName: space.name,
|
||||
}, {
|
||||
a: sub => <AccessibleButton onClick={onBetaClick} kind="link">{ sub }</AccessibleButton>,
|
||||
})
|
||||
: _t("To join %(spaceName)s, turn on the <a>Spaces beta</a>", {
|
||||
spaceName: space.name,
|
||||
}, {
|
||||
a: sub => <AccessibleButton onClick={onBetaClick} kind="link">{ sub }</AccessibleButton>,
|
||||
})
|
||||
}
|
||||
</div>;
|
||||
} else if (cannotJoin) {
|
||||
footer = <div className="mx_SpaceRoomView_preview_spaceBetaPrompt">
|
||||
{ _t("To view %(spaceName)s, you need an invite", {
|
||||
spaceName: space.name,
|
||||
}) }
|
||||
</div>;
|
||||
}
|
||||
|
||||
return <div className="mx_SpaceRoomView_preview">
|
||||
<BetaPill onClick={onBetaClick} />
|
||||
{ inviterSection }
|
||||
|
@ -274,20 +303,7 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
|||
<div className="mx_SpaceRoomView_preview_joinButtons">
|
||||
{ joinButtons }
|
||||
</div>
|
||||
{ !spacesEnabled && <div className="mx_SpaceRoomView_preview_spaceBetaPrompt">
|
||||
{ myMembership === "join"
|
||||
? _t("To view %(spaceName)s, turn on the <a>Spaces beta</a>", {
|
||||
spaceName: space.name,
|
||||
}, {
|
||||
a: sub => <AccessibleButton onClick={onBetaClick} kind="link">{ sub }</AccessibleButton>,
|
||||
})
|
||||
: _t("To join %(spaceName)s, turn on the <a>Spaces beta</a>", {
|
||||
spaceName: space.name,
|
||||
}, {
|
||||
a: sub => <AccessibleButton onClick={onBetaClick} kind="link">{ sub }</AccessibleButton>,
|
||||
})
|
||||
}
|
||||
</div> }
|
||||
{ footer }
|
||||
</div>;
|
||||
};
|
||||
|
||||
|
@ -576,14 +592,14 @@ const SpaceSetupPrivateScope = ({ space, justCreatedOpts, onFinished }) => {
|
|||
|
||||
<AccessibleButton
|
||||
className="mx_SpaceRoomView_privateScope_justMeButton"
|
||||
onClick={() => { onFinished(false) }}
|
||||
onClick={() => { onFinished(false); }}
|
||||
>
|
||||
<h3>{ _t("Just me") }</h3>
|
||||
<div>{ _t("A private space to organise your rooms") }</div>
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
className="mx_SpaceRoomView_privateScope_meAndMyTeammatesButton"
|
||||
onClick={() => { onFinished(true) }}
|
||||
onClick={() => { onFinished(true); }}
|
||||
>
|
||||
<h3>{ _t("Me and my teammates") }</h3>
|
||||
<div>{ _t("A private space for you and your teammates") }</div>
|
||||
|
@ -670,7 +686,7 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
|
|||
let buttonLabel = _t("Skip for now");
|
||||
if (emailAddresses.some(name => name.trim())) {
|
||||
onClick = onNextClick;
|
||||
buttonLabel = busy ? _t("Inviting...") : _t("Continue")
|
||||
buttonLabel = busy ? _t("Inviting...") : _t("Continue");
|
||||
}
|
||||
|
||||
return <div className="mx_SpaceRoomView_inviteTeammates">
|
||||
|
|
|
@ -17,10 +17,10 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import {_t} from '../../languageHandler';
|
||||
import { _t } from '../../languageHandler';
|
||||
import * as sdk from "../../index";
|
||||
import AutoHideScrollbar from './AutoHideScrollbar';
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
|
||||
/**
|
||||
* Represents a tab for the TabbedView.
|
||||
|
@ -75,7 +75,7 @@ export default class TabbedView extends React.Component<IProps, IState> {
|
|||
private _setActiveTab(tab: Tab) {
|
||||
const idx = this.props.tabs.indexOf(tab);
|
||||
if (idx !== -1) {
|
||||
this.setState({activeTabIndex: idx});
|
||||
this.setState({ activeTabIndex: idx });
|
||||
} else {
|
||||
console.error("Could not find tab " + tab.label + " in tabs");
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -15,9 +15,9 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import ToastStore, {IToast} from "../../stores/ToastStore";
|
||||
import ToastStore, { IToast } from "../../stores/ToastStore";
|
||||
import classNames from "classnames";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
|
||||
interface IState {
|
||||
toasts: IToast<any>[];
|
||||
|
@ -58,7 +58,7 @@ export default class ToastContainer extends React.Component<{}, IState> {
|
|||
let containerClasses;
|
||||
if (totalCount !== 0) {
|
||||
const topToast = this.state.toasts[0];
|
||||
const {title, icon, key, component, className, props} = topToast;
|
||||
const { title, icon, key, component, className, props } = topToast;
|
||||
const toastClasses = classNames("mx_Toast_toast", {
|
||||
"mx_Toast_hasIcon": icon,
|
||||
[`mx_Toast_icon_${icon}`]: icon,
|
||||
|
|
|
@ -25,7 +25,8 @@ import { Action } from "../../dispatcher/actions";
|
|||
import ProgressBar from "../views/elements/ProgressBar";
|
||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||
import { IUpload } from "../../models/IUpload";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
|
@ -38,6 +39,8 @@ interface IState {
|
|||
|
||||
@replaceableComponent("structures.UploadBar")
|
||||
export default class UploadBar extends React.Component<IProps, IState> {
|
||||
static contextType = MatrixClientContext;
|
||||
|
||||
private dispatcherRef: string;
|
||||
private mounted: boolean;
|
||||
|
||||
|
@ -47,7 +50,7 @@ export default class UploadBar extends React.Component<IProps, IState> {
|
|||
// Set initial state to any available upload in this room - we might be mounting
|
||||
// earlier than the first progress event, so should show something relevant.
|
||||
const uploadsHere = this.getUploadsInRoom();
|
||||
this.state = {currentUpload: uploadsHere[0], uploadsHere};
|
||||
this.state = { currentUpload: uploadsHere[0], uploadsHere };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -74,7 +77,7 @@ export default class UploadBar extends React.Component<IProps, IState> {
|
|||
case Action.UploadFailed: {
|
||||
if (!this.mounted) return;
|
||||
const uploadsHere = this.getUploadsInRoom();
|
||||
this.setState({currentUpload: uploadsHere[0], uploadsHere});
|
||||
this.setState({ currentUpload: uploadsHere[0], uploadsHere });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -82,7 +85,7 @@ export default class UploadBar extends React.Component<IProps, IState> {
|
|||
|
||||
private onCancelClick = (ev) => {
|
||||
ev.preventDefault();
|
||||
ContentMessages.sharedInstance().cancelUpload(this.state.currentUpload.promise);
|
||||
ContentMessages.sharedInstance().cancelUpload(this.state.currentUpload.promise, this.context);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
|
@ -32,8 +32,8 @@ import FeedbackDialog from "../views/dialogs/FeedbackDialog";
|
|||
import Modal from "../../Modal";
|
||||
import LogoutDialog from "../views/dialogs/LogoutDialog";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import {getCustomTheme} from "../../theme";
|
||||
import AccessibleButton, {ButtonEvent} from "../views/elements/AccessibleButton";
|
||||
import { getCustomTheme } from "../../theme";
|
||||
import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
|
||||
import SdkConfig from "../../SdkConfig";
|
||||
import { getHomePageUrl } from "../../utils/pages";
|
||||
import { OwnProfileStore } from "../../stores/OwnProfileStore";
|
||||
|
@ -56,7 +56,7 @@ import HostSignupAction from "./HostSignupAction";
|
|||
import { IHostSignupConfig } from "../views/dialogs/HostSignupDialogTypes";
|
||||
import SpaceStore, { UPDATE_SELECTED_SPACE } from "../../stores/SpaceStore";
|
||||
import RoomName from "../views/elements/RoomName";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import InlineSpinner from "../views/elements/InlineSpinner";
|
||||
import TooltipButton from "../views/elements/TooltipButton";
|
||||
interface IProps {
|
||||
|
@ -123,7 +123,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
|
||||
private onRoom = (room: Room): void => {
|
||||
this.removePendingJoinRoom(room.roomId);
|
||||
}
|
||||
};
|
||||
|
||||
private onTagStoreUpdate = () => {
|
||||
this.forceUpdate(); // we don't have anything useful in state to update
|
||||
|
@ -152,14 +152,14 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
private onThemeChanged = () => {
|
||||
this.setState({isDarkTheme: this.isUserOnDarkTheme()});
|
||||
this.setState({ isDarkTheme: this.isUserOnDarkTheme() });
|
||||
};
|
||||
|
||||
private onAction = (ev: ActionPayload) => {
|
||||
switch (ev.action) {
|
||||
case Action.ToggleUserMenu:
|
||||
if (this.state.contextMenuPosition) {
|
||||
this.setState({contextMenuPosition: null});
|
||||
this.setState({ contextMenuPosition: null });
|
||||
} else {
|
||||
if (this.buttonRef.current) this.buttonRef.current.click();
|
||||
}
|
||||
|
@ -185,7 +185,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
if (this.state.pendingRoomJoin.delete(roomId)) {
|
||||
this.setState({
|
||||
pendingRoomJoin: new Set<string>(this.state.pendingRoomJoin),
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -193,7 +193,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
const target = ev.target as HTMLButtonElement;
|
||||
this.setState({contextMenuPosition: target.getBoundingClientRect()});
|
||||
this.setState({ contextMenuPosition: target.getBoundingClientRect() });
|
||||
};
|
||||
|
||||
private onContextMenu = (ev: React.MouseEvent) => {
|
||||
|
@ -210,7 +210,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
private onCloseMenu = () => {
|
||||
this.setState({contextMenuPosition: null});
|
||||
this.setState({ contextMenuPosition: null });
|
||||
};
|
||||
|
||||
private onSwitchThemeClick = (ev: React.MouseEvent) => {
|
||||
|
@ -228,9 +228,9 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
const payload: OpenToTabPayload = {action: Action.ViewUserSettings, initialTabId: tabId};
|
||||
const payload: OpenToTabPayload = { action: Action.ViewUserSettings, initialTabId: tabId };
|
||||
defaultDispatcher.dispatch(payload);
|
||||
this.setState({contextMenuPosition: null}); // also close the menu
|
||||
this.setState({ contextMenuPosition: null }); // also close the menu
|
||||
};
|
||||
|
||||
private onShowArchived = (ev: ButtonEvent) => {
|
||||
|
@ -247,7 +247,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
ev.stopPropagation();
|
||||
|
||||
Modal.createTrackedDialog('Feedback Dialog', '', FeedbackDialog);
|
||||
this.setState({contextMenuPosition: null}); // also close the menu
|
||||
this.setState({ contextMenuPosition: null }); // also close the menu
|
||||
};
|
||||
|
||||
private onSignOutClick = async (ev: ButtonEvent) => {
|
||||
|
@ -257,30 +257,30 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
const cli = MatrixClientPeg.get();
|
||||
if (!cli || !cli.isCryptoEnabled() || !(await cli.exportRoomKeys())?.length) {
|
||||
// log out without user prompt if they have no local megolm sessions
|
||||
dis.dispatch({action: 'logout'});
|
||||
dis.dispatch({ action: 'logout' });
|
||||
} else {
|
||||
Modal.createTrackedDialog('Logout from LeftPanel', '', LogoutDialog);
|
||||
}
|
||||
|
||||
this.setState({contextMenuPosition: null}); // also close the menu
|
||||
this.setState({ contextMenuPosition: null }); // also close the menu
|
||||
};
|
||||
|
||||
private onSignInClick = () => {
|
||||
dis.dispatch({ action: 'start_login' });
|
||||
this.setState({contextMenuPosition: null}); // also close the menu
|
||||
this.setState({ contextMenuPosition: null }); // also close the menu
|
||||
};
|
||||
|
||||
private onRegisterClick = () => {
|
||||
dis.dispatch({ action: 'start_registration' });
|
||||
this.setState({contextMenuPosition: null}); // also close the menu
|
||||
this.setState({ contextMenuPosition: null }); // also close the menu
|
||||
};
|
||||
|
||||
private onHomeClick = (ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
defaultDispatcher.dispatch({action: 'view_home_page'});
|
||||
this.setState({contextMenuPosition: null}); // also close the menu
|
||||
defaultDispatcher.dispatch({ action: 'view_home_page' });
|
||||
this.setState({ contextMenuPosition: null }); // also close the menu
|
||||
};
|
||||
|
||||
private onCommunitySettingsClick = (ev: ButtonEvent) => {
|
||||
|
@ -290,7 +290,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
Modal.createTrackedDialog('Edit Community', '', EditCommunityPrototypeDialog, {
|
||||
communityId: CommunityPrototypeStore.instance.getSelectedCommunityId(),
|
||||
});
|
||||
this.setState({contextMenuPosition: null}); // also close the menu
|
||||
this.setState({ contextMenuPosition: null }); // also close the menu
|
||||
};
|
||||
|
||||
private onCommunityMembersClick = (ev: ButtonEvent) => {
|
||||
|
@ -307,7 +307,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
action: 'view_room',
|
||||
room_id: chat.roomId,
|
||||
}, true);
|
||||
dis.dispatch({action: Action.SetRightPanelPhase, phase: RightPanelPhases.RoomMemberList});
|
||||
dis.dispatch({ action: Action.SetRightPanelPhase, phase: RightPanelPhases.RoomMemberList });
|
||||
} else {
|
||||
// "This should never happen" clauses go here for the prototype.
|
||||
Modal.createTrackedDialog('Failed to find general chat', '', ErrorDialog, {
|
||||
|
@ -315,7 +315,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
description: _t("Failed to find the general chat for this community"),
|
||||
});
|
||||
}
|
||||
this.setState({contextMenuPosition: null}); // also close the menu
|
||||
this.setState({ contextMenuPosition: null }); // also close the menu
|
||||
};
|
||||
|
||||
private onCommunityInviteClick = (ev: ButtonEvent) => {
|
||||
|
@ -323,7 +323,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
ev.stopPropagation();
|
||||
|
||||
showCommunityInviteDialog(CommunityPrototypeStore.instance.getSelectedCommunityId());
|
||||
this.setState({contextMenuPosition: null}); // also close the menu
|
||||
this.setState({ contextMenuPosition: null }); // also close the menu
|
||||
};
|
||||
|
||||
private onDndToggle = (ev) => {
|
||||
|
@ -357,7 +357,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
),
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
} else if (hostSignupConfig) {
|
||||
if (hostSignupConfig && hostSignupConfig.url) {
|
||||
// If hostSignup.domains is set to a non-empty array, only show
|
||||
|
@ -509,7 +509,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
/>
|
||||
</IconizedContextMenuOptionList>
|
||||
</React.Fragment>
|
||||
)
|
||||
);
|
||||
} else if (MatrixClientPeg.get().isGuest()) {
|
||||
primaryOptionList = (
|
||||
<React.Fragment>
|
||||
|
|
|
@ -17,14 +17,14 @@ limitations under the License.
|
|||
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import {MatrixClientPeg} from "../../MatrixClientPeg";
|
||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
import * as sdk from "../../index";
|
||||
import Modal from '../../Modal';
|
||||
import { _t } from '../../languageHandler';
|
||||
import HomePage from "./HomePage";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
|
||||
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
|
||||
@replaceableComponent("structures.UserView")
|
||||
export default class UserView extends React.Component {
|
||||
|
@ -56,7 +56,7 @@ export default class UserView extends React.Component {
|
|||
|
||||
async _loadProfileInfo() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
this.setState({loading: true});
|
||||
this.setState({ loading: true });
|
||||
let profileInfo;
|
||||
try {
|
||||
profileInfo = await cli.getProfileInfo(this.props.userId);
|
||||
|
@ -66,13 +66,13 @@ export default class UserView extends React.Component {
|
|||
title: _t('Could not load user profile'),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
this.setState({loading: false});
|
||||
this.setState({ loading: false });
|
||||
return;
|
||||
}
|
||||
const fakeEvent = new MatrixEvent({type: "m.room.member", content: profileInfo});
|
||||
const fakeEvent = new MatrixEvent({ type: "m.room.member", content: profileInfo });
|
||||
const member = new RoomMember(null, this.props.userId);
|
||||
member.setMembershipEvent(fakeEvent);
|
||||
this.setState({member, loading: false});
|
||||
this.setState({ member, loading: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -20,7 +20,7 @@ import { _t } from '../../../languageHandler';
|
|||
import * as sdk from '../../../index';
|
||||
import { SetupEncryptionStore, Phase } from '../../../stores/SetupEncryptionStore';
|
||||
import SetupEncryptionBody from "./SetupEncryptionBody";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("structures.auth.CompleteSecurity")
|
||||
export default class CompleteSecurity extends React.Component {
|
||||
|
@ -33,12 +33,12 @@ export default class CompleteSecurity extends React.Component {
|
|||
const store = SetupEncryptionStore.sharedInstance();
|
||||
store.on("update", this._onStoreUpdate);
|
||||
store.start();
|
||||
this.state = {phase: store.phase};
|
||||
this.state = { phase: store.phase };
|
||||
}
|
||||
|
||||
_onStoreUpdate = () => {
|
||||
const store = SetupEncryptionStore.sharedInstance();
|
||||
this.setState({phase: store.phase});
|
||||
this.setState({ phase: store.phase });
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -50,7 +50,7 @@ export default class CompleteSecurity extends React.Component {
|
|||
render() {
|
||||
const AuthPage = sdk.getComponent("auth.AuthPage");
|
||||
const CompleteSecurityBody = sdk.getComponent("auth.CompleteSecurityBody");
|
||||
const {phase} = this.state;
|
||||
const { phase } = this.state;
|
||||
let icon;
|
||||
let title;
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ import PropTypes from 'prop-types';
|
|||
import AuthPage from '../../views/auth/AuthPage';
|
||||
import CompleteSecurityBody from '../../views/auth/CompleteSecurityBody';
|
||||
import CreateCrossSigningDialog from '../../views/dialogs/security/CreateCrossSigningDialog';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("structures.auth.E2eSetup")
|
||||
export default class E2eSetup extends React.Component {
|
||||
|
|
|
@ -22,13 +22,13 @@ import { _t, _td } from '../../../languageHandler';
|
|||
import * as sdk from '../../../index';
|
||||
import Modal from "../../../Modal";
|
||||
import PasswordReset from "../../../PasswordReset";
|
||||
import AutoDiscoveryUtils, {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
||||
import AutoDiscoveryUtils, { ValidatedServerConfig } from "../../../utils/AutoDiscoveryUtils";
|
||||
import classNames from 'classnames';
|
||||
import AuthPage from "../../views/auth/AuthPage";
|
||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||
import ServerPicker from "../../views/elements/ServerPicker";
|
||||
import PassphraseField from '../../views/auth/PassphraseField';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { PASSWORD_MIN_SCORE } from '../../views/auth/RegistrationForm';
|
||||
|
||||
// Phases
|
||||
|
|
|
@ -14,28 +14,28 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {ReactNode} from 'react';
|
||||
import {MatrixError} from "matrix-js-sdk/src/http-api";
|
||||
import React, { ReactNode } from 'react';
|
||||
import { MatrixError } from "matrix-js-sdk/src/http-api";
|
||||
|
||||
import {_t, _td} from '../../../languageHandler';
|
||||
import { _t, _td } from '../../../languageHandler';
|
||||
import * as sdk from '../../../index';
|
||||
import Login, {ISSOFlow, LoginFlow} from '../../../Login';
|
||||
import Login, { ISSOFlow, LoginFlow } from '../../../Login';
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
import { messageForResourceLimitError } from '../../../utils/ErrorUtils';
|
||||
import AutoDiscoveryUtils, {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
||||
import AutoDiscoveryUtils, { ValidatedServerConfig } from "../../../utils/AutoDiscoveryUtils";
|
||||
import classNames from "classnames";
|
||||
import AuthPage from "../../views/auth/AuthPage";
|
||||
import PlatformPeg from '../../../PlatformPeg';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import {UIFeature} from "../../../settings/UIFeature";
|
||||
import { UIFeature } from "../../../settings/UIFeature";
|
||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||
import {IMatrixClientCreds} from "../../../MatrixClientPeg";
|
||||
import { IMatrixClientCreds } from "../../../MatrixClientPeg";
|
||||
import PasswordLogin from "../../views/auth/PasswordLogin";
|
||||
import InlineSpinner from "../../views/elements/InlineSpinner";
|
||||
import Spinner from "../../views/elements/Spinner";
|
||||
import SSOButtons from "../../views/elements/SSOButtons";
|
||||
import ServerPicker from "../../views/elements/ServerPicker";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
// These are used in several places, and come from the js-sdk's autodiscovery
|
||||
// stuff. We define them here so that they'll be picked up by i18n.
|
||||
|
@ -166,7 +166,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
|||
|
||||
onPasswordLogin = async (username, phoneCountry, phoneNumber, password) => {
|
||||
if (!this.state.serverIsAlive) {
|
||||
this.setState({busy: true});
|
||||
this.setState({ busy: true });
|
||||
// Do a quick liveliness check on the URLs
|
||||
let aliveAgain = true;
|
||||
try {
|
||||
|
@ -174,7 +174,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
|||
this.props.serverConfig.hsUrl,
|
||||
this.props.serverConfig.isUrl,
|
||||
);
|
||||
this.setState({serverIsAlive: true, errorText: ""});
|
||||
this.setState({ serverIsAlive: true, errorText: "" });
|
||||
} catch (e) {
|
||||
const componentState = AutoDiscoveryUtils.authComponentStateForError(e);
|
||||
this.setState({
|
||||
|
@ -201,7 +201,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
|||
this.loginLogic.loginViaPassword(
|
||||
username, phoneCountry, phoneNumber, password,
|
||||
).then((data) => {
|
||||
this.setState({serverIsAlive: true}); // it must be, we logged in.
|
||||
this.setState({ serverIsAlive: true }); // it must be, we logged in.
|
||||
this.props.onLoggedIn(data, password);
|
||||
}, (error) => {
|
||||
if (this.unmounted) {
|
||||
|
@ -252,7 +252,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
|||
<div className="mx_Login_smallError">
|
||||
{_t(
|
||||
'Please note you are logging into the %(hs)s server, not matrix.org.',
|
||||
{hs: this.props.serverConfig.hsName},
|
||||
{ hs: this.props.serverConfig.hsName },
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -363,7 +363,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
|||
}
|
||||
};
|
||||
|
||||
private async initLoginLogic({hsUrl, isUrl}: ValidatedServerConfig) {
|
||||
private async initLoginLogic({ hsUrl, isUrl }: ValidatedServerConfig) {
|
||||
let isDefaultServer = false;
|
||||
if (this.props.serverConfig.isDefault
|
||||
&& hsUrl === this.props.serverConfig.hsUrl
|
||||
|
@ -501,9 +501,9 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
|||
return <React.Fragment>
|
||||
{ flows.map(flow => {
|
||||
const stepRenderer = this.stepRendererMap[flow.type];
|
||||
return <React.Fragment key={flow.type}>{ stepRenderer() }</React.Fragment>
|
||||
return <React.Fragment key={flow.type}>{ stepRenderer() }</React.Fragment>;
|
||||
}) }
|
||||
</React.Fragment>
|
||||
</React.Fragment>;
|
||||
}
|
||||
|
||||
private renderPasswordStep = () => {
|
||||
|
|
|
@ -14,23 +14,23 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {createClient} from 'matrix-js-sdk/src/matrix';
|
||||
import React, {ReactNode} from 'react';
|
||||
import {MatrixClient} from "matrix-js-sdk/src/client";
|
||||
import { createClient } from 'matrix-js-sdk/src/matrix';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
|
||||
import * as sdk from '../../../index';
|
||||
import { _t, _td } from '../../../languageHandler';
|
||||
import { messageForResourceLimitError } from '../../../utils/ErrorUtils';
|
||||
import AutoDiscoveryUtils, {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
||||
import AutoDiscoveryUtils, { ValidatedServerConfig } from "../../../utils/AutoDiscoveryUtils";
|
||||
import classNames from "classnames";
|
||||
import * as Lifecycle from '../../../Lifecycle';
|
||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import AuthPage from "../../views/auth/AuthPage";
|
||||
import Login, {ISSOFlow} from "../../../Login";
|
||||
import Login, { ISSOFlow } from "../../../Login";
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import SSOButtons from "../../views/elements/SSOButtons";
|
||||
import ServerPicker from '../../views/elements/ServerPicker';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
serverConfig: ValidatedServerConfig;
|
||||
|
@ -49,7 +49,7 @@ interface IProps {
|
|||
// for operations like uploading cross-signing keys).
|
||||
onLoggedIn(params: {
|
||||
userId: string;
|
||||
deviceId: string
|
||||
deviceId: string;
|
||||
homeserverUrl: string;
|
||||
identityServerUrl?: string;
|
||||
accessToken: string;
|
||||
|
@ -131,7 +131,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
|||
serverDeadError: "",
|
||||
};
|
||||
|
||||
const {hsUrl, isUrl} = this.props.serverConfig;
|
||||
const { hsUrl, isUrl } = this.props.serverConfig;
|
||||
this.loginLogic = new Login(hsUrl, isUrl, null, {
|
||||
defaultDeviceDisplayName: "Element login check", // We shouldn't ever be used
|
||||
});
|
||||
|
@ -180,7 +180,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
|||
}
|
||||
}
|
||||
|
||||
const {hsUrl, isUrl} = serverConfig;
|
||||
const { hsUrl, isUrl } = serverConfig;
|
||||
const cli = createClient({
|
||||
baseUrl: hsUrl,
|
||||
idBaseUrl: isUrl,
|
||||
|
@ -230,7 +230,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
|||
// the user off to the login page to figure their account out.
|
||||
if (ssoFlow) {
|
||||
// Redirect to login page - server probably expects SSO only
|
||||
dis.dispatch({action: 'start_login'});
|
||||
dis.dispatch({ action: 'start_login' });
|
||||
} else {
|
||||
this.setState({
|
||||
serverErrorIsFatal: true, // fatal because user cannot continue on this server
|
||||
|
@ -267,7 +267,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
|||
session_id: sessionId,
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
private onUIAuthFinished = async (success: boolean, response: any) => {
|
||||
if (!success) {
|
||||
|
@ -432,7 +432,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
|||
private onLoginClickWithCheck = async ev => {
|
||||
ev.preventDefault();
|
||||
|
||||
const sessionLoaded = await Lifecycle.loadSession({ignoreGuest: true});
|
||||
const sessionLoaded = await Lifecycle.loadSession({ ignoreGuest: true });
|
||||
if (!sessionLoaded) {
|
||||
// ok fine, there's still no session: really go to the login page
|
||||
this.props.onLoginClick();
|
||||
|
@ -487,7 +487,13 @@ export default class Registration extends React.Component<IProps, IState> {
|
|||
fragmentAfterLogin={this.props.fragmentAfterLogin}
|
||||
/>
|
||||
<h3 className="mx_AuthBody_centered">
|
||||
{ _t("%(ssoButtons)s Or %(usernamePassword)s", { ssoButtons: "", usernamePassword: ""}).trim() }
|
||||
{_t(
|
||||
"%(ssoButtons)s Or %(usernamePassword)s",
|
||||
{
|
||||
ssoButtons: "",
|
||||
usernamePassword: "",
|
||||
},
|
||||
).trim()}
|
||||
</h3>
|
||||
</React.Fragment>;
|
||||
}
|
||||
|
@ -563,7 +569,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
|||
<p><AccessibleButton element="span" className="mx_linkButton" onClick={async event => {
|
||||
const sessionLoaded = await this.onLoginClickWithCheck(event);
|
||||
if (sessionLoaded) {
|
||||
dis.dispatch({action: "view_welcome_page"});
|
||||
dis.dispatch({ action: "view_welcome_page" });
|
||||
}
|
||||
}}>
|
||||
{_t("Continue with previous account")}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2020-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.
|
||||
|
@ -15,33 +15,43 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import Modal from '../../../Modal';
|
||||
import VerificationRequestDialog from '../../views/dialogs/VerificationRequestDialog';
|
||||
import * as sdk from '../../../index';
|
||||
import { SetupEncryptionStore, Phase } from '../../../stores/SetupEncryptionStore';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { ISecretStorageKeyInfo } from 'matrix-js-sdk';
|
||||
import EncryptionPanel from "../../views/right_panel/EncryptionPanel";
|
||||
import AccessibleButton from '../../views/elements/AccessibleButton';
|
||||
import Spinner from '../../views/elements/Spinner';
|
||||
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
|
||||
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||
|
||||
function keyHasPassphrase(keyInfo) {
|
||||
return (
|
||||
function keyHasPassphrase(keyInfo: ISecretStorageKeyInfo): boolean {
|
||||
return Boolean(
|
||||
keyInfo.passphrase &&
|
||||
keyInfo.passphrase.salt &&
|
||||
keyInfo.passphrase.iterations
|
||||
keyInfo.passphrase.iterations,
|
||||
);
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.auth.SetupEncryptionBody")
|
||||
export default class SetupEncryptionBody extends React.Component {
|
||||
static propTypes = {
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
};
|
||||
interface IProps {
|
||||
onFinished: (boolean) => void;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
interface IState {
|
||||
phase: Phase;
|
||||
verificationRequest: VerificationRequest;
|
||||
backupInfo: IKeyBackupInfo;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.auth.SetupEncryptionBody")
|
||||
export default class SetupEncryptionBody extends React.Component<IProps, IState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const store = SetupEncryptionStore.sharedInstance();
|
||||
store.on("update", this._onStoreUpdate);
|
||||
store.on("update", this.onStoreUpdate);
|
||||
store.start();
|
||||
this.state = {
|
||||
phase: store.phase,
|
||||
|
@ -53,10 +63,10 @@ export default class SetupEncryptionBody extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
_onStoreUpdate = () => {
|
||||
private onStoreUpdate = () => {
|
||||
const store = SetupEncryptionStore.sharedInstance();
|
||||
if (store.phase === Phase.Finished) {
|
||||
this.props.onFinished();
|
||||
this.props.onFinished(true);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
|
@ -66,18 +76,18 @@ export default class SetupEncryptionBody extends React.Component {
|
|||
});
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
public componentWillUnmount() {
|
||||
const store = SetupEncryptionStore.sharedInstance();
|
||||
store.off("update", this._onStoreUpdate);
|
||||
store.off("update", this.onStoreUpdate);
|
||||
store.stop();
|
||||
}
|
||||
|
||||
_onUsePassphraseClick = async () => {
|
||||
private onUsePassphraseClick = async () => {
|
||||
const store = SetupEncryptionStore.sharedInstance();
|
||||
store.usePassPhrase();
|
||||
}
|
||||
};
|
||||
|
||||
_onVerifyClick = () => {
|
||||
private onVerifyClick = () => {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const userId = cli.getUserId();
|
||||
const requestPromise = cli.requestVerification(userId);
|
||||
|
@ -91,42 +101,44 @@ export default class SetupEncryptionBody extends React.Component {
|
|||
request.cancel();
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onSkipClick = () => {
|
||||
private onSkipClick = () => {
|
||||
const store = SetupEncryptionStore.sharedInstance();
|
||||
store.skip();
|
||||
}
|
||||
};
|
||||
|
||||
onSkipConfirmClick = () => {
|
||||
private onSkipConfirmClick = () => {
|
||||
const store = SetupEncryptionStore.sharedInstance();
|
||||
store.skipConfirm();
|
||||
}
|
||||
};
|
||||
|
||||
onSkipBackClick = () => {
|
||||
private onSkipBackClick = () => {
|
||||
const store = SetupEncryptionStore.sharedInstance();
|
||||
store.returnAfterSkip();
|
||||
}
|
||||
};
|
||||
|
||||
onDoneClick = () => {
|
||||
private onDoneClick = () => {
|
||||
const store = SetupEncryptionStore.sharedInstance();
|
||||
store.done();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
||||
private onEncryptionPanelClose = () => {
|
||||
this.props.onFinished(false);
|
||||
};
|
||||
|
||||
public render() {
|
||||
const {
|
||||
phase,
|
||||
} = this.state;
|
||||
|
||||
if (this.state.verificationRequest) {
|
||||
const EncryptionPanel = sdk.getComponent("views.right_panel.EncryptionPanel");
|
||||
return <EncryptionPanel
|
||||
layout="dialog"
|
||||
verificationRequest={this.state.verificationRequest}
|
||||
onClose={this.props.onFinished}
|
||||
onClose={this.onEncryptionPanelClose}
|
||||
member={MatrixClientPeg.get().getUser(this.state.verificationRequest.otherUserId)}
|
||||
isRoomEncrypted={false}
|
||||
/>;
|
||||
} else if (phase === Phase.Intro) {
|
||||
const store = SetupEncryptionStore.sharedInstance();
|
||||
|
@ -139,14 +151,14 @@ export default class SetupEncryptionBody extends React.Component {
|
|||
|
||||
let useRecoveryKeyButton;
|
||||
if (recoveryKeyPrompt) {
|
||||
useRecoveryKeyButton = <AccessibleButton kind="link" onClick={this._onUsePassphraseClick}>
|
||||
useRecoveryKeyButton = <AccessibleButton kind="link" onClick={this.onUsePassphraseClick}>
|
||||
{recoveryKeyPrompt}
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
let verifyButton;
|
||||
if (store.hasDevicesToVerifyAgainst) {
|
||||
verifyButton = <AccessibleButton kind="primary" onClick={this._onVerifyClick}>
|
||||
verifyButton = <AccessibleButton kind="primary" onClick={this.onVerifyClick}>
|
||||
{ _t("Use another login") }
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
@ -217,7 +229,6 @@ export default class SetupEncryptionBody extends React.Component {
|
|||
</div>
|
||||
);
|
||||
} else if (phase === Phase.Busy || phase === Phase.Loading) {
|
||||
const Spinner = sdk.getComponent('views.elements.Spinner');
|
||||
return <Spinner />;
|
||||
} else {
|
||||
console.log(`SetupEncryptionBody: Unknown phase ${phase}`);
|
|
@ -15,17 +15,17 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {_t} from '../../../languageHandler';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import * as sdk from '../../../index';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import * as Lifecycle from '../../../Lifecycle';
|
||||
import Modal from '../../../Modal';
|
||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
import {ISSOFlow, LoginFlow, sendLoginRequest} from "../../../Login";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { ISSOFlow, LoginFlow, sendLoginRequest } from "../../../Login";
|
||||
import AuthPage from "../../views/auth/AuthPage";
|
||||
import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "../../../BasePlatform";
|
||||
import { SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY } from "../../../BasePlatform";
|
||||
import SSOButtons from "../../views/elements/SSOButtons";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
const LOGIN_VIEW = {
|
||||
LOADING: 1,
|
||||
|
@ -49,7 +49,7 @@ interface IProps {
|
|||
fragmentAfterLogin?: string;
|
||||
|
||||
// Called when the SSO login completes
|
||||
onTokenLoginCompleted: () => void,
|
||||
onTokenLoginCompleted: () => void;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
@ -79,7 +79,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
|||
componentDidMount(): void {
|
||||
// We've ended up here when we don't need to - navigate to login
|
||||
if (!Lifecycle.isSoftLogout()) {
|
||||
dis.dispatch({action: "start_login"});
|
||||
dis.dispatch({ action: "start_login" });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -109,7 +109,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
|||
const queryParams = this.props.realQueryParams;
|
||||
const hasAllParams = queryParams && queryParams['loginToken'];
|
||||
if (hasAllParams) {
|
||||
this.setState({loginView: LOGIN_VIEW.LOADING});
|
||||
this.setState({ loginView: LOGIN_VIEW.LOADING });
|
||||
this.trySsoLogin();
|
||||
return;
|
||||
}
|
||||
|
@ -125,18 +125,18 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
onPasswordChange = (ev) => {
|
||||
this.setState({password: ev.target.value});
|
||||
this.setState({ password: ev.target.value });
|
||||
};
|
||||
|
||||
onForgotPassword = () => {
|
||||
dis.dispatch({action: 'start_password_recovery'});
|
||||
dis.dispatch({ action: 'start_password_recovery' });
|
||||
};
|
||||
|
||||
onPasswordLogin = async (ev) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
this.setState({busy: true});
|
||||
this.setState({ busy: true });
|
||||
|
||||
const hsUrl = MatrixClientPeg.get().getHomeserverUrl();
|
||||
const isUrl = MatrixClientPeg.get().getIdentityServerUrl();
|
||||
|
@ -168,12 +168,12 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
|||
|
||||
Lifecycle.hydrateSession(credentials).catch((e) => {
|
||||
console.error(e);
|
||||
this.setState({busy: false, errorText: _t("Failed to re-authenticate")});
|
||||
this.setState({ busy: false, errorText: _t("Failed to re-authenticate") });
|
||||
});
|
||||
};
|
||||
|
||||
async trySsoLogin() {
|
||||
this.setState({busy: true});
|
||||
this.setState({ busy: true });
|
||||
|
||||
const hsUrl = localStorage.getItem(SSO_HOMESERVER_URL_KEY);
|
||||
const isUrl = localStorage.getItem(SSO_ID_SERVER_URL_KEY) || MatrixClientPeg.get().getIdentityServerUrl();
|
||||
|
@ -188,7 +188,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
|||
credentials = await sendLoginRequest(hsUrl, isUrl, loginType, loginParams);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.setState({busy: false, loginView: LOGIN_VIEW.UNSUPPORTED});
|
||||
this.setState({ busy: false, loginView: LOGIN_VIEW.UNSUPPORTED });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -196,7 +196,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
|||
if (this.props.onTokenLoginCompleted) this.props.onTokenLoginCompleted();
|
||||
}).catch((e) => {
|
||||
console.error(e);
|
||||
this.setState({busy: false, loginView: LOGIN_VIEW.UNSUPPORTED});
|
||||
this.setState({ busy: false, loginView: LOGIN_VIEW.UNSUPPORTED });
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue