Merge remote-tracking branch 'upstream/develop' into compact-reply-rendering

This commit is contained in:
Tulir Asokan 2020-09-19 19:14:37 +03:00
commit 987cdf1bf0
201 changed files with 6532 additions and 2503 deletions

View file

@ -42,6 +42,8 @@ import {WidgetType} from "../../../widgets/WidgetType";
import {Capability} from "../../../widgets/WidgetApi";
import {sleep} from "../../../utils/promise";
import {SettingLevel} from "../../../settings/SettingLevel";
import WidgetStore from "../../../stores/WidgetStore";
import {Action} from "../../../dispatcher/actions";
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
const ENABLE_REACT_PERF = false;
@ -100,6 +102,8 @@ export default class AppTile extends React.Component {
_getNewState(newProps) {
// This is a function to make the impact of calling SettingsStore slightly less
const hasPermissionToLoad = () => {
if (this._usingLocalWidget()) return true;
const currentlyAllowedWidgets = SettingsStore.getValue("allowedWidgets", newProps.room.roomId);
return !!currentlyAllowedWidgets[newProps.app.eventId];
};
@ -310,35 +314,12 @@ export default class AppTile extends React.Component {
if (this.props.onEditClick) {
this.props.onEditClick();
} else {
// TODO: Open the right manager for the widget
if (SettingsStore.getValue("feature_many_integration_managers")) {
IntegrationManagers.sharedInstance().openAll(
this.props.room,
'type_' + this.props.app.type,
this.props.app.id,
);
} else {
IntegrationManagers.sharedInstance().getPrimaryManager().open(
this.props.room,
'type_' + this.props.app.type,
this.props.app.id,
);
}
WidgetUtils.editWidget(this.props.room, this.props.app);
}
}
_onSnapshotClick() {
console.log("Requesting widget snapshot");
ActiveWidgetStore.getWidgetMessaging(this.props.app.id).getScreenshot()
.catch((err) => {
console.error("Failed to get screenshot", err);
})
.then((screenshot) => {
dis.dispatch({
action: 'picture_snapshot',
file: screenshot,
}, true);
});
WidgetUtils.snapshotWidget(this.props.app);
}
/**
@ -419,6 +400,10 @@ export default class AppTile extends React.Component {
}
}
_onUnpinClicked = () => {
WidgetStore.instance.unpinWidget(this.props.app.id);
}
_onRevokeClicked() {
console.info("Revoke widget permissions - %s", this.props.app.id);
this._revokeWidgetPermission();
@ -490,12 +475,20 @@ export default class AppTile extends React.Component {
if (payload.widgetId === this.props.app.id) {
switch (payload.action) {
case 'm.sticker':
if (this._hasCapability('m.sticker')) {
dis.dispatch({action: 'post_sticker_message', data: payload.data});
} else {
console.warn('Ignoring sticker message. Invalid capability');
}
break;
if (this._hasCapability('m.sticker')) {
dis.dispatch({action: 'post_sticker_message', data: payload.data});
} else {
console.warn('Ignoring sticker message. Invalid capability');
}
break;
case Action.AppTileDelete:
this._onDeleteClick();
break;
case Action.AppTileRevoke:
this._onRevokeClicked();
break;
}
}
}
@ -613,6 +606,15 @@ export default class AppTile extends React.Component {
return uriFromTemplate(u, vars);
}
/**
* Whether we're using a local version of the widget rather than loading the
* actual widget URL
* @returns {bool} true If using a local version of the widget
*/
_usingLocalWidget() {
return WidgetType.JITSI.matches(this.props.app.type);
}
/**
* Get the URL used in the iframe
* In cases where we supply our own UI for a widget, this is an internal
@ -626,7 +628,10 @@ export default class AppTile extends React.Component {
if (WidgetType.JITSI.matches(this.props.app.type)) {
console.log("Replacing Jitsi widget URL with local wrapper");
url = WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: true});
url = WidgetUtils.getLocalJitsiWrapperUrl({
forLocalRender: true,
auth: this.props.app.data ? this.props.app.data.auth : null,
});
url = this._addWurlParams(url);
} else {
url = this._getSafeUrl(this.state.widgetUrl);
@ -637,7 +642,10 @@ export default class AppTile extends React.Component {
_getPopoutUrl() {
if (WidgetType.JITSI.matches(this.props.app.type)) {
return this._templatedUrl(
WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: false}),
WidgetUtils.getLocalJitsiWrapperUrl({
forLocalRender: false,
auth: this.props.app.data ? this.props.app.data.auth : null,
}),
this.props.app.type,
);
} else {
@ -804,14 +812,16 @@ export default class AppTile extends React.Component {
const showMinimiseButton = this.props.showMinimise && this.props.show;
const showMaximiseButton = this.props.showMinimise && !this.props.show;
let appTileClass;
let appTileClasses;
if (this.props.miniMode) {
appTileClass = 'mx_AppTile_mini';
appTileClasses = {mx_AppTile_mini: true};
} else if (this.props.fullWidth) {
appTileClass = 'mx_AppTileFullWidth';
appTileClasses = {mx_AppTileFullWidth: true};
} else {
appTileClass = 'mx_AppTile';
appTileClasses = {mx_AppTile: true};
}
appTileClasses.mx_AppTile_minimised = !this.props.show;
appTileClasses = classNames(appTileClasses);
const menuBarClasses = classNames({
mx_AppTileMenuBar: true,
@ -831,6 +841,9 @@ export default class AppTile extends React.Component {
contextMenu = (
<ContextMenu {...aboveLeftOf(elementRect, null)} onFinished={this._closeContextMenu}>
<WidgetContextMenu
onUnpinClicked={
ActiveWidgetStore.getWidgetPersistence(this.props.app.id) ? null : this._onUnpinClicked
}
onRevokeClicked={this._onRevokeClicked}
onEditClicked={showEditButton ? this._onEditClick : undefined}
onDeleteClicked={showDeleteButton ? this._onDeleteClick : undefined}
@ -843,20 +856,20 @@ export default class AppTile extends React.Component {
}
return <React.Fragment>
<div className={appTileClass} id={this.props.app.id}>
<div className={appTileClasses} id={this.props.app.id}>
{ this.props.showMenubar &&
<div ref={this._menu_bar} className={menuBarClasses} onClick={this.onClickMenuBar}>
<span className="mx_AppTileMenuBarTitle" style={{pointerEvents: (this.props.handleMinimisePointerEvents ? 'all' : false)}}>
{ /* Minimise widget */ }
{ showMinimiseButton && <AccessibleButton
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_minimise"
title={_t('Minimize apps')}
title={_t('Minimize widget')}
onClick={this._onMinimiseClick}
/> }
{ /* Maximise widget */ }
{ showMaximiseButton && <AccessibleButton
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_maximise"
title={_t('Maximize apps')}
title={_t('Maximize widget')}
onClick={this._onMinimiseClick}
/> }
{ /* Title */ }

View file

@ -20,6 +20,7 @@ import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import PlatformPeg from '../../../PlatformPeg';
import Modal from '../../../Modal';
import SdkConfig from "../../../SdkConfig";
/**
* This error boundary component can be used to wrap large content areas and
@ -73,9 +74,10 @@ export default class ErrorBoundary extends React.PureComponent {
if (this.state.error) {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const newIssueUrl = "https://github.com/vector-im/element-web/issues/new";
return <div className="mx_ErrorBoundary">
<div className="mx_ErrorBoundary_body">
<h1>{_t("Something went wrong!")}</h1>
let bugReportSection;
if (SdkConfig.get().bug_report_endpoint_url) {
bugReportSection = <React.Fragment>
<p>{_t(
"Please <newIssueLink>create a new issue</newIssueLink> " +
"on GitHub so that we can investigate this bug.", {}, {
@ -94,6 +96,13 @@ export default class ErrorBoundary extends React.PureComponent {
<AccessibleButton onClick={this._onBugReport} kind='primary'>
{_t("Submit debug logs")}
</AccessibleButton>
</React.Fragment>;
}
return <div className="mx_ErrorBoundary">
<div className="mx_ErrorBoundary_body">
<h1>{_t("Something went wrong!")}</h1>
{ bugReportSection }
<AccessibleButton onClick={this._onClearCacheAndReload} kind='danger'>
{_t("Clear cache and reload")}
</AccessibleButton>

View file

@ -21,6 +21,8 @@ import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import * as Avatar from '../../../Avatar';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import EventTile from '../rooms/EventTile';
import SettingsStore from "../../../settings/SettingsStore";
import {UIFeature} from "../../../settings/UIFeature";
interface IProps {
/**
@ -121,7 +123,11 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
});
return <div className={className}>
<EventTile mxEvent={event} useIRCLayout={this.props.useIRCLayout} />
<EventTile
mxEvent={event}
useIRCLayout={this.props.useIRCLayout}
enableFlair={SettingsStore.getValue(UIFeature.Flair)}
/>
</div>;
}
}

View file

@ -1,63 +0,0 @@
/*
Copyright 2017 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import SettingsStore from "../../../settings/SettingsStore";
import AccessibleTooltipButton from "./AccessibleTooltipButton";
export default class ManageIntegsButton extends React.Component {
constructor(props) {
super(props);
}
onManageIntegrations = (ev) => {
ev.preventDefault();
const managers = IntegrationManagers.sharedInstance();
if (!managers.hasManager()) {
managers.openNoManagerDialog();
} else {
if (SettingsStore.getValue("feature_many_integration_managers")) {
managers.openAll(this.props.room);
} else {
managers.getPrimaryManager().open(this.props.room);
}
}
};
render() {
let integrationsButton = <div />;
if (IntegrationManagers.sharedInstance().hasManager()) {
integrationsButton = (
<AccessibleTooltipButton
className='mx_RoomHeader_button mx_RoomHeader_manageIntegsButton'
title={_t("Manage Integrations")}
onClick={this.onManageIntegrations}
/>
);
}
return integrationsButton;
}
}
ManageIntegsButton.propTypes = {
room: PropTypes.object.isRequired,
};

View file

@ -17,7 +17,7 @@ limitations under the License.
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import {throttle} from "lodash";
import ResizeObserver from 'resize-observer-polyfill';
import dis from '../../../dispatcher/dispatcher';
@ -156,7 +156,7 @@ export default class PersistedElement extends React.Component {
child.style.display = visible ? 'block' : 'none';
}
updateChildPosition(child, parent) {
updateChildPosition = throttle((child, parent) => {
if (!child || !parent) return;
const parentRect = parent.getBoundingClientRect();
@ -167,9 +167,9 @@ export default class PersistedElement extends React.Component {
width: parentRect.width + 'px',
height: parentRect.height + 'px',
});
}
}, 100, {trailing: true, leading: true});
render() {
return <div ref={this.collectChildContainer}></div>;
return <div ref={this.collectChildContainer} />;
}
}

View file

@ -76,7 +76,7 @@ export default class PersistentApp extends React.Component {
userId={MatrixClientPeg.get().credentials.userId}
show={true}
creatorUserId={app.creatorUserId}
widgetPageTitle={(app.data && app.data.title) ? app.data.title : ''}
widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)}
waitForIframeLoad={app.waitForIframeLoad}
whitelistCapabilities={capWhitelist}
showDelete={false}

View file

@ -29,7 +29,7 @@ import {Action} from "../../../dispatcher/actions";
// For URLs of matrix.to links in the timeline which have been reformatted by
// HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`)
const REGEX_LOCAL_PERMALINK = /^#\/(?:user|room|group)\/(([#!@+])[^/]*)$/;
const REGEX_LOCAL_PERMALINK = /^#\/(?:user|room|group)\/(([#!@+]).*?)(?=\/|\?|$)/;
class Pill extends React.Component {
static isPillUrl(url) {

View file

@ -57,11 +57,14 @@ export default class PowerSelector extends React.Component {
customValue: this.props.value,
selectValue: 0,
};
this._initStateFromProps(this.props);
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
// eslint-disable-next-line camelcase
UNSAFE_componentWillMount() {
this._initStateFromProps(this.props);
}
// eslint-disable-next-line camelcase
UNSAFE_componentWillReceiveProps(newProps) {
this._initStateFromProps(newProps);

View file

@ -28,6 +28,7 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { getUserNameColorClass } from "../../../utils/FormattingUtils";
import {Action} from "../../../dispatcher/actions";
import sanitizeHtml from "sanitize-html";
import {UIFeature} from "../../../settings/UIFeature";
// This component does no cycle detection, simply because the only way to make such a cycle would be to
// craft event_id's, using a homeserver that generates predictable event IDs; even then the impact would
@ -361,6 +362,7 @@ export default class ReplyThread extends React.Component {
isRedacted={ev.isRedacted()}
isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")}
useIRCLayout={this.props.useIRCLayout}
enableFlair={SettingsStore.getValue(UIFeature.Flair)}
/>
</blockquote>;
});