Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into room-preview-spaces

This commit is contained in:
Jaiwanth 2021-05-03 21:53:54 +05:30
commit cf9edb1884
97 changed files with 819 additions and 568 deletions

View file

@ -15,7 +15,6 @@ module.exports = {
"prefer-promise-reject-errors": "off", "prefer-promise-reject-errors": "off",
"no-async-promise-executor": "off", "no-async-promise-executor": "off",
"quotes": "off", "quotes": "off",
"indent": "off",
}, },
overrides: [{ overrides: [{

View file

@ -34,6 +34,10 @@ limitations under the License.
border-color: $reaction-row-button-selected-border-color; border-color: $reaction-row-button-selected-border-color;
} }
&.mx_AccessibleButton_disabled {
cursor: not-allowed;
}
.mx_ReactionsRowButton_content { .mx_ReactionsRowButton_content {
max-width: 100px; max-width: 100px;
overflow: hidden; overflow: hidden;

View file

@ -148,13 +148,15 @@ function _onGroupAddRoomFinished(groupId, addrs, addRoomsPublicly) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog( Modal.createTrackedDialog(
'Failed to add the following room to the group', 'Failed to add the following room to the group',
'', ErrorDialog, '',
{ ErrorDialog,
title: _t( {
"Failed to add the following rooms to %(groupId)s:", title: _t(
{groupId}, "Failed to add the following rooms to %(groupId)s:",
), {groupId},
description: errorList.join(", "), ),
}); description: errorList.join(", "),
},
);
}); });
} }

View file

@ -163,7 +163,7 @@ export default class IdentityAuthClient {
</div> </div>
), ),
button: _t("Trust"), button: _t("Trust"),
}); });
const [confirmed] = await finished; const [confirmed] = await finished;
if (confirmed) { if (confirmed) {
// eslint-disable-next-line react-hooks/rules-of-hooks // eslint-disable-next-line react-hooks/rules-of-hooks

View file

@ -54,7 +54,7 @@ export default class PasswordReset {
return res; return res;
}, function(err) { }, function(err) {
if (err.errcode === 'M_THREEPID_NOT_FOUND') { if (err.errcode === 'M_THREEPID_NOT_FOUND') {
err.message = _t('This email address was not found'); err.message = _t('This email address was not found');
} else if (err.httpStatus) { } else if (err.httpStatus) {
err.message = err.message + ` (Status ${err.httpStatus})`; err.message = err.message + ` (Status ${err.httpStatus})`;
} }

View file

@ -547,17 +547,23 @@ function textForMjolnirEvent(event) {
// else the entity !== prevEntity - count as a removal & add // else the entity !== prevEntity - count as a removal & add
if (USER_RULE_TYPES.includes(event.getType())) { if (USER_RULE_TYPES.includes(event.getType())) {
return _t("%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching " + return _t(
"%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching " +
"%(newGlob)s for %(reason)s", "%(newGlob)s for %(reason)s",
{senderName, oldGlob: prevEntity, newGlob: entity, reason}); {senderName, oldGlob: prevEntity, newGlob: entity, reason},
);
} else if (ROOM_RULE_TYPES.includes(event.getType())) { } else if (ROOM_RULE_TYPES.includes(event.getType())) {
return _t("%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching " + return _t(
"%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching " +
"%(newGlob)s for %(reason)s", "%(newGlob)s for %(reason)s",
{senderName, oldGlob: prevEntity, newGlob: entity, reason}); {senderName, oldGlob: prevEntity, newGlob: entity, reason},
);
} else if (SERVER_RULE_TYPES.includes(event.getType())) { } else if (SERVER_RULE_TYPES.includes(event.getType())) {
return _t("%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching " + return _t(
"%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching " +
"%(newGlob)s for %(reason)s", "%(newGlob)s for %(reason)s",
{senderName, oldGlob: prevEntity, newGlob: entity, reason}); {senderName, oldGlob: prevEntity, newGlob: entity, reason},
);
} }
// Unknown type. We'll say something but we shouldn't end up here. // Unknown type. We'll say something but we shouldn't end up here.

View file

@ -498,9 +498,9 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
title={this._titleForPhase(this.state.phase)} title={this._titleForPhase(this.state.phase)}
hasCancel={[PHASE_PASSPHRASE, PHASE_DONE].includes(this.state.phase)} hasCancel={[PHASE_PASSPHRASE, PHASE_DONE].includes(this.state.phase)}
> >
<div> <div>
{content} {content}
</div> </div>
</BaseDialog> </BaseDialog>
); );
} }

View file

@ -856,9 +856,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
hasCancel={this.props.hasCancel && [PHASE_PASSPHRASE].includes(this.state.phase)} hasCancel={this.props.hasCancel && [PHASE_PASSPHRASE].includes(this.state.phase)}
fixedWidth={false} fixedWidth={false}
> >
<div> <div>
{content} {content}
</div> </div>
</BaseDialog> </BaseDialog>
); );
} }

View file

@ -170,8 +170,11 @@ export default class ExportE2eKeysDialog extends React.Component {
</div> </div>
</div> </div>
<div className='mx_Dialog_buttons'> <div className='mx_Dialog_buttons'>
<input className='mx_Dialog_primary' type='submit' value={_t('Export')} <input
disabled={disableForm} className='mx_Dialog_primary'
type='submit'
value={_t('Export')}
disabled={disableForm}
/> />
<button onClick={this._onCancelClick} disabled={disableForm}> <button onClick={this._onCancelClick} disabled={disableForm}>
{ _t("Cancel") } { _t("Cancel") }

View file

@ -140,36 +140,36 @@ export default class ImportE2eKeysDialog extends React.Component {
</div> </div>
<div className='mx_E2eKeysDialog_inputTable'> <div className='mx_E2eKeysDialog_inputTable'>
<div className='mx_E2eKeysDialog_inputRow'> <div className='mx_E2eKeysDialog_inputRow'>
<div className='mx_E2eKeysDialog_inputLabel'> <div className='mx_E2eKeysDialog_inputLabel'>
<label htmlFor='importFile'> <label htmlFor='importFile'>
{ _t("File to import") } { _t("File to import") }
</label> </label>
</div> </div>
<div className='mx_E2eKeysDialog_inputCell'> <div className='mx_E2eKeysDialog_inputCell'>
<input <input
ref={this._file} ref={this._file}
id='importFile' id='importFile'
type='file' type='file'
autoFocus={true} autoFocus={true}
onChange={this._onFormChange} onChange={this._onFormChange}
disabled={disableForm} /> disabled={disableForm} />
</div> </div>
</div> </div>
<div className='mx_E2eKeysDialog_inputRow'> <div className='mx_E2eKeysDialog_inputRow'>
<div className='mx_E2eKeysDialog_inputLabel'> <div className='mx_E2eKeysDialog_inputLabel'>
<label htmlFor='passphrase'> <label htmlFor='passphrase'>
{ _t("Enter passphrase") } { _t("Enter passphrase") }
</label> </label>
</div> </div>
<div className='mx_E2eKeysDialog_inputCell'> <div className='mx_E2eKeysDialog_inputCell'>
<input <input
ref={this._passphrase} ref={this._passphrase}
id='passphrase' id='passphrase'
size='64' size='64'
type='password' type='password'
onChange={this._onFormChange} onChange={this._onFormChange}
disabled={disableForm} /> disabled={disableForm} />
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -200,10 +200,10 @@ class FilePanel extends React.Component {
previousPhase={RightPanelPhases.RoomSummary} previousPhase={RightPanelPhases.RoomSummary}
> >
<div className="mx_RoomView_empty"> <div className="mx_RoomView_empty">
{ _t("You must <a>register</a> to use this functionality", { _t("You must <a>register</a> to use this functionality",
{}, {},
{ 'a': (sub) => <a href="#/register" key="sub">{ sub }</a> }) { 'a': (sub) => <a href="#/register" key="sub">{ sub }</a> })
} }
</div> </div>
</BaseCard>; </BaseCard>;
} else if (this.noRoom) { } else if (this.noRoom) {

View file

@ -153,17 +153,17 @@ class GroupFilterPanel extends React.Component {
type="draggable-TagTile" type="draggable-TagTile"
> >
{ (provided, snapshot) => ( { (provided, snapshot) => (
<div <div
className="mx_GroupFilterPanel_tagTileContainer" className="mx_GroupFilterPanel_tagTileContainer"
ref={provided.innerRef} ref={provided.innerRef}
> >
{ this.renderGlobalIcon() } { this.renderGlobalIcon() }
{ tags } { tags }
<div> <div>
{createButton} {createButton}
</div>
{ provided.placeholder }
</div> </div>
{ provided.placeholder }
</div>
) } ) }
</Droppable> </Droppable>
</AutoHideScrollbar> </AutoHideScrollbar>

View file

@ -43,7 +43,7 @@ import {mediaFromMxc} from "../../customisations/Media";
import {replaceableComponent} from "../../utils/replaceableComponent"; import {replaceableComponent} from "../../utils/replaceableComponent";
const LONG_DESC_PLACEHOLDER = _td( const LONG_DESC_PLACEHOLDER = _td(
`<h1>HTML for your community's page</h1> `<h1>HTML for your community's page</h1>
<p> <p>
Use the long description to introduce new members to the community, or distribute Use the long description to introduce new members to the community, or distribute
some important <a href="foo">links</a> some important <a href="foo">links</a>
@ -110,14 +110,16 @@ class CategoryRoomList extends React.Component {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog( Modal.createTrackedDialog(
'Failed to add the following room to the group summary', 'Failed to add the following room to the group summary',
'', ErrorDialog, '',
{ ErrorDialog,
title: _t( {
"Failed to add the following rooms to the summary of %(groupId)s:", title: _t(
{groupId: this.props.groupId}, "Failed to add the following rooms to the summary of %(groupId)s:",
), {groupId: this.props.groupId},
description: errorList.join(", "), ),
}); description: errorList.join(", "),
},
);
}); });
}, },
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
@ -146,8 +148,8 @@ class CategoryRoomList extends React.Component {
let catHeader = <div />; let catHeader = <div />;
if (this.props.category && this.props.category.profile) { if (this.props.category && this.props.category.profile) {
catHeader = <div className="mx_GroupView_featuredThings_category"> catHeader = <div className="mx_GroupView_featuredThings_category">
{ this.props.category.profile.name } { this.props.category.profile.name }
</div>; </div>;
} }
return <div className="mx_GroupView_featuredThings_container"> return <div className="mx_GroupView_featuredThings_container">
{ catHeader } { catHeader }
@ -190,13 +192,14 @@ class FeaturedRoom extends React.Component {
Modal.createTrackedDialog( Modal.createTrackedDialog(
'Failed to remove room from group summary', 'Failed to remove room from group summary',
'', ErrorDialog, '', ErrorDialog,
{ {
title: _t( title: _t(
"Failed to remove the room from the summary of %(groupId)s", "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}),
}); },
);
}); });
}; };
@ -283,13 +286,14 @@ class RoleUserList extends React.Component {
Modal.createTrackedDialog( Modal.createTrackedDialog(
'Failed to add the following users to the community summary', 'Failed to add the following users to the community summary',
'', ErrorDialog, '', ErrorDialog,
{ {
title: _t( title: _t(
"Failed to add the following users to the summary of %(groupId)s:", "Failed to add the following users to the summary of %(groupId)s:",
{groupId: this.props.groupId}, {groupId: this.props.groupId},
), ),
description: errorList.join(", "), description: errorList.join(", "),
}); },
);
}); });
}, },
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
@ -299,11 +303,11 @@ class RoleUserList extends React.Component {
const TintableSvg = sdk.getComponent("elements.TintableSvg"); const TintableSvg = sdk.getComponent("elements.TintableSvg");
const addButton = this.props.editing ? const addButton = this.props.editing ?
(<AccessibleButton className="mx_GroupView_featuredThings_addButton" onClick={this.onAddUsersClicked}> (<AccessibleButton className="mx_GroupView_featuredThings_addButton" onClick={this.onAddUsersClicked}>
<TintableSvg src={require("../../../res/img/icons-create-room.svg")} width="64" height="64" /> <TintableSvg src={require("../../../res/img/icons-create-room.svg")} width="64" height="64" />
<div className="mx_GroupView_featuredThings_addButton_label"> <div className="mx_GroupView_featuredThings_addButton_label">
{ _t('Add a User') } { _t('Add a User') }
</div> </div>
</AccessibleButton>) : <div />; </AccessibleButton>) : <div />;
const userNodes = this.props.users.map((u) => { const userNodes = this.props.users.map((u) => {
return <FeaturedUser return <FeaturedUser
key={u.user_id} key={u.user_id}
@ -352,14 +356,16 @@ class FeaturedUser extends React.Component {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog( Modal.createTrackedDialog(
'Failed to remove user from community summary', 'Failed to remove user from community summary',
'', ErrorDialog, '',
{ ErrorDialog,
title: _t( {
"Failed to remove a user from the summary of %(groupId)s", title: _t(
{groupId: this.props.groupId}, "Failed to remove a user from the summary of %(groupId)s",
), {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}),
},
);
}); });
}; };
@ -767,8 +773,8 @@ export default class GroupView extends React.Component {
title: _t("Leave Community"), title: _t("Leave Community"),
description: ( description: (
<span> <span>
{ _t("Leave %(groupName)s?", {groupName: this.props.groupId}) } { _t("Leave %(groupName)s?", {groupName: this.props.groupId}) }
{ warnings } { warnings }
</span> </span>
), ),
button: _t("Leave"), button: _t("Leave"),
@ -1055,10 +1061,11 @@ export default class GroupView extends React.Component {
return null; return null;
} }
const membershipButtonClasses = classnames([ const membershipButtonClasses = classnames(
'mx_RoomHeader_textButton', [
'mx_GroupView_textButton', 'mx_RoomHeader_textButton',
], 'mx_GroupView_textButton',
],
membershipButtonExtraClasses, membershipButtonExtraClasses,
); );

View file

@ -347,7 +347,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
if (element) { if (element) {
classes = element.classList; classes = element.classList;
} }
} while (element && !cssClasses.some(c => classes.contains(c))); } while (element && (!cssClasses.some(c => classes.contains(c)) || element.offsetParent === null));
if (element) { if (element) {
element.focus(); element.focus();

View file

@ -427,8 +427,10 @@ export default class MessagePanel extends React.Component {
// we get a new DOM node (restarting the animation) when the ghost // we get a new DOM node (restarting the animation) when the ghost
// moves to a different event. // moves to a different event.
return ( return (
<li key={"_readuptoghost_"+eventId} <li
className="mx_RoomView_myReadMarker_container"> key={"_readuptoghost_"+eventId}
className="mx_RoomView_myReadMarker_container"
>
{ hr } { hr }
</li> </li>
); );
@ -1014,13 +1016,13 @@ class CreationGrouper {
ret.push( ret.push(
<EventListSummary <EventListSummary
key="roomcreationsummary" key="roomcreationsummary"
events={this.events} events={this.events}
onToggle={panel._onHeightChanged} // Update scroll state onToggle={panel._onHeightChanged} // Update scroll state
summaryMembers={[ev.sender]} summaryMembers={[ev.sender]}
summaryText={summaryText} summaryText={summaryText}
> >
{ eventTiles } { eventTiles }
</EventListSummary>, </EventListSummary>,
); );
@ -1222,11 +1224,11 @@ class MemberGrouper {
ret.push( ret.push(
<MemberEventListSummary key={key} <MemberEventListSummary key={key}
events={this.events} events={this.events}
onToggle={panel._onHeightChanged} // Update scroll state onToggle={panel._onHeightChanged} // Update scroll state
startExpanded={highlightInMels} startExpanded={highlightInMels}
> >
{ eventTiles } { eventTiles }
</MemberEventListSummary>, </MemberEventListSummary>,
); );

View file

@ -17,6 +17,8 @@ limitations under the License.
import * as React from "react"; import * as React from "react";
import { createRef } from "react"; import { createRef } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { Room } from "matrix-js-sdk/src/models/room";
import defaultDispatcher from "../../dispatcher/dispatcher"; import defaultDispatcher from "../../dispatcher/dispatcher";
import { _t } from "../../languageHandler"; import { _t } from "../../languageHandler";
import { ActionPayload } from "../../dispatcher/payloads"; import { ActionPayload } from "../../dispatcher/payloads";
@ -26,7 +28,7 @@ import RoomListStore from "../../stores/room-list/RoomListStore";
import { NameFilterCondition } from "../../stores/room-list/filters/NameFilterCondition"; import { NameFilterCondition } from "../../stores/room-list/filters/NameFilterCondition";
import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager"; import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager";
import {replaceableComponent} from "../../utils/replaceableComponent"; import {replaceableComponent} from "../../utils/replaceableComponent";
import SpaceStore, {UPDATE_SELECTED_SPACE} from "../../stores/SpaceStore"; import SpaceStore, {UPDATE_SELECTED_SPACE, UPDATE_TOP_LEVEL_SPACES} from "../../stores/SpaceStore";
interface IProps { interface IProps {
isMinimized: boolean; isMinimized: boolean;
@ -40,6 +42,7 @@ interface IProps {
interface IState { interface IState {
query: string; query: string;
focused: boolean; focused: boolean;
inSpaces: boolean;
} }
@replaceableComponent("structures.RoomSearch") @replaceableComponent("structures.RoomSearch")
@ -54,11 +57,13 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
this.state = { this.state = {
query: "", query: "",
focused: false, focused: false,
inSpaces: false,
}; };
this.dispatcherRef = defaultDispatcher.register(this.onAction); this.dispatcherRef = defaultDispatcher.register(this.onAction);
// clear filter when changing spaces, in future we may wish to maintain a filter per-space // clear filter when changing spaces, in future we may wish to maintain a filter per-space
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.clearInput); SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.clearInput);
SpaceStore.instance.on(UPDATE_TOP_LEVEL_SPACES, this.onSpaces);
} }
public componentDidUpdate(prevProps: Readonly<IProps>, prevState: Readonly<IState>): void { public componentDidUpdate(prevProps: Readonly<IProps>, prevState: Readonly<IState>): void {
@ -79,8 +84,15 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
public componentWillUnmount() { public componentWillUnmount() {
defaultDispatcher.unregister(this.dispatcherRef); defaultDispatcher.unregister(this.dispatcherRef);
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.clearInput); SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.clearInput);
SpaceStore.instance.off(UPDATE_TOP_LEVEL_SPACES, this.onSpaces);
} }
private onSpaces = (spaces: Room[]) => {
this.setState({
inSpaces: spaces.length > 0,
});
};
private onAction = (payload: ActionPayload) => { private onAction = (payload: ActionPayload) => {
if (payload.action === 'view_room' && payload.clear_search) { if (payload.action === 'view_room' && payload.clear_search) {
this.clearInput(); this.clearInput();
@ -152,6 +164,11 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
'mx_RoomSearch_inputExpanded': this.state.query || this.state.focused, 'mx_RoomSearch_inputExpanded': this.state.query || this.state.focused,
}); });
let placeholder = _t("Filter");
if (this.state.inSpaces) {
placeholder = _t("Filter all spaces");
}
let icon = ( let icon = (
<div className='mx_RoomSearch_icon' /> <div className='mx_RoomSearch_icon' />
); );
@ -165,7 +182,7 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
onBlur={this.onBlur} onBlur={this.onBlur}
onChange={this.onChange} onChange={this.onChange}
onKeyDown={this.onKeyDown} onKeyDown={this.onKeyDown}
placeholder={_t("Filter")} placeholder={placeholder}
autoComplete="off" autoComplete="off"
/> />
); );

View file

@ -200,20 +200,22 @@ export default class RoomStatusBar extends React.Component {
} else if (resourceLimitError) { } else if (resourceLimitError) {
title = messageForResourceLimitError( title = messageForResourceLimitError(
resourceLimitError.data.limit_type, resourceLimitError.data.limit_type,
resourceLimitError.data.admin_contact, { resourceLimitError.data.admin_contact,
'monthly_active_user': _td( {
"Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. " + 'monthly_active_user': _td(
"Please <a>contact your service administrator</a> to continue using the service.", "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. " +
), "Please <a>contact your service administrator</a> to continue using the service.",
'hs_disabled': _td( ),
"Your message wasn't sent because this homeserver has been blocked by it's administrator. " + 'hs_disabled': _td(
"Please <a>contact your service administrator</a> to continue using the service.", "Your message wasn't sent because this homeserver has been blocked by it's administrator. " +
), "Please <a>contact your service administrator</a> to continue using the service.",
'': _td( ),
"Your message wasn't sent because this homeserver has exceeded a resource limit. " + '': _td(
"Please <a>contact your service administrator</a> to continue using the service.", "Your message wasn't sent because this homeserver has exceeded a resource limit. " +
), "Please <a>contact your service administrator</a> to continue using the service.",
}); ),
},
);
} else { } else {
title = _t('Some of your messages have not been sent'); title = _t('Some of your messages have not been sent');
} }
@ -265,7 +267,7 @@ export default class RoomStatusBar extends React.Component {
<div role="alert"> <div role="alert">
<div className="mx_RoomStatusBar_connectionLostBar"> <div className="mx_RoomStatusBar_connectionLostBar">
<img src={require("../../../res/img/feather-customised/warning-triangle.svg")} width="24" <img src={require("../../../res/img/feather-customised/warning-triangle.svg")} width="24"
height="24" title="/!\ " alt="/!\ " /> height="24" title="/!\ " alt="/!\ " />
<div> <div>
<div className="mx_RoomStatusBar_connectionLostBar_title"> <div className="mx_RoomStatusBar_connectionLostBar_title">
{_t('Connectivity to the server has been lost.')} {_t('Connectivity to the server has been lost.')}

View file

@ -190,6 +190,9 @@ export interface IState {
rejectError?: Error; rejectError?: Error;
hasPinnedWidgets?: boolean; hasPinnedWidgets?: boolean;
dragCounter: number; dragCounter: number;
// 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;
} }
@replaceableComponent("structures.RoomView") @replaceableComponent("structures.RoomView")
@ -326,6 +329,7 @@ export default class RoomView extends React.Component<IProps, IState> {
shouldPeek: this.state.matrixClientIsReady && RoomViewStore.shouldPeek(), shouldPeek: this.state.matrixClientIsReady && RoomViewStore.shouldPeek(),
showingPinned: SettingsStore.getValue("PinnedEvents.isOpen", roomId), showingPinned: SettingsStore.getValue("PinnedEvents.isOpen", roomId),
showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId), showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId),
wasContextSwitch: RoomViewStore.getWasContextSwitch(),
}; };
if (!initial && this.state.shouldPeek && !newState.shouldPeek) { if (!initial && this.state.shouldPeek && !newState.shouldPeek) {
@ -2014,6 +2018,7 @@ export default class RoomView extends React.Component<IProps, IState> {
timelineSet={this.state.room.getUnfilteredTimelineSet()} timelineSet={this.state.room.getUnfilteredTimelineSet()}
showReadReceipts={this.state.showReadReceipts} showReadReceipts={this.state.showReadReceipts}
manageReadReceipts={!this.state.isPeeking} manageReadReceipts={!this.state.isPeeking}
sendReadReceiptOnLoad={!this.state.wasContextSwitch}
manageReadMarkers={!this.state.isPeeking} manageReadMarkers={!this.state.isPeeking}
hidden={hideMessagePanel} hidden={hideMessagePanel}
highlightedEventId={highlightedEventId} highlightedEventId={highlightedEventId}

View file

@ -884,16 +884,20 @@ export default class ScrollPanel extends React.Component {
// give the <ol> an explicit role=list because Safari+VoiceOver seems to think an ordered-list with // give the <ol> an explicit role=list because Safari+VoiceOver seems to think an ordered-list with
// list-style-type: none; is no longer a list // list-style-type: none; is no longer a list
return (<AutoHideScrollbar wrappedRef={this._collectScroll} return (
<AutoHideScrollbar
wrappedRef={this._collectScroll}
onScroll={this.onScroll} onScroll={this.onScroll}
className={`mx_ScrollPanel ${this.props.className}`} style={this.props.style}> className={`mx_ScrollPanel ${this.props.className}`}
{ this.props.fixedChildren } style={this.props.style}
<div className="mx_RoomView_messageListWrapper"> >
<ol ref={this._itemlist} className="mx_RoomView_MessageList" aria-live="polite" role="list"> { this.props.fixedChildren }
{ this.props.children } <div className="mx_RoomView_messageListWrapper">
</ol> <ol ref={this._itemlist} className="mx_RoomView_MessageList" aria-live="polite" role="list">
</div> { this.props.children }
</AutoHideScrollbar> </ol>
); </div>
</AutoHideScrollbar>
);
} }
} }

View file

@ -68,6 +68,7 @@ class TimelinePanel extends React.Component {
showReadReceipts: PropTypes.bool, showReadReceipts: PropTypes.bool,
// Enable managing RRs and RMs. These require the timelineSet to have a room. // Enable managing RRs and RMs. These require the timelineSet to have a room.
manageReadReceipts: PropTypes.bool, manageReadReceipts: PropTypes.bool,
sendReadReceiptOnLoad: PropTypes.bool,
manageReadMarkers: PropTypes.bool, manageReadMarkers: PropTypes.bool,
// true to give the component a 'display: none' style. // true to give the component a 'display: none' style.
@ -126,6 +127,7 @@ class TimelinePanel extends React.Component {
// event tile heights. (See _unpaginateEvents) // event tile heights. (See _unpaginateEvents)
timelineCap: Number.MAX_VALUE, timelineCap: Number.MAX_VALUE,
className: 'mx_RoomView_messagePanel', className: 'mx_RoomView_messagePanel',
sendReadReceiptOnLoad: true,
}; };
constructor(props) { constructor(props) {
@ -785,8 +787,10 @@ class TimelinePanel extends React.Component {
return; return;
} }
const lastDisplayedEvent = this.state.events[lastDisplayedIndex]; const lastDisplayedEvent = this.state.events[lastDisplayedIndex];
this._setReadMarker(lastDisplayedEvent.getId(), this._setReadMarker(
lastDisplayedEvent.getTs()); lastDisplayedEvent.getId(),
lastDisplayedEvent.getTs(),
);
// the read-marker should become invisible, so that if the user scrolls // the read-marker should become invisible, so that if the user scrolls
// down, they don't see it. // down, they don't see it.
@ -872,7 +876,7 @@ class TimelinePanel extends React.Component {
// The messagepanel knows where the RM is, so we must have loaded // The messagepanel knows where the RM is, so we must have loaded
// the relevant event. // the relevant event.
this._messagePanel.current.scrollToEvent(this.state.readMarkerEventId, this._messagePanel.current.scrollToEvent(this.state.readMarkerEventId,
0, 1/3); 0, 1/3);
return; return;
} }
@ -1044,12 +1048,14 @@ class TimelinePanel extends React.Component {
} }
if (eventId) { if (eventId) {
this._messagePanel.current.scrollToEvent(eventId, pixelOffset, this._messagePanel.current.scrollToEvent(eventId, pixelOffset,
offsetBase); offsetBase);
} else { } else {
this._messagePanel.current.scrollToBottom(); this._messagePanel.current.scrollToBottom();
} }
this.sendReadReceipt(); if (this.props.sendReadReceiptOnLoad) {
this.sendReadReceipt();
}
}); });
}; };
@ -1418,8 +1424,8 @@ class TimelinePanel extends React.Component {
['PREPARED', 'CATCHUP'].includes(this.state.clientSyncState) ['PREPARED', 'CATCHUP'].includes(this.state.clientSyncState)
); );
const events = this.state.firstVisibleEventIndex const events = this.state.firstVisibleEventIndex
? this.state.events.slice(this.state.firstVisibleEventIndex) ? this.state.events.slice(this.state.firstVisibleEventIndex)
: this.state.events; : this.state.events;
return ( return (
<MessagePanel <MessagePanel
ref={this._messagePanel} ref={this._messagePanel}

View file

@ -169,7 +169,7 @@ export class PasswordAuthEntry extends React.Component {
{ submitButtonOrSpinner } { submitButtonOrSpinner }
</div> </div>
</form> </form>
{ errorSection } { errorSection }
</div> </div>
); );
} }
@ -375,7 +375,7 @@ export class TermsAuthEntry extends React.Component {
if (this.props.showContinue !== false) { if (this.props.showContinue !== false) {
// XXX: button classes // XXX: button classes
submitButton = <button className="mx_InteractiveAuthEntryComponents_termsSubmit mx_GeneralButton" submitButton = <button className="mx_InteractiveAuthEntryComponents_termsSubmit mx_GeneralButton"
onClick={this._trySubmit} disabled={!allChecked}>{_t("Accept")}</button>; onClick={this._trySubmit} disabled={!allChecked}>{_t("Accept")}</button>;
} }
return ( return (

View file

@ -350,7 +350,7 @@ export default class MessageContextMenu extends React.Component {
> >
{ _t('Source URL') } { _t('Source URL') }
</MenuItem> </MenuItem>
); );
} }
if (this.props.collapseReplyThread) { if (this.props.collapseReplyThread) {

View file

@ -184,7 +184,7 @@ export default class BugReportDialog extends React.Component {
return ( return (
<BaseDialog className="mx_BugReportDialog" onFinished={this._onCancel} <BaseDialog className="mx_BugReportDialog" onFinished={this._onCancel}
title={_t('Submit debug logs')} title={_t('Submit debug logs')}
contentId='mx_Dialog_content' contentId='mx_Dialog_content'
> >
<div className="mx_Dialog_content" id='mx_Dialog_content'> <div className="mx_Dialog_content" id='mx_Dialog_content'>

View file

@ -95,7 +95,7 @@ export default class ChangelogDialog extends React.Component {
description={content} description={content}
button={_t("Update")} button={_t("Update")}
onFinished={this.props.onFinished} onFinished={this.props.onFinished}
/> />
); );
} }
} }

View file

@ -39,9 +39,12 @@ export default class ConfirmWipeDeviceDialog extends React.Component {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return ( return (
<BaseDialog className='mx_ConfirmWipeDeviceDialog' hasCancel={true} <BaseDialog
onFinished={this.props.onFinished} className='mx_ConfirmWipeDeviceDialog'
title={_t("Clear all data in this session?")}> hasCancel={true}
onFinished={this.props.onFinished}
title={_t("Clear all data in this session?")}
>
<div className='mx_ConfirmWipeDeviceDialog_content'> <div className='mx_ConfirmWipeDeviceDialog_content'>
<p> <p>
{_t( {_t(

View file

@ -70,8 +70,16 @@ class GenericEditor extends React.PureComponent {
} }
textInput(id, label) { textInput(id, label) {
return <Field id={id} label={label} size="42" autoFocus={true} type="text" autoComplete="on" return <Field
value={this.state[id]} onChange={this._onChange} />; id={id}
label={label}
size="42"
autoFocus={true}
type="text"
autoComplete="on"
value={this.state[id]}
onChange={this._onChange}
/>;
} }
} }
@ -155,7 +163,7 @@ export class SendCustomEvent extends GenericEditor {
<br /> <br />
<Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea" <Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea"
autoComplete="off" value={this.state.evContent} onChange={this._onChange} element="textarea" /> autoComplete="off" value={this.state.evContent} onChange={this._onChange} element="textarea" />
</div> </div>
<div className="mx_Dialog_buttons"> <div className="mx_Dialog_buttons">
<button onClick={this.onBack}>{ _t('Back') }</button> <button onClick={this.onBack}>{ _t('Back') }</button>
@ -239,7 +247,7 @@ class SendAccountData extends GenericEditor {
<br /> <br />
<Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea" <Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea"
autoComplete="off" value={this.state.evContent} onChange={this._onChange} element="textarea" /> autoComplete="off" value={this.state.evContent} onChange={this._onChange} element="textarea" />
</div> </div>
<div className="mx_Dialog_buttons"> <div className="mx_Dialog_buttons">
<button onClick={this.onBack}>{ _t('Back') }</button> <button onClick={this.onBack}>{ _t('Back') }</button>
@ -315,15 +323,15 @@ class FilteredList extends React.PureComponent {
const TruncatedList = sdk.getComponent("elements.TruncatedList"); const TruncatedList = sdk.getComponent("elements.TruncatedList");
return <div> return <div>
<Field label={_t('Filter results')} autoFocus={true} size={64} <Field label={_t('Filter results')} autoFocus={true} size={64}
type="text" autoComplete="off" value={this.props.query} onChange={this.onQuery} type="text" autoComplete="off" value={this.props.query} onChange={this.onQuery}
className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query" className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query"
// force re-render so that autoFocus is applied when this component is re-used // force re-render so that autoFocus is applied when this component is re-used
key={this.props.children[0] ? this.props.children[0].key : ''} /> key={this.props.children[0] ? this.props.children[0].key : ''} />
<TruncatedList getChildren={this.getChildren} <TruncatedList getChildren={this.getChildren}
getChildCount={this.getChildCount} getChildCount={this.getChildCount}
truncateAt={this.state.truncateAt} truncateAt={this.state.truncateAt}
createOverflowElement={this.createOverflowElement} /> createOverflowElement={this.createOverflowElement} />
</div>; </div>;
} }
} }
@ -647,7 +655,7 @@ function VerificationRequest({txnId, request}) {
/* Note that request.timeout is a getter, so its value changes */ /* Note that request.timeout is a getter, so its value changes */
const id = setInterval(() => { const id = setInterval(() => {
setRequestTimeout(request.timeout); setRequestTimeout(request.timeout);
}, 500); }, 500);
return () => { clearInterval(id); }; return () => { clearInterval(id); };
@ -941,35 +949,35 @@ class SettingsExplorer extends React.Component {
/> />
<table> <table>
<thead> <thead>
<tr> <tr>
<th>{_t("Setting ID")}</th> <th>{_t("Setting ID")}</th>
<th>{_t("Value")}</th> <th>{_t("Value")}</th>
<th>{_t("Value in this room")}</th> <th>{_t("Value in this room")}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{allSettings.map(i => ( {allSettings.map(i => (
<tr key={i}> <tr key={i}>
<td> <td>
<a href="" onClick={(e) => this.onViewClick(e, i)}> <a href="" onClick={(e) => this.onViewClick(e, i)}>
<code>{i}</code> <code>{i}</code>
</a> </a>
<a href="" onClick={(e) => this.onEditClick(e, i)} <a href="" onClick={(e) => this.onEditClick(e, i)}
className='mx_DevTools_SettingsExplorer_edit' className='mx_DevTools_SettingsExplorer_edit'
> >
</a> </a>
</td> </td>
<td> <td>
<code>{this.renderSettingValue(SettingsStore.getValue(i))}</code> <code>{this.renderSettingValue(SettingsStore.getValue(i))}</code>
</td> </td>
<td> <td>
<code> <code>
{this.renderSettingValue(SettingsStore.getValue(i, room.roomId))} {this.renderSettingValue(SettingsStore.getValue(i, room.roomId))}
</code> </code>
</td> </td>
</tr> </tr>
))} ))}
</tbody> </tbody>
</table> </table>
</div> </div>
@ -998,11 +1006,11 @@ class SettingsExplorer extends React.Component {
<div> <div>
<table> <table>
<thead> <thead>
<tr> <tr>
<th>{_t("Level")}</th> <th>{_t("Level")}</th>
<th>{_t("Settable at global")}</th> <th>{_t("Settable at global")}</th>
<th>{_t("Settable at room")}</th> <th>{_t("Settable at room")}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{LEVEL_ORDER.map(lvl => ( {LEVEL_ORDER.map(lvl => (

View file

@ -42,9 +42,12 @@ export default class IntegrationsDisabledDialog extends React.Component {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return ( return (
<BaseDialog className='mx_IntegrationsDisabledDialog' hasCancel={true} <BaseDialog
onFinished={this.props.onFinished} className='mx_IntegrationsDisabledDialog'
title={_t("Integrations are disabled")}> hasCancel={true}
onFinished={this.props.onFinished}
title={_t("Integrations are disabled")}
>
<div className='mx_IntegrationsDisabledDialog_content'> <div className='mx_IntegrationsDisabledDialog_content'>
<p>{_t("Enable 'Manage Integrations' in Settings to do this.")}</p> <p>{_t("Enable 'Manage Integrations' in Settings to do this.")}</p>
</div> </div>

View file

@ -37,9 +37,12 @@ export default class IntegrationsImpossibleDialog extends React.Component {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return ( return (
<BaseDialog className='mx_IntegrationsImpossibleDialog' hasCancel={false} <BaseDialog
onFinished={this.props.onFinished} className='mx_IntegrationsImpossibleDialog'
title={_t("Integrations not allowed")}> hasCancel={false}
onFinished={this.props.onFinished}
title={_t("Integrations not allowed")}
>
<div className='mx_IntegrationsImpossibleDialog_content'> <div className='mx_IntegrationsImpossibleDialog_content'>
<p> <p>
{_t( {_t(

View file

@ -24,7 +24,7 @@ export default function KeySignatureUploadFailedDialog({
source, source,
continuation, continuation,
onFinished, onFinished,
}) { }) {
const RETRIES = 2; const RETRIES = 2;
const BaseDialog = sdk.getComponent('dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
@ -84,10 +84,10 @@ export default function KeySignatureUploadFailedDialog({
} else { } else {
body = (<div> body = (<div>
{success ? {success ?
<span>{_t("Upload completed")}</span> : <span>{_t("Upload completed")}</span> :
cancelled ? cancelled ?
<span>{_t("Cancelled signature upload")}</span> : <span>{_t("Cancelled signature upload")}</span> :
<span>{_t("Unable to upload")}</span>} <span>{_t("Unable to upload")}</span>}
<DialogButtons <DialogButtons
primaryButton={_t("OK")} primaryButton={_t("OK")}
hasCancel={false} hasCancel={false}

View file

@ -164,8 +164,12 @@ export default class MessageEditHistoryDialog extends React.PureComponent {
} }
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return ( return (
<BaseDialog className='mx_MessageEditHistoryDialog' hasCancel={true} <BaseDialog
onFinished={this.props.onFinished} title={_t("Message edits")}> className='mx_MessageEditHistoryDialog'
hasCancel={true}
onFinished={this.props.onFinished}
title={_t("Message edits")}
>
{content} {content}
</BaseDialog> </BaseDialog>
); );

View file

@ -116,8 +116,12 @@ export default class RoomSettingsDialog extends React.Component {
const roomName = MatrixClientPeg.get().getRoom(this.props.roomId).name; const roomName = MatrixClientPeg.get().getRoom(this.props.roomId).name;
return ( return (
<BaseDialog className='mx_RoomSettingsDialog' hasCancel={true} <BaseDialog
onFinished={this.props.onFinished} title={_t("Room Settings - %(roomName)s", {roomName})}> className='mx_RoomSettingsDialog'
hasCancel={true}
onFinished={this.props.onFinished}
title={_t("Room Settings - %(roomName)s", {roomName})}
>
<div className='mx_SettingsDialog_content'> <div className='mx_SettingsDialog_content'>
<TabbedView tabs={this._getTabs()} /> <TabbedView tabs={this._getTabs()} />
</div> </div>

View file

@ -98,7 +98,7 @@ export default class SessionRestoreErrorDialog extends React.Component {
"may be incompatible with this version. Close this window and return " + "may be incompatible with this version. Close this window and return " +
"to the more recent version.", "to the more recent version.",
{ brand }, { brand },
) }</p> ) }</p>
<p>{ _t( <p>{ _t(
"Clearing your browser's storage may fix the problem, but will sign you " + "Clearing your browser's storage may fix the problem, but will sign you " +

View file

@ -45,10 +45,12 @@ export default class StorageEvictedDialog extends React.Component {
let logRequest; let logRequest;
if (SdkConfig.get().bug_report_endpoint_url) { if (SdkConfig.get().bug_report_endpoint_url) {
logRequest = _t( logRequest = _t(
"To help us prevent this in future, please <a>send us logs</a>.", {}, "To help us prevent this in future, please <a>send us logs</a>.",
{ {},
a: text => <a href="#" onClick={this._sendBugReport}>{text}</a>, {
}); a: text => <a href="#" onClick={this._sendBugReport}>{text}</a>,
},
);
} }
return ( return (

View file

@ -155,8 +155,12 @@ export default class UserSettingsDialog extends React.Component {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return ( return (
<BaseDialog className='mx_UserSettingsDialog' hasCancel={true} <BaseDialog
onFinished={this.props.onFinished} title={_t("Settings")}> className='mx_UserSettingsDialog'
hasCancel={true}
onFinished={this.props.onFinished}
title={_t("Settings")}
>
<div className='mx_SettingsDialog_content'> <div className='mx_SettingsDialog_content'>
<TabbedView tabs={this._getTabs()} initialTabId={this.props.initialTabId} /> <TabbedView tabs={this._getTabs()} initialTabId={this.props.initialTabId} />
</div> </div>

View file

@ -52,11 +52,13 @@ export default class VerificationRequestDialog extends React.Component {
const title = request && request.isSelfVerification ? const title = request && request.isSelfVerification ?
_t("Verify other login") : _t("Verification Request"); _t("Verify other login") : _t("Verification Request");
return <BaseDialog className="mx_InfoDialog" onFinished={this.props.onFinished} return <BaseDialog
contentId="mx_Dialog_content" className="mx_InfoDialog"
title={title} onFinished={this.props.onFinished}
hasCancel={true} contentId="mx_Dialog_content"
> title={title}
hasCancel={true}
>
<EncryptionPanel <EncryptionPanel
layout="dialog" layout="dialog"
verificationRequest={this.props.verificationRequest} verificationRequest={this.props.verificationRequest}

View file

@ -70,9 +70,12 @@ export default class WidgetOpenIDPermissionsDialog extends React.Component {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return ( return (
<BaseDialog className='mx_WidgetOpenIDPermissionsDialog' hasCancel={true} <BaseDialog
onFinished={this.props.onFinished} className='mx_WidgetOpenIDPermissionsDialog'
title={_t("Allow this widget to verify your identity")}> hasCancel={true}
onFinished={this.props.onFinished}
title={_t("Allow this widget to verify your identity")}
>
<div className='mx_WidgetOpenIDPermissionsDialog_content'> <div className='mx_WidgetOpenIDPermissionsDialog_content'>
<p> <p>
{_t("The widget will verify your user ID, but won't be able to perform actions for you:")} {_t("The widget will verify your user ID, but won't be able to perform actions for you:")}

View file

@ -40,10 +40,11 @@ export default class ConfirmDestroyCrossSigningDialog extends React.Component {
return ( return (
<BaseDialog <BaseDialog
className='mx_ConfirmDestroyCrossSigningDialog' className='mx_ConfirmDestroyCrossSigningDialog'
hasCancel={true} hasCancel={true}
onFinished={this.props.onFinished} onFinished={this.props.onFinished}
title={_t("Destroy cross-signing keys?")}> title={_t("Destroy cross-signing keys?")}
>
<div className='mx_ConfirmDestroyCrossSigningDialog_content'> <div className='mx_ConfirmDestroyCrossSigningDialog_content'>
<p> <p>
{_t( {_t(

View file

@ -373,21 +373,24 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
{_t( {_t(
"If you've forgotten your Security Phrase you can "+ "If you've forgotten your Security Phrase you can "+
"<button1>use your Security Key</button1> or " + "<button1>use your Security Key</button1> or " +
"<button2>set up new recovery options</button2>" "<button2>set up new recovery options</button2>",
, {}, { {},
button1: s => <AccessibleButton className="mx_linkButton" {
element="span" button1: s => <AccessibleButton
onClick={this._onUseRecoveryKeyClick} className="mx_linkButton"
> element="span"
{s} onClick={this._onUseRecoveryKeyClick}
</AccessibleButton>, >
button2: s => <AccessibleButton className="mx_linkButton" {s}
element="span" </AccessibleButton>,
onClick={this._onResetRecoveryClick} button2: s => <AccessibleButton
> className="mx_linkButton"
{s} element="span"
</AccessibleButton>, onClick={this._onResetRecoveryClick}
})} >
{s}
</AccessibleButton>,
})}
</div>; </div>;
} else { } else {
title = _t("Enter Security Key"); title = _t("Enter Security Key");
@ -435,15 +438,17 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
</div> </div>
{_t( {_t(
"If you've forgotten your Security Key you can "+ "If you've forgotten your Security Key you can "+
"<button>set up new recovery options</button>" "<button>set up new recovery options</button>",
, {}, { {},
button: s => <AccessibleButton className="mx_linkButton" {
element="span" button: s => <AccessibleButton className="mx_linkButton"
onClick={this._onResetRecoveryClick} element="span"
> onClick={this._onResetRecoveryClick}
{s} >
</AccessibleButton>, {s}
})} </AccessibleButton>,
},
)}
</div>; </div>;
} }
@ -452,9 +457,9 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
onFinished={this.props.onFinished} onFinished={this.props.onFinished}
title={title} title={title}
> >
<div className='mx_RestoreKeyBackupDialog_content'> <div className='mx_RestoreKeyBackupDialog_content'>
{content} {content}
</div> </div>
</BaseDialog> </BaseDialog>
); );
} }

View file

@ -70,8 +70,8 @@ export default class ActionButton extends React.Component {
} }
const icon = this.props.iconPath ? const icon = this.props.iconPath ?
(<TintableSvg src={this.props.iconPath} width={this.props.size} height={this.props.size} />) : (<TintableSvg src={this.props.iconPath} width={this.props.size} height={this.props.size} />) :
undefined; undefined;
const classNames = ["mx_RoleButton"]; const classNames = ["mx_RoleButton"];
if (this.props.className) { if (this.props.className) {

View file

@ -109,7 +109,7 @@ export default class AppTile extends React.Component {
const childContentProtocol = u.protocol; const childContentProtocol = u.protocol;
if (parentContentProtocol === 'https:' && childContentProtocol !== 'https:') { if (parentContentProtocol === 'https:' && childContentProtocol !== 'https:') {
console.warn("Refusing to load mixed-content app:", console.warn("Refusing to load mixed-content app:",
parentContentProtocol, childContentProtocol, window.location, this.props.app.url); parentContentProtocol, childContentProtocol, window.location, this.props.app.url);
return true; return true;
} }
return false; return false;

View file

@ -65,12 +65,18 @@ export class EditableItem extends React.Component {
<span className="mx_EditableItem_promptText"> <span className="mx_EditableItem_promptText">
{_t("Are you sure?")} {_t("Are you sure?")}
</span> </span>
<AccessibleButton onClick={this._onActuallyRemove} kind="primary_sm" <AccessibleButton
className="mx_EditableItem_confirmBtn"> onClick={this._onActuallyRemove}
kind="primary_sm"
className="mx_EditableItem_confirmBtn"
>
{_t("Yes")} {_t("Yes")}
</AccessibleButton> </AccessibleButton>
<AccessibleButton onClick={this._onDontRemove} kind="danger_sm" <AccessibleButton
className="mx_EditableItem_confirmBtn"> onClick={this._onDontRemove}
kind="danger_sm"
className="mx_EditableItem_confirmBtn"
>
{_t("No")} {_t("No")}
</AccessibleButton> </AccessibleButton>
</div> </div>
@ -121,11 +127,15 @@ export default class EditableItemList extends React.Component {
_renderNewItemField() { _renderNewItemField() {
return ( return (
<form onSubmit={this._onItemAdded} autoComplete="off" <form
noValidate={true} className="mx_EditableItemList_newItem"> onSubmit={this._onItemAdded}
autoComplete="off"
noValidate={true}
className="mx_EditableItemList_newItem"
>
<Field label={this.props.placeholder} type="text" <Field label={this.props.placeholder} type="text"
autoComplete="off" value={this.props.newItem || ""} onChange={this._onNewItemChanged} autoComplete="off" value={this.props.newItem || ""} onChange={this._onNewItemChanged}
list={this.props.suggestionsListId} /> list={this.props.suggestionsListId} />
<AccessibleButton onClick={this._onItemAdded} kind="primary" type="submit" disabled={!this.props.newItem}> <AccessibleButton onClick={this._onItemAdded} kind="primary" type="submit" disabled={!this.props.newItem}>
{_t("Add")} {_t("Add")}
</AccessibleButton> </AccessibleButton>

View file

@ -221,13 +221,15 @@ export default class EditableText extends React.Component {
</div>; </div>;
} else { } else {
// show the content editable div, but manually manage its contents as react and contentEditable don't play nice together // show the content editable div, but manually manage its contents as react and contentEditable don't play nice together
editableEl = <div ref={this._editable_div} editableEl = <div
contentEditable={true} ref={this._editable_div}
className={className} contentEditable={true}
onKeyDown={this.onKeyDown} className={className}
onKeyUp={this.onKeyUp} onKeyDown={this.onKeyDown}
onFocus={this.onFocus} onKeyUp={this.onKeyUp}
onBlur={this.onBlur} />; onFocus={this.onFocus}
onBlur={this.onBlur}
/>;
} }
return editableEl; return editableEl;

View file

@ -46,8 +46,12 @@ export default class LabelledToggleSwitch extends React.Component {
// This is a minimal version of a SettingsFlag // This is a minimal version of a SettingsFlag
let firstPart = <span className="mx_SettingsFlag_label">{this.props.label}</span>; let firstPart = <span className="mx_SettingsFlag_label">{this.props.label}</span>;
let secondPart = <ToggleSwitch checked={this.props.value} disabled={this.props.disabled} let secondPart = <ToggleSwitch
onChange={this.props.onChange} aria-label={this.props.label} />; checked={this.props.value}
disabled={this.props.disabled}
onChange={this.props.onChange}
aria-label={this.props.label}
/>;
if (this.props.toggleInFront) { if (this.props.toggleInFront) {
const temp = firstPart; const temp = firstPart;

View file

@ -60,10 +60,10 @@ export default class LanguageDropdown extends React.Component {
// doesn't know this, therefore we do this. // doesn't know this, therefore we do this.
const language = SettingsStore.getValue("language", null, /*excludeDefault:*/true); const language = SettingsStore.getValue("language", null, /*excludeDefault:*/true);
if (language) { if (language) {
this.props.onOptionChange(language); this.props.onOptionChange(language);
} else { } else {
const language = languageHandler.normalizeLanguageKey(languageHandler.getLanguageFromBrowser()); const language = languageHandler.normalizeLanguageKey(languageHandler.getLanguageFromBrowser());
this.props.onOptionChange(language); this.props.onOptionChange(language);
} }
} }
} }

View file

@ -225,19 +225,19 @@ class Pill extends React.Component {
} }
break; break;
case Pill.TYPE_USER_MENTION: { case Pill.TYPE_USER_MENTION: {
// If this user is not a member of this room, default to the empty member // If this user is not a member of this room, default to the empty member
const member = this.state.member; const member = this.state.member;
if (member) { if (member) {
userId = member.userId; userId = member.userId;
member.rawDisplayName = member.rawDisplayName || ''; member.rawDisplayName = member.rawDisplayName || '';
linkText = member.rawDisplayName; linkText = member.rawDisplayName;
if (this.props.shouldShowPillAvatar) { if (this.props.shouldShowPillAvatar) {
avatar = <MemberAvatar member={member} width={16} height={16} aria-hidden="true" />; avatar = <MemberAvatar member={member} width={16} height={16} aria-hidden="true" />;
}
pillClass = 'mx_UserPill';
href = null;
onClick = this.onUserPillClicked;
} }
pillClass = 'mx_UserPill';
href = null;
onClick = this.onUserPillClicked;
}
} }
break; break;
case Pill.TYPE_ROOM_MENTION: { case Pill.TYPE_ROOM_MENTION: {

View file

@ -135,9 +135,13 @@ export default class PowerSelector extends React.Component {
if (this.state.custom) { if (this.state.custom) {
picker = ( picker = (
<Field type="number" <Field type="number"
label={label} max={this.props.maxValue} label={label} max={this.props.maxValue}
onBlur={this.onCustomBlur} onKeyDown={this.onCustomKeyDown} onChange={this.onCustomChange} onBlur={this.onCustomBlur}
value={String(this.state.customValue)} disabled={this.props.disabled} /> onKeyDown={this.onCustomKeyDown}
onChange={this.onCustomChange}
value={String(this.state.customValue)}
disabled={this.props.disabled}
/>
); );
} else { } else {
// Each level must have a definition in this.state.levelRoleMap // Each level must have a definition in this.state.levelRoleMap
@ -154,8 +158,9 @@ export default class PowerSelector extends React.Component {
picker = ( picker = (
<Field element="select" <Field element="select"
label={label} onChange={this.onSelectChange} label={label} onChange={this.onSelectChange}
value={String(this.state.selectValue)} disabled={this.props.disabled}> value={String(this.state.selectValue)} disabled={this.props.disabled}
>
{options} {options}
</Field> </Field>
); );

View file

@ -46,17 +46,18 @@ export default class RoomAliasField extends React.PureComponent {
const domain = (<span title={aliasPostfix}>{aliasPostfix}</span>); const domain = (<span title={aliasPostfix}>{aliasPostfix}</span>);
const maxlength = 255 - this.props.domain.length - 2; // 2 for # and : const maxlength = 255 - this.props.domain.length - 2; // 2 for # and :
return ( return (
<Field <Field
label={_t("Room address")} label={_t("Room address")}
className="mx_RoomAliasField" className="mx_RoomAliasField"
prefixComponent={poundSign} prefixComponent={poundSign}
postfixComponent={domain} postfixComponent={domain}
ref={ref => this._fieldRef = ref} ref={ref => this._fieldRef = ref}
onValidate={this._onValidate} onValidate={this._onValidate}
placeholder={_t("e.g. my-room")} placeholder={_t("e.g. my-room")}
onChange={this._onChange} onChange={this._onChange}
value={this.props.value.substring(1, this.props.value.length - this.props.domain.length - 1)} value={this.props.value.substring(1, this.props.value.length - this.props.domain.length - 1)}
maxLength={maxlength} /> maxLength={maxlength}
/>
); );
} }

View file

@ -59,13 +59,13 @@ class TintableSvg extends React.Component {
render() { render() {
return ( return (
<object className={"mx_TintableSvg " + (this.props.className ? this.props.className : "")} <object className={"mx_TintableSvg " + (this.props.className ? this.props.className : "")}
type="image/svg+xml" type="image/svg+xml"
data={this.props.src} data={this.props.src}
width={this.props.width} width={this.props.width}
height={this.props.height} height={this.props.height}
onLoad={this.onLoad} onLoad={this.onLoad}
tabIndex="-1" tabIndex="-1"
/> />
); );
} }
} }

View file

@ -178,9 +178,15 @@ export default class GroupMemberList extends React.Component {
} }
const inputBox = ( const inputBox = (
<input className="mx_GroupMemberList_query mx_textinput" id="mx_GroupMemberList_query" type="text" <input
onChange={this.onSearchQueryChanged} value={this.state.searchQuery} className="mx_GroupMemberList_query mx_textinput"
placeholder={_t('Filter community members')} autoComplete="off" /> id="mx_GroupMemberList_query"
type="text"
onChange={this.onSearchQueryChanged}
value={this.state.searchQuery}
placeholder={_t('Filter community members')}
autoComplete="off"
/>
); );
const joined = this.state.members ? <div className="mx_MemberList_joined"> const joined = this.state.members ? <div className="mx_MemberList_joined">

View file

@ -67,11 +67,11 @@ export default class GroupPublicityToggle extends React.Component {
const GroupTile = sdk.getComponent('groups.GroupTile'); const GroupTile = sdk.getComponent('groups.GroupTile');
return <div className="mx_GroupPublicity_toggle"> return <div className="mx_GroupPublicity_toggle">
<GroupTile groupId={this.props.groupId} showDescription={false} <GroupTile groupId={this.props.groupId} showDescription={false}
avatarHeight={40} draggable={false} avatarHeight={40} draggable={false}
/> />
<ToggleSwitch checked={this.state.isGroupPublicised} <ToggleSwitch checked={this.state.isGroupPublicised}
disabled={!this.state.ready || this.state.busy} disabled={!this.state.ready || this.state.busy}
onChange={this._onPublicityToggle} /> onChange={this._onPublicityToggle} />
</div>; </div>;
} }
} }

View file

@ -141,9 +141,14 @@ export default class GroupRoomList extends React.Component {
); );
} }
const inputBox = ( const inputBox = (
<input className="mx_GroupRoomList_query mx_textinput" id="mx_GroupRoomList_query" type="text" <input
onChange={this.onSearchQueryChanged} value={this.state.searchQuery} className="mx_GroupRoomList_query mx_textinput" id="mx_GroupRoomList_query"
placeholder={_t('Filter community rooms')} autoComplete="off" /> type="text"
onChange={this.onSearchQueryChanged}
value={this.state.searchQuery}
placeholder={_t('Filter community rooms')}
autoComplete="off"
/>
); );
const TruncatedList = sdk.getComponent("elements.TruncatedList"); const TruncatedList = sdk.getComponent("elements.TruncatedList");
@ -152,7 +157,7 @@ export default class GroupRoomList extends React.Component {
{ inviteButton } { inviteButton }
<AutoHideScrollbar className="mx_GroupRoomList_joined mx_GroupRoomList_outerWrapper"> <AutoHideScrollbar className="mx_GroupRoomList_joined mx_GroupRoomList_outerWrapper">
<TruncatedList className="mx_GroupRoomList_wrapper" truncateAt={this.state.truncateAt} <TruncatedList className="mx_GroupRoomList_wrapper" truncateAt={this.state.truncateAt}
createOverflowElement={this._createOverflowTile}> createOverflowElement={this._createOverflowTile}>
{ this.makeGroupRoomTiles(this.state.searchQuery) } { this.makeGroupRoomTiles(this.state.searchQuery) }
</TruncatedList> </TruncatedList>
</AutoHideScrollbar> </AutoHideScrollbar>

View file

@ -125,9 +125,9 @@ export default class MImageBody extends React.Component {
_isGif() { _isGif() {
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
return ( return (
content && content &&
content.info && content.info &&
content.info.mimetype === "image/gif" content.info.mimetype === "image/gif"
); );
} }
@ -346,9 +346,9 @@ export default class MImageBody extends React.Component {
} else { } else {
imageElement = ( imageElement = (
<img style={{display: 'none'}} src={thumbUrl} ref={this._image} <img style={{display: 'none'}} src={thumbUrl} ref={this._image}
alt={content.body} alt={content.body}
onError={this.onImageError} onError={this.onImageError}
onLoad={this.onImageLoad} onLoad={this.onImageLoad}
/> />
); );
} }
@ -384,12 +384,12 @@ export default class MImageBody extends React.Component {
// mx_MImageBody_thumbnail resizes img to exactly container size // mx_MImageBody_thumbnail resizes img to exactly container size
img = ( img = (
<img className="mx_MImageBody_thumbnail" src={thumbUrl} ref={this._image} <img className="mx_MImageBody_thumbnail" src={thumbUrl} ref={this._image}
style={{ maxWidth: maxWidth + "px" }} style={{ maxWidth: maxWidth + "px" }}
alt={content.body} alt={content.body}
onError={this.onImageError} onError={this.onImageError}
onLoad={this.onImageLoad} onLoad={this.onImageLoad}
onMouseEnter={this.onImageEnter} onMouseEnter={this.onImageEnter}
onMouseLeave={this.onImageLeave} /> onMouseLeave={this.onImageLeave} />
); );
} }
@ -467,9 +467,9 @@ export default class MImageBody extends React.Component {
const contentUrl = this._getContentUrl(); const contentUrl = this._getContentUrl();
let thumbUrl; let thumbUrl;
if (this._isGif() && SettingsStore.getValue("autoplayGifsAndVideos")) { if (this._isGif() && SettingsStore.getValue("autoplayGifsAndVideos")) {
thumbUrl = contentUrl; thumbUrl = contentUrl;
} else { } else {
thumbUrl = this._getThumbUrl(); thumbUrl = this._getThumbUrl();
} }
const thumbnail = this._messageContent(contentUrl, thumbUrl, content); const thumbnail = this._messageContent(contentUrl, thumbUrl, content);

View file

@ -82,9 +82,7 @@ export default class MKeyVerificationConclusion extends React.Component {
} }
// User isn't actually verified // User isn't actually verified
if (!MatrixClientPeg.get() if (!MatrixClientPeg.get().checkUserTrust(request.otherUserId).isCrossSigningVerified()) {
.checkUserTrust(request.otherUserId)
.isCrossSigningVerified()) {
return false; return false;
} }

View file

@ -129,12 +129,13 @@ export default class ReactionsRowButton extends React.PureComponent {
}, },
); );
} }
const isPeeking = room.getMyMembership() !== "join";
const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
return <AccessibleButton return <AccessibleButton
className={classes} className={classes}
aria-label={label} aria-label={label}
onClick={this.onClick} onClick={this.onClick}
disabled={isPeeking}
onMouseOver={this.onMouseOver} onMouseOver={this.onMouseOver}
onMouseLeave={this.onMouseLeave} onMouseLeave={this.onMouseLeave}
> >

View file

@ -521,11 +521,12 @@ export default class TextualBody extends React.Component {
const LinkPreviewWidget = sdk.getComponent('rooms.LinkPreviewWidget'); const LinkPreviewWidget = sdk.getComponent('rooms.LinkPreviewWidget');
widgets = this.state.links.map((link)=>{ widgets = this.state.links.map((link)=>{
return <LinkPreviewWidget return <LinkPreviewWidget
key={link} key={link}
link={link} link={link}
mxEvent={this.props.mxEvent} mxEvent={this.props.mxEvent}
onCancelClick={this.onCancelClick} onCancelClick={this.onCancelClick}
onHeightChanged={this.props.onHeightChanged} />; onHeightChanged={this.props.onHeightChanged}
/>;
}); });
} }

View file

@ -310,9 +310,14 @@ export default class AliasSettings extends React.Component {
let found = false; let found = false;
const canonicalValue = this.state.canonicalAlias || ""; const canonicalValue = this.state.canonicalAlias || "";
const canonicalAliasSection = ( const canonicalAliasSection = (
<Field onChange={this.onCanonicalAliasChange} value={canonicalValue} <Field
disabled={this.state.updatingCanonicalAlias || !this.props.canSetCanonicalAlias} onChange={this.onCanonicalAliasChange}
element='select' id='canonicalAlias' label={_t('Main address')}> value={canonicalValue}
disabled={this.state.updatingCanonicalAlias || !this.props.canSetCanonicalAlias}
element='select'
id='canonicalAlias'
label={_t('Main address')}
>
<option value="" key="unset">{ _t('not specified') }</option> <option value="" key="unset">{ _t('not specified') }</option>
{ {
this._getAliases().map((alias, i) => { this._getAliases().map((alias, i) => {
@ -326,9 +331,9 @@ export default class AliasSettings extends React.Component {
} }
{ {
found || !this.state.canonicalAlias ? '' : found || !this.state.canonicalAlias ? '' :
<option value={ this.state.canonicalAlias } key='arbitrary'> <option value={ this.state.canonicalAlias } key='arbitrary'>
{ this.state.canonicalAlias } { this.state.canonicalAlias }
</option> </option>
} }
</Field> </Field>
); );

View file

@ -205,16 +205,34 @@ export default class RoomProfileSettings extends React.Component {
noValidate={true} noValidate={true}
className="mx_ProfileSettings_profileForm" className="mx_ProfileSettings_profileForm"
> >
<input type="file" ref={this._avatarUpload} className="mx_ProfileSettings_avatarUpload" <input
onChange={this._onAvatarChanged} accept="image/*" /> type="file"
ref={this._avatarUpload}
className="mx_ProfileSettings_avatarUpload"
onChange={this._onAvatarChanged}
accept="image/*"
/>
<div className="mx_ProfileSettings_profile"> <div className="mx_ProfileSettings_profile">
<div className="mx_ProfileSettings_controls"> <div className="mx_ProfileSettings_controls">
<Field label={_t("Room Name")} <Field
type="text" value={this.state.displayName} autoComplete="off" label={_t("Room Name")}
onChange={this._onDisplayNameChanged} disabled={!this.state.canSetName} /> type="text"
<Field className="mx_ProfileSettings_controls_topic" id="profileTopic" label={_t("Room Topic")} disabled={!this.state.canSetTopic} value={this.state.displayName}
type="text" value={this.state.topic} autoComplete="off" autoComplete="off"
onChange={this._onTopicChanged} element="textarea" /> onChange={this._onDisplayNameChanged}
disabled={!this.state.canSetName}
/>
<Field
className="mx_ProfileSettings_controls_topic"
id="profileTopic"
label={_t("Room Topic")}
disabled={!this.state.canSetTopic}
type="text"
value={this.state.topic}
autoComplete="off"
onChange={this._onTopicChanged}
element="textarea"
/>
</div> </div>
<AvatarSetting <AvatarSetting
avatarUrl={this.state.avatarUrl} avatarUrl={this.state.avatarUrl}

View file

@ -68,10 +68,12 @@ export default class UrlPreviewSettings extends React.Component {
if (SettingsStore.canSetValue("urlPreviewsEnabled", roomId, "room")) { if (SettingsStore.canSetValue("urlPreviewsEnabled", roomId, "room")) {
previewsForRoom = ( previewsForRoom = (
<label> <label>
<SettingsFlag name="urlPreviewsEnabled" <SettingsFlag
level={SettingLevel.ROOM} name="urlPreviewsEnabled"
roomId={roomId} level={SettingLevel.ROOM}
isExplicit={true} /> roomId={roomId}
isExplicit={true}
/>
</label> </label>
); );
} else { } else {
@ -91,8 +93,8 @@ export default class UrlPreviewSettings extends React.Component {
const previewsForRoomAccount = ( // in an e2ee room we use a special key to enforce per-room opt-in const previewsForRoomAccount = ( // in an e2ee room we use a special key to enforce per-room opt-in
<SettingsFlag name={isEncrypted ? 'urlPreviewsEnabled_e2ee' : 'urlPreviewsEnabled'} <SettingsFlag name={isEncrypted ? 'urlPreviewsEnabled_e2ee' : 'urlPreviewsEnabled'}
level={SettingLevel.ROOM_ACCOUNT} level={SettingLevel.ROOM_ACCOUNT}
roomId={roomId} /> roomId={roomId} />
); );
return ( return (

View file

@ -176,8 +176,11 @@ class EntityTile extends React.Component {
// The wrapping div is required to make the magic mouse listener work, for some reason. // The wrapping div is required to make the magic mouse listener work, for some reason.
return ( return (
<div ref={(c) => this.container = c} > <div ref={(c) => this.container = c} >
<AccessibleButton className={classNames(mainClassNames)} title={this.props.title} <AccessibleButton
onClick={this.props.onClick}> className={classNames(mainClassNames)}
title={this.props.title}
onClick={this.props.onClick}
>
<div className="mx_EntityTile_avatar"> <div className="mx_EntityTile_avatar">
{ av } { av }
{ e2eIcon } { e2eIcon }

View file

@ -128,8 +128,8 @@ export default class LinkPreviewWidget extends React.Component {
let img; let img;
if (image) { if (image) {
img = <div className="mx_LinkPreviewWidget_image" style={{ height: thumbHeight }}> img = <div className="mx_LinkPreviewWidget_image" style={{ height: thumbHeight }}>
<img style={{ maxWidth: imageMaxWidth, maxHeight: imageMaxHeight }} src={image} onClick={this.onImageClick} /> <img style={{ maxWidth: imageMaxWidth, maxHeight: imageMaxHeight }} src={image} onClick={this.onImageClick} />
</div>; </div>;
} }
// The description includes &-encoded HTML entities, we decode those as React treats the thing as an // The description includes &-encoded HTML entities, we decode those as React treats the thing as an

View file

@ -53,9 +53,9 @@ export default class PinnedEventTile extends React.Component {
if (index !== -1) { if (index !== -1) {
pinned.splice(index, 1); pinned.splice(index, 1);
MatrixClientPeg.get().sendStateEvent(this.props.mxRoom.roomId, 'm.room.pinned_events', {pinned}, '') MatrixClientPeg.get().sendStateEvent(this.props.mxRoom.roomId, 'm.room.pinned_events', {pinned}, '')
.then(() => { .then(() => {
if (this.props.onUnpinned) this.props.onUnpinned(); if (this.props.onUnpinned) this.props.onUnpinned();
}); });
} else if (this.props.onUnpinned) this.props.onUnpinned(); } else if (this.props.onUnpinned) this.props.onUnpinned();
} }
}; };
@ -98,8 +98,11 @@ export default class PinnedEventTile extends React.Component {
{ formatFullDate(new Date(this.props.mxEvent.getTs())) } { formatFullDate(new Date(this.props.mxEvent.getTs())) }
</span> </span>
<div className="mx_PinnedEventTile_message"> <div className="mx_PinnedEventTile_message">
<MessageEvent mxEvent={this.props.mxEvent} className="mx_PinnedEventTile_body" maxImageHeight={150} <MessageEvent
onHeightChanged={() => {}} // we need to give this, apparently mxEvent={this.props.mxEvent}
className="mx_PinnedEventTile_body"
maxImageHeight={150}
onHeightChanged={() => {}} // we need to give this, apparently
/> />
</div> </div>
</div> </div>

View file

@ -64,10 +64,10 @@ export default class PinnedEventsPanel extends React.Component {
pinnedEvents.getContent().pinned.map((eventId) => { pinnedEvents.getContent().pinned.map((eventId) => {
promises.push(cli.getEventTimeline(this.props.room.getUnfilteredTimelineSet(), eventId, 0).then( promises.push(cli.getEventTimeline(this.props.room.getUnfilteredTimelineSet(), eventId, 0).then(
(timeline) => { (timeline) => {
const event = timeline.getEvents().find((e) => e.getId() === eventId); const event = timeline.getEvents().find((e) => e.getId() === eventId);
return {eventId, timeline, event}; return {eventId, timeline, event};
}).catch((err) => { }).catch((err) => {
console.error("Error looking up pinned event " + eventId + " in room " + this.props.room.roomId); console.error("Error looking up pinned event " + eventId + " in room " + this.props.room.roomId);
console.error(err); console.error(err);
return null; // return lack of context to avoid unhandled errors return null; // return lack of context to avoid unhandled errors
@ -113,10 +113,14 @@ export default class PinnedEventsPanel extends React.Component {
} }
return this.state.pinned.map((context) => { return this.state.pinned.map((context) => {
return (<PinnedEventTile key={context.event.getId()} return (
mxRoom={this.props.room} <PinnedEventTile
mxEvent={context.event} key={context.event.getId()}
onUnpinned={this._updatePinnedMessages} />); mxRoom={this.props.room}
mxEvent={context.event}
onUnpinned={this._updatePinnedMessages}
/>
);
}); });
} }

View file

@ -187,8 +187,7 @@ export default class ReadReceiptMarker extends React.PureComponent {
} }
return ( return (
<NodeAnimator <NodeAnimator startStyles={this.state.startStyles}>
startStyles={this.state.startStyles} >
<MemberAvatar <MemberAvatar
member={this.props.member} member={this.props.member}
fallbackUserId={this.props.fallbackUserId} fallbackUserId={this.props.fallbackUserId}

View file

@ -79,8 +79,13 @@ export default class ReplyPreview extends React.Component {
{ _t('Replying') } { _t('Replying') }
</div> </div>
<div className="mx_ReplyPreview_header mx_ReplyPreview_cancel"> <div className="mx_ReplyPreview_header mx_ReplyPreview_cancel">
<img className="mx_filterFlipColor" src={require("../../../../res/img/cancel.svg")} width="18" height="18" <img
onClick={cancelQuoting} /> className="mx_filterFlipColor"
src={require("../../../../res/img/cancel.svg")}
width="18"
height="18"
onClick={cancelQuoting}
/>
</div> </div>
<div className="mx_ReplyPreview_clear" /> <div className="mx_ReplyPreview_clear" />
<EventTile <EventTile

View file

@ -88,11 +88,11 @@ export default class RoomDetailRow extends React.Component {
const name = room.name || getDisplayAliasForRoom(room) || _t('Unnamed room'); const name = room.name || getDisplayAliasForRoom(room) || _t('Unnamed room');
const guestRead = room.worldReadable ? ( const guestRead = room.worldReadable ? (
<div className="mx_RoomDirectory_perm">{ _t('World readable') }</div> <div className="mx_RoomDirectory_perm">{ _t('World readable') }</div>
) : <div />; ) : <div />;
const guestJoin = room.guestCanJoin ? ( const guestJoin = room.guestCanJoin ? (
<div className="mx_RoomDirectory_perm">{ _t('Guests can join') }</div> <div className="mx_RoomDirectory_perm">{ _t('Guests can join') }</div>
) : <div />; ) : <div />;
const perms = (guestRead || guestJoin) ? (<div className="mx_RoomDirectory_perms"> const perms = (guestRead || guestJoin) ? (<div className="mx_RoomDirectory_perms">
{ guestRead }&nbsp; { guestRead }&nbsp;

View file

@ -19,6 +19,7 @@ import React, {useState} from "react";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore"; import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
import {useEventEmitter} from "../../../hooks/useEventEmitter"; import {useEventEmitter} from "../../../hooks/useEventEmitter";
import SpaceStore from "../../../stores/SpaceStore";
const RoomListNumResults: React.FC = () => { const RoomListNumResults: React.FC = () => {
const [count, setCount] = useState<number>(null); const [count, setCount] = useState<number>(null);
@ -34,7 +35,10 @@ const RoomListNumResults: React.FC = () => {
if (typeof count !== "number") return null; if (typeof count !== "number") return null;
return <div className="mx_LeftPanel_roomListFilterCount"> return <div className="mx_LeftPanel_roomListFilterCount">
{_t("%(count)s results", { count })} { SpaceStore.instance.spacePanelSpaces.length
? _t("%(count)s results in all spaces", { count })
: _t("%(count)s results", { count })
}
</div>; </div>;
}; };

View file

@ -266,25 +266,25 @@ export default class Stickerpicker extends React.Component {
width: this.popoverWidth, width: this.popoverWidth,
}} }}
> >
<PersistedElement persistKey={PERSISTED_ELEMENT_KEY} zIndex={STICKERPICKER_Z_INDEX}> <PersistedElement persistKey={PERSISTED_ELEMENT_KEY} zIndex={STICKERPICKER_Z_INDEX}>
<AppTile <AppTile
app={stickerApp} app={stickerApp}
room={this.props.room} room={this.props.room}
fullWidth={true} fullWidth={true}
userId={MatrixClientPeg.get().credentials.userId} userId={MatrixClientPeg.get().credentials.userId}
creatorUserId={stickerpickerWidget.sender || MatrixClientPeg.get().credentials.userId} creatorUserId={stickerpickerWidget.sender || MatrixClientPeg.get().credentials.userId}
waitForIframeLoad={true} waitForIframeLoad={true}
showMenubar={true} showMenubar={true}
onEditClick={this._launchManageIntegrations} onEditClick={this._launchManageIntegrations}
onDeleteClick={this._removeStickerpickerWidgets} onDeleteClick={this._removeStickerpickerWidgets}
showTitle={false} showTitle={false}
showCancel={false} showCancel={false}
showPopout={false} showPopout={false}
onMinimiseClick={this._onHideStickersClick} onMinimiseClick={this._onHideStickersClick}
handleMinimisePointerEvents={true} handleMinimisePointerEvents={true}
userWidget={true} userWidget={true}
/> />
</PersistedElement> </PersistedElement>
</div> </div>
</div> </div>
); );

View file

@ -154,7 +154,7 @@ export default class ChangeAvatar extends React.Component {
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
// XXX: FIXME: once we track in the JS what our own displayname is(!) then use it here rather than ? // XXX: FIXME: once we track in the JS what our own displayname is(!) then use it here rather than ?
avatarImg = <BaseAvatar width={this.props.width} height={this.props.height} resizeMethod='crop' avatarImg = <BaseAvatar width={this.props.width} height={this.props.height} resizeMethod='crop'
name='?' idName={MatrixClientPeg.get().getUserIdLocalpart()} url={this.state.avatarUrl} />; name='?' idName={MatrixClientPeg.get().getUserIdLocalpart()} url={this.state.avatarUrl} />;
} }
let uploadSection; let uploadSection;

View file

@ -206,7 +206,7 @@ export default class ChangePassword extends React.Component {
test: ({ value, allowEmpty }) => allowEmpty || !!value, test: ({ value, allowEmpty }) => allowEmpty || !!value,
invalid: () => _t("Passwords can't be empty"), invalid: () => _t("Passwords can't be empty"),
}, },
], ],
}); });
onChangeNewPassword = (ev) => { onChangeNewPassword = (ev) => {
@ -245,7 +245,7 @@ export default class ChangePassword extends React.Component {
}, },
invalid: () => _t("Passwords don't match"), invalid: () => _t("Passwords don't match"),
}, },
], ],
}); });
onClickChange = async (ev) => { onClickChange = async (ev) => {

View file

@ -259,7 +259,7 @@ export default class CrossSigningPanel extends React.PureComponent {
<td>{_t("Homeserver feature support:")}</td> <td>{_t("Homeserver feature support:")}</td>
<td>{homeserverSupportsCrossSigning ? _t("exists") : _t("not found")}</td> <td>{homeserverSupportsCrossSigning ? _t("exists") : _t("not found")}</td>
</tr> </tr>
</tbody></table> </tbody></table>
</details> </details>
{errorSection} {errorSection}
{actionRow} {actionRow}

View file

@ -214,7 +214,7 @@ export default class DevicesPanel extends React.Component {
const deleteButton = this.state.deleting ? const deleteButton = this.state.deleting ?
<Spinner w={22} h={22} /> : <Spinner w={22} h={22} /> :
<AccessibleButton onClick={this._onDeleteClick} kind="danger_sm"> <AccessibleButton onClick={this._onDeleteClick} kind="danger_sm">
{ _t("Delete %(count)s sessions", {count: this.state.selectedDevices.length}) } { _t("Delete %(count)s sessions", {count: this.state.selectedDevices.length})}
</AccessibleButton>; </AccessibleButton>;
const classes = classNames(this.props.className, "mx_DevicesPanel"); const classes = classNames(this.props.className, "mx_DevicesPanel");

View file

@ -100,7 +100,7 @@ export default class Notifications extends React.Component {
MatrixClientPeg.get().setPushRuleEnabled( MatrixClientPeg.get().setPushRuleEnabled(
'global', self.state.masterPushRule.kind, self.state.masterPushRule.rule_id, !checked, 'global', self.state.masterPushRule.kind, self.state.masterPushRule.rule_id, !checked,
).then(function() { ).then(function() {
self._refreshFromServer(); self._refreshFromServer();
}); });
}; };
@ -580,12 +580,12 @@ export default class Notifications extends React.Component {
"vectorRuleId": "_keywords", "vectorRuleId": "_keywords",
"description": ( "description": (
<span> <span>
{ _t('Messages containing <span>keywords</span>', { _t('Messages containing <span>keywords</span>',
{}, {},
{ 'span': (sub) => { 'span': (sub) =>
<span className="mx_UserNotifSettings_keywords" onClick={ self.onKeywordsClicked }>{sub}</span>, <span className="mx_UserNotifSettings_keywords" onClick={ self.onKeywordsClicked }>{sub}</span>,
}, },
)} )}
</span> </span>
), ),
"vectorState": self.state.vectorContentRules.vectorState, "vectorState": self.state.vectorContentRules.vectorState,
@ -743,8 +743,8 @@ export default class Notifications extends React.Component {
emailNotificationsRow(address, label) { emailNotificationsRow(address, label) {
return <LabelledToggleSwitch value={this.hasEmailPusher(this.state.pushers, address)} return <LabelledToggleSwitch value={this.hasEmailPusher(this.state.pushers, address)}
onChange={this.onEnableEmailNotificationsChange.bind(this, address)} onChange={this.onEnableEmailNotificationsChange.bind(this, address)}
label={label} key={`emailNotif_${label}`} />; label={label} key={`emailNotif_${label}`} />;
} }
render() { render() {
@ -757,8 +757,8 @@ export default class Notifications extends React.Component {
let masterPushRuleDiv; let masterPushRuleDiv;
if (this.state.masterPushRule) { if (this.state.masterPushRule) {
masterPushRuleDiv = <LabelledToggleSwitch value={!this.state.masterPushRule.enabled} masterPushRuleDiv = <LabelledToggleSwitch value={!this.state.masterPushRule.enabled}
onChange={this.onEnableNotificationsChange} onChange={this.onEnableNotificationsChange}
label={_t('Enable notifications for this account')} />; label={_t('Enable notifications for this account')} />;
} }
let clearNotificationsButton; let clearNotificationsButton;
@ -874,16 +874,16 @@ export default class Notifications extends React.Component {
{ spinner } { spinner }
<LabelledToggleSwitch value={SettingsStore.getValue("notificationsEnabled")} <LabelledToggleSwitch value={SettingsStore.getValue("notificationsEnabled")}
onChange={this.onEnableDesktopNotificationsChange} onChange={this.onEnableDesktopNotificationsChange}
label={_t('Enable desktop notifications for this session')} /> label={_t('Enable desktop notifications for this session')} />
<LabelledToggleSwitch value={SettingsStore.getValue("notificationBodyEnabled")} <LabelledToggleSwitch value={SettingsStore.getValue("notificationBodyEnabled")}
onChange={this.onEnableDesktopNotificationBodyChange} onChange={this.onEnableDesktopNotificationBodyChange}
label={_t('Show message in desktop notification')} /> label={_t('Show message in desktop notification')} />
<LabelledToggleSwitch value={SettingsStore.getValue("audioNotificationsEnabled")} <LabelledToggleSwitch value={SettingsStore.getValue("audioNotificationsEnabled")}
onChange={this.onEnableAudioNotificationsChange} onChange={this.onEnableAudioNotificationsChange}
label={_t('Enable audible notifications for this session')} /> label={_t('Enable audible notifications for this session')} />
{ emailNotificationsRows } { emailNotificationsRows }

View file

@ -170,8 +170,12 @@ export default class ProfileSettings extends React.Component {
noValidate={true} noValidate={true}
className="mx_ProfileSettings_profileForm" className="mx_ProfileSettings_profileForm"
> >
<input type="file" ref={this._avatarUpload} className="mx_ProfileSettings_avatarUpload" <input
onChange={this._onAvatarChanged} accept="image/*" /> type="file"
ref={this._avatarUpload} className="mx_ProfileSettings_avatarUpload"
onChange={this._onAvatarChanged}
accept="image/*"
/>
<div className="mx_ProfileSettings_profile"> <div className="mx_ProfileSettings_profile">
<div className="mx_ProfileSettings_controls"> <div className="mx_ProfileSettings_controls">
<span className="mx_SettingsTab_subheading">{_t("Profile")}</span> <span className="mx_SettingsTab_subheading">{_t("Profile")}</span>

View file

@ -90,12 +90,18 @@ export class ExistingEmailAddress extends React.Component {
<span className="mx_ExistingEmailAddress_promptText"> <span className="mx_ExistingEmailAddress_promptText">
{_t("Remove %(email)s?", {email: this.props.email.address} )} {_t("Remove %(email)s?", {email: this.props.email.address} )}
</span> </span>
<AccessibleButton onClick={this._onActuallyRemove} kind="danger_sm" <AccessibleButton
className="mx_ExistingEmailAddress_confirmBtn"> onClick={this._onActuallyRemove}
kind="danger_sm"
className="mx_ExistingEmailAddress_confirmBtn"
>
{_t("Remove")} {_t("Remove")}
</AccessibleButton> </AccessibleButton>
<AccessibleButton onClick={this._onDontRemove} kind="link_sm" <AccessibleButton
className="mx_ExistingEmailAddress_confirmBtn"> onClick={this._onDontRemove}
kind="link_sm"
className="mx_ExistingEmailAddress_confirmBtn"
>
{_t("Cancel")} {_t("Cancel")}
</AccessibleButton> </AccessibleButton>
</div> </div>
@ -228,21 +234,28 @@ export default class EmailAddresses extends React.Component {
); );
if (this.state.verifying) { if (this.state.verifying) {
addButton = ( addButton = (
<div> <div>
<div>{_t("We've sent you an email to verify your address. Please follow the instructions there and then click the button below.")}</div> <div>{_t("We've sent you an email to verify your address. Please follow the instructions there and then click the button below.")}</div>
<AccessibleButton onClick={this._onContinueClick} kind="primary" <AccessibleButton
disabled={this.state.continueDisabled}> onClick={this._onContinueClick}
{_t("Continue")} kind="primary"
</AccessibleButton> disabled={this.state.continueDisabled}
</div> >
{_t("Continue")}
</AccessibleButton>
</div>
); );
} }
return ( return (
<div className="mx_EmailAddresses"> <div className="mx_EmailAddresses">
{existingEmailElements} {existingEmailElements}
<form onSubmit={this._onAddClick} autoComplete="off" <form
noValidate={true} className="mx_EmailAddresses_new"> onSubmit={this._onAddClick}
autoComplete="off"
noValidate={true}
className="mx_EmailAddresses_new"
>
<Field <Field
type="text" type="text"
label={_t("Email Address")} label={_t("Email Address")}

View file

@ -85,12 +85,18 @@ export class ExistingPhoneNumber extends React.Component {
<span className="mx_ExistingPhoneNumber_promptText"> <span className="mx_ExistingPhoneNumber_promptText">
{_t("Remove %(phone)s?", {phone: this.props.msisdn.address})} {_t("Remove %(phone)s?", {phone: this.props.msisdn.address})}
</span> </span>
<AccessibleButton onClick={this._onActuallyRemove} kind="danger_sm" <AccessibleButton
className="mx_ExistingPhoneNumber_confirmBtn"> onClick={this._onActuallyRemove}
kind="danger_sm"
className="mx_ExistingPhoneNumber_confirmBtn"
>
{_t("Remove")} {_t("Remove")}
</AccessibleButton> </AccessibleButton>
<AccessibleButton onClick={this._onDontRemove} kind="link_sm" <AccessibleButton
className="mx_ExistingPhoneNumber_confirmBtn"> onClick={this._onDontRemove}
kind="link_sm"
className="mx_ExistingPhoneNumber_confirmBtn"
>
{_t("Cancel")} {_t("Cancel")}
</AccessibleButton> </AccessibleButton>
</div> </div>
@ -246,8 +252,11 @@ export default class PhoneNumbers extends React.Component {
value={this.state.newPhoneNumberCode} value={this.state.newPhoneNumberCode}
onChange={this._onChangeNewPhoneNumberCode} onChange={this._onChangeNewPhoneNumberCode}
/> />
<AccessibleButton onClick={this._onContinueClick} kind="primary" <AccessibleButton
disabled={this.state.continueDisabled}> onClick={this._onContinueClick}
kind="primary"
disabled={this.state.continueDisabled}
>
{_t("Continue")} {_t("Continue")}
</AccessibleButton> </AccessibleButton>
</form> </form>

View file

@ -80,9 +80,11 @@ export default class GeneralRoomSettingsTab extends React.Component {
flairSection = <> flairSection = <>
<span className='mx_SettingsTab_subheading'>{_t("Flair")}</span> <span className='mx_SettingsTab_subheading'>{_t("Flair")}</span>
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'> <div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
<RelatedGroupSettings roomId={room.roomId} <RelatedGroupSettings
canSetRelatedGroups={canChangeGroups} roomId={room.roomId}
relatedGroupsEvent={groupsEvent} /> canSetRelatedGroups={canChangeGroups}
relatedGroupsEvent={groupsEvent}
/>
</div> </div>
</>; </>;
} }
@ -97,8 +99,8 @@ export default class GeneralRoomSettingsTab extends React.Component {
<div className="mx_SettingsTab_heading">{_t("Room Addresses")}</div> <div className="mx_SettingsTab_heading">{_t("Room Addresses")}</div>
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'> <div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
<AliasSettings roomId={this.props.roomId} <AliasSettings roomId={this.props.roomId}
canSetCanonicalAlias={canSetCanonical} canSetAliases={canSetAliases} canSetCanonicalAlias={canSetCanonical} canSetAliases={canSetAliases}
canonicalAliasEvent={canonicalAliasEv} aliasEvents={aliasEvents} /> canonicalAliasEvent={canonicalAliasEv} aliasEvents={aliasEvents} />
</div> </div>
<div className="mx_SettingsTab_heading">{_t("Other")}</div> <div className="mx_SettingsTab_heading">{_t("Other")}</div>
{ flairSection } { flairSection }

View file

@ -155,7 +155,7 @@ export default class NotificationsSettingsTab extends React.Component {
<div> <div>
<span>{_t("Notification sound")}: <code>{this.state.currentSound}</code></span><br /> <span>{_t("Notification sound")}: <code>{this.state.currentSound}</code></span><br />
<AccessibleButton className="mx_NotificationSound_resetSound" disabled={this.state.currentSound == "default"} onClick={this._clearSound.bind(this)} kind="primary"> <AccessibleButton className="mx_NotificationSound_resetSound" disabled={this.state.currentSound == "default"} onClick={this._clearSound.bind(this)} kind="primary">
{_t("Reset")} {_t("Reset")}
</AccessibleButton> </AccessibleButton>
</div> </div>
<div> <div>
@ -167,11 +167,11 @@ export default class NotificationsSettingsTab extends React.Component {
{currentUploadedFile} {currentUploadedFile}
<AccessibleButton className="mx_NotificationSound_browse" onClick={this._triggerUploader.bind(this)} kind="primary"> <AccessibleButton className="mx_NotificationSound_browse" onClick={this._triggerUploader.bind(this)} kind="primary">
{_t("Browse")} {_t("Browse")}
</AccessibleButton> </AccessibleButton>
<AccessibleButton className="mx_NotificationSound_save" disabled={this.state.uploadedFile == null} onClick={this._onClickSaveSound.bind(this)} kind="primary"> <AccessibleButton className="mx_NotificationSound_save" disabled={this.state.uploadedFile == null} onClick={this._onClickSaveSound.bind(this)} kind="primary">
{_t("Save")} {_t("Save")}
</AccessibleButton> </AccessibleButton>
<br /> <br />
</div> </div>

View file

@ -323,8 +323,11 @@ export default class GeneralUserSettingsTab extends React.Component {
return ( return (
<div className="mx_SettingsTab_section"> <div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Language and region")}</span> <span className="mx_SettingsTab_subheading">{_t("Language and region")}</span>
<LanguageDropdown className="mx_GeneralUserSettingsTab_languageInput" <LanguageDropdown
onOptionChange={this._onLanguageChange} value={this.state.language} /> className="mx_GeneralUserSettingsTab_languageInput"
onOptionChange={this._onLanguageChange}
value={this.state.language}
/>
</div> </div>
); );
} }
@ -333,8 +336,10 @@ export default class GeneralUserSettingsTab extends React.Component {
return ( return (
<div className="mx_SettingsTab_section"> <div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Spell check dictionaries")}</span> <span className="mx_SettingsTab_subheading">{_t("Spell check dictionaries")}</span>
<SpellCheckSettings languages={this.state.spellCheckLanguages} <SpellCheckSettings
onLanguagesChange={this._onSpellCheckLanguagesChange} /> languages={this.state.spellCheckLanguages}
onLanguagesChange={this._onSpellCheckLanguagesChange}
/>
</div> </div>
); );
} }

View file

@ -257,12 +257,16 @@ export default class SecurityUserSettingsTab extends React.Component {
const userIds = !ignoredUserIds?.length const userIds = !ignoredUserIds?.length
? _t('You have no ignored users.') ? _t('You have no ignored users.')
: ignoredUserIds.map((u) => <IgnoredUser : ignoredUserIds.map((u) => {
userId={u} return (
onUnignored={this._onUserUnignored} <IgnoredUser
key={u} userId={u}
inProgress={waitingUnignored.includes(u)} onUnignored={this._onUserUnignored}
/>); key={u}
inProgress={waitingUnignored.includes(u)}
/>
);
});
return ( return (
<div className='mx_SettingsTab_section'> <div className='mx_SettingsTab_section'>

View file

@ -176,8 +176,8 @@ export default class VoiceUserSettingsTab extends React.Component {
const defaultDevice = getDefaultDevice(audioOutputs); const defaultDevice = getDefaultDevice(audioOutputs);
speakerDropdown = ( speakerDropdown = (
<Field element="select" label={_t("Audio Output")} <Field element="select" label={_t("Audio Output")}
value={this.state.activeAudioOutput || defaultDevice} value={this.state.activeAudioOutput || defaultDevice}
onChange={this._setAudioOutput}> onChange={this._setAudioOutput}>
{this._renderDeviceOptions(audioOutputs, 'audioOutput')} {this._renderDeviceOptions(audioOutputs, 'audioOutput')}
</Field> </Field>
); );
@ -188,8 +188,8 @@ export default class VoiceUserSettingsTab extends React.Component {
const defaultDevice = getDefaultDevice(audioInputs); const defaultDevice = getDefaultDevice(audioInputs);
microphoneDropdown = ( microphoneDropdown = (
<Field element="select" label={_t("Microphone")} <Field element="select" label={_t("Microphone")}
value={this.state.activeAudioInput || defaultDevice} value={this.state.activeAudioInput || defaultDevice}
onChange={this._setAudioInput}> onChange={this._setAudioInput}>
{this._renderDeviceOptions(audioInputs, 'audioInput')} {this._renderDeviceOptions(audioInputs, 'audioInput')}
</Field> </Field>
); );
@ -200,8 +200,8 @@ export default class VoiceUserSettingsTab extends React.Component {
const defaultDevice = getDefaultDevice(videoInputs); const defaultDevice = getDefaultDevice(videoInputs);
webcamDropdown = ( webcamDropdown = (
<Field element="select" label={_t("Camera")} <Field element="select" label={_t("Camera")}
value={this.state.activeVideoInput || defaultDevice} value={this.state.activeVideoInput || defaultDevice}
onChange={this._setVideoInput}> onChange={this._setVideoInput}>
{this._renderDeviceOptions(videoInputs, 'videoInput')} {this._renderDeviceOptions(videoInputs, 'videoInput')}
</Field> </Field>
); );

View file

@ -29,14 +29,14 @@ export default class VerificationCancelled extends React.Component {
render() { render() {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return <div> return <div>
<p>{_t( <p>{_t(
"The other party cancelled the verification.", "The other party cancelled the verification.",
)}</p> )}</p>
<DialogButtons <DialogButtons
primaryButton={_t('OK')} primaryButton={_t('OK')}
hasCancel={false} hasCancel={false}
onPrimaryButtonClick={this.props.onDone} onPrimaryButtonClick={this.props.onDone}
/> />
</div>; </div>;
} }
} }

View file

@ -61,9 +61,9 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} =
// const inlinePattern = "(?:^|\\s)(?<!\\\\)\\$(?!\\s)(([^$]|\\\\\\$)+?)(?<!\\\\|\\s)\\$"; // const inlinePattern = "(?:^|\\s)(?<!\\\\)\\$(?!\\s)(([^$]|\\\\\\$)+?)(?<!\\\\|\\s)\\$";
// conditions for display math detection $$...$$: // conditions for display math detection $$...$$:
// - pattern starts at beginning of line or is not prefixed with backslash or dollar // - pattern starts and ends on a new line
// - left delimiter ($$) is not escaped by backslash // - left delimiter ($$) is not escaped by backslash
"display": "(^|[^\\\\$])\\$\\$(([^$]|\\\\\\$)+?)\\$\\$", "display": "(^)\\$\\$(([^$]|\\\\\\$)+?)\\$\\$$",
// conditions for inline math detection $...$: // conditions for inline math detection $...$:
// - pattern starts at beginning of line, follows whitespace character or punctuation // - pattern starts at beginning of line, follows whitespace character or punctuation
@ -78,9 +78,9 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} =
// detect math with latex delimiters, inline: \(...\), display \[...\] // detect math with latex delimiters, inline: \(...\), display \[...\]
// conditions for display math detection \[...\]: // conditions for display math detection \[...\]:
// - pattern starts at beginning of line or is not prefixed with backslash // - pattern starts and ends on a new line
// - pattern is not empty // - pattern is not empty
"display": "(^|[^\\\\])\\\\\\[(?!\\\\\\])(.*?)\\\\\\]", "display": "(^)\\\\\\[(?!\\\\\\])(.*?)\\\\\\]$",
// conditions for inline math detection \(...\): // conditions for inline math detection \(...\):
// - pattern starts at beginning of line or is not prefixed with backslash // - pattern starts at beginning of line or is not prefixed with backslash

View file

@ -1552,6 +1552,8 @@
"Explore all public rooms": "Explore all public rooms", "Explore all public rooms": "Explore all public rooms",
"Quick actions": "Quick actions", "Quick actions": "Quick actions",
"Use the + to make a new room or explore existing ones below": "Use the + to make a new room or explore existing ones below", "Use the + to make a new room or explore existing ones below": "Use the + to make a new room or explore existing ones below",
"%(count)s results in all spaces|other": "%(count)s results in all spaces",
"%(count)s results in all spaces|one": "%(count)s result in all spaces",
"%(count)s results|other": "%(count)s results", "%(count)s results|other": "%(count)s results",
"%(count)s results|one": "%(count)s result", "%(count)s results|one": "%(count)s result",
"This room": "This room", "This room": "This room",
@ -2612,6 +2614,7 @@
"If you can't find the room you're looking for, ask for an invite or <a>Create a new room</a>.": "If you can't find the room you're looking for, ask for an invite or <a>Create a new room</a>.", "If you can't find the room you're looking for, ask for an invite or <a>Create a new room</a>.": "If you can't find the room you're looking for, ask for an invite or <a>Create a new room</a>.",
"Explore rooms in %(communityName)s": "Explore rooms in %(communityName)s", "Explore rooms in %(communityName)s": "Explore rooms in %(communityName)s",
"Filter": "Filter", "Filter": "Filter",
"Filter all spaces": "Filter all spaces",
"Clear filter": "Clear filter", "Clear filter": "Clear filter",
"Filter rooms and people": "Filter rooms and people", "Filter rooms and people": "Filter rooms and people",
"You can't send any messages until you review and agree to <consentLink>our terms and conditions</consentLink>.": "You can't send any messages until you review and agree to <consentLink>our terms and conditions</consentLink>.", "You can't send any messages until you review and agree to <consentLink>our terms and conditions</consentLink>.": "You can't send any messages until you review and agree to <consentLink>our terms and conditions</consentLink>.",

View file

@ -127,8 +127,13 @@ export default class EventIndex extends EventEmitter {
this.crawlerCheckpoints.push(forwardCheckpoint); this.crawlerCheckpoints.push(forwardCheckpoint);
} }
} catch (e) { } catch (e) {
console.log("EventIndex: Error adding initial checkpoints for room", console.log(
room.roomId, backCheckpoint, forwardCheckpoint, e); "EventIndex: Error adding initial checkpoints for room",
room.roomId,
backCheckpoint,
forwardCheckpoint,
e,
);
} }
})); }));
} }
@ -379,8 +384,12 @@ export default class EventIndex extends EventEmitter {
try { try {
await indexManager.addCrawlerCheckpoint(checkpoint); await indexManager.addCrawlerCheckpoint(checkpoint);
} catch (e) { } catch (e) {
console.log("EventIndex: Error adding new checkpoint for room", console.log(
room.roomId, checkpoint, e); "EventIndex: Error adding new checkpoint for room",
room.roomId,
checkpoint,
e,
);
} }
this.crawlerCheckpoints.push(checkpoint); this.crawlerCheckpoints.push(checkpoint);
@ -459,7 +468,7 @@ export default class EventIndex extends EventEmitter {
} catch (e) { } catch (e) {
if (e.httpStatus === 403) { if (e.httpStatus === 403) {
console.log("EventIndex: Removing checkpoint as we don't have ", console.log("EventIndex: Removing checkpoint as we don't have ",
"permissions to fetch messages from this room.", checkpoint); "permissions to fetch messages from this room.", checkpoint);
try { try {
await indexManager.removeCrawlerCheckpoint(checkpoint); await indexManager.removeCrawlerCheckpoint(checkpoint);
} catch (e) { } catch (e) {
@ -589,7 +598,7 @@ export default class EventIndex extends EventEmitter {
// to do here anymore. // to do here anymore.
if (!newCheckpoint) { if (!newCheckpoint) {
console.log("EventIndex: The server didn't return a valid ", console.log("EventIndex: The server didn't return a valid ",
"new checkpoint, not continuing the crawl.", checkpoint); "new checkpoint, not continuing the crawl.", checkpoint);
continue; continue;
} }
@ -599,12 +608,12 @@ export default class EventIndex extends EventEmitter {
// the new checkpoint to be used by the crawler. // the new checkpoint to be used by the crawler.
if (eventsAlreadyAdded === true && newCheckpoint.fullCrawl !== true) { if (eventsAlreadyAdded === true && newCheckpoint.fullCrawl !== true) {
console.log("EventIndex: Checkpoint had already all events", console.log("EventIndex: Checkpoint had already all events",
"added, stopping the crawl", checkpoint); "added, stopping the crawl", checkpoint);
await indexManager.removeCrawlerCheckpoint(newCheckpoint); await indexManager.removeCrawlerCheckpoint(newCheckpoint);
} else { } else {
if (eventsAlreadyAdded === true) { if (eventsAlreadyAdded === true) {
console.log("EventIndex: Checkpoint had already all events", console.log("EventIndex: Checkpoint had already all events",
"added, but continuing due to a full crawl", checkpoint); "added, but continuing due to a full crawl", checkpoint);
} }
this.crawlerCheckpoints.push(newCheckpoint); this.crawlerCheckpoints.push(newCheckpoint);
} }
@ -776,8 +785,14 @@ export default class EventIndex extends EventEmitter {
* @returns {Promise<boolean>} Resolves to true if events were added to the * @returns {Promise<boolean>} Resolves to true if events were added to the
* timeline, false otherwise. * timeline, false otherwise.
*/ */
async populateFileTimeline(timelineSet, timeline, room, limit = 10, async populateFileTimeline(
fromEvent = null, direction = EventTimeline.BACKWARDS) { timelineSet,
timeline,
room,
limit = 10,
fromEvent = null,
direction = EventTimeline.BACKWARDS,
) {
const matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction); const matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction);
// If this is a normal fill request, not a pagination request, we need // If this is a normal fill request, not a pagination request, we need
@ -807,7 +822,7 @@ export default class EventIndex extends EventEmitter {
} }
console.log("EventIndex: Populating file panel with", matrixEvents.length, console.log("EventIndex: Populating file panel with", matrixEvents.length,
"events and setting the pagination token to", paginationToken); "events and setting the pagination token to", paginationToken);
timeline.setPaginationToken(paginationToken, EventTimeline.BACKWARDS); timeline.setPaginationToken(paginationToken, EventTimeline.BACKWARDS);
return ret; return ret;

View file

@ -124,15 +124,15 @@ class CustomRoomTagStore extends EventEmitter {
const tags = Object.assign({}, oldTags, tag); const tags = Object.assign({}, oldTags, tag);
this._setState({tags}); this._setState({tags});
} }
break;
} }
break;
case 'on_client_not_viable': case 'on_client_not_viable':
case 'on_logged_out': { case 'on_logged_out': {
// we assume to always have a tags object in the state // we assume to always have a tags object in the state
this._state = {tags: {}}; this._state = {tags: {}};
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this._onListsUpdated); RoomListStore.instance.off(LISTS_UPDATE_EVENT, this._onListsUpdated);
break;
} }
break;
} }
} }

View file

@ -168,7 +168,7 @@ class GroupFilterOrderStore extends Store {
Analytics.trackEvent('FilterStore', 'select_tag'); Analytics.trackEvent('FilterStore', 'select_tag');
} }
break; break;
case 'deselect_tags': case 'deselect_tags':
if (payload.tag) { if (payload.tag) {
// if a tag is passed, only deselect that tag // if a tag is passed, only deselect that tag
@ -181,7 +181,7 @@ class GroupFilterOrderStore extends Store {
}); });
} }
Analytics.trackEvent('FilterStore', 'deselect_tags'); Analytics.trackEvent('FilterStore', 'deselect_tags');
break; break;
case 'on_client_not_viable': case 'on_client_not_viable':
case 'on_logged_out': { case 'on_logged_out': {
// Reset state without pushing an update to the view, which generally assumes that // Reset state without pushing an update to the view, which generally assumes that
@ -207,8 +207,8 @@ class GroupFilterOrderStore extends Store {
groupIds.forEach(groupId => { groupIds.forEach(groupId => {
const rooms = const rooms =
GroupStore.getGroupRooms(groupId) GroupStore.getGroupRooms(groupId)
.map(r => client.getRoom(r.roomId)) // to Room objects .map(r => client.getRoom(r.roomId)) // to Room objects
.filter(r => r !== null && r !== undefined); // filter out rooms we haven't joined from the group .filter(r => r !== null && r !== undefined); // filter out rooms we haven't joined from the group
const badge = rooms && RoomNotifs.aggregateNotificationCount(rooms); const badge = rooms && RoomNotifs.aggregateNotificationCount(rooms);
changedBadges[groupId] = (badge && badge.count !== 0) ? badge : undefined; changedBadges[groupId] = (badge && badge.count !== 0) ? badge : undefined;
}); });

View file

@ -62,6 +62,8 @@ const INITIAL_STATE = {
shouldPeek: false, shouldPeek: false,
viaServers: [], viaServers: [],
wasContextSwitch: false,
}; };
/** /**
@ -116,6 +118,7 @@ class RoomViewStore extends Store<ActionPayload> {
roomId: null, roomId: null,
roomAlias: null, roomAlias: null,
viaServers: [], viaServers: [],
wasContextSwitch: false,
}); });
break; break;
case 'view_room_error': case 'view_room_error':
@ -195,6 +198,7 @@ class RoomViewStore extends Store<ActionPayload> {
// pull the user out of Room Settings // pull the user out of Room Settings
isEditingSettings: false, isEditingSettings: false,
viaServers: payload.via_servers, viaServers: payload.via_servers,
wasContextSwitch: payload.context_switch,
}; };
// Allow being given an event to be replied to when switching rooms but sanity check its for this room // Allow being given an event to be replied to when switching rooms but sanity check its for this room
@ -231,6 +235,7 @@ class RoomViewStore extends Store<ActionPayload> {
roomLoading: true, roomLoading: true,
roomLoadError: null, roomLoadError: null,
viaServers: payload.via_servers, viaServers: payload.via_servers,
wasContextSwitch: payload.context_switch,
}); });
try { try {
const result = await MatrixClientPeg.get().getRoomIdForAlias(payload.room_alias); const result = await MatrixClientPeg.get().getRoomIdForAlias(payload.room_alias);
@ -256,6 +261,8 @@ class RoomViewStore extends Store<ActionPayload> {
room_alias: payload.room_alias, room_alias: payload.room_alias,
auto_join: payload.auto_join, auto_join: payload.auto_join,
oob_data: payload.oob_data, oob_data: payload.oob_data,
viaServers: payload.via_servers,
wasContextSwitch: payload.context_switch,
}); });
} }
} }
@ -266,7 +273,6 @@ class RoomViewStore extends Store<ActionPayload> {
roomAlias: payload.room_alias, roomAlias: payload.room_alias,
roomLoading: false, roomLoading: false,
roomLoadError: payload.err, roomLoadError: payload.err,
viaServers: [],
}); });
} }
@ -426,6 +432,10 @@ class RoomViewStore extends Store<ActionPayload> {
public shouldPeek() { public shouldPeek() {
return this.state.shouldPeek; return this.state.shouldPeek;
} }
public getWasContextSwitch() {
return this.state.wasContextSwitch;
}
} }
let singletonRoomViewStore = null; let singletonRoomViewStore = null;

View file

@ -601,7 +601,11 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
let rooms = this.matrixClient.getVisibleRooms().filter(r => VisibilityProvider.instance.isRoomVisible(r)); let rooms = this.matrixClient.getVisibleRooms().filter(r => VisibilityProvider.instance.isRoomVisible(r));
if (this.prefilterConditions.length > 0) { // if spaces are enabled only consider the prefilter conditions when there are no runtime conditions
// for the search all spaces feature
if (this.prefilterConditions.length > 0
&& (!SettingsStore.getValue("feature_spaces") || !this.filterConditions.length)
) {
rooms = rooms.filter(r => { rooms = rooms.filter(r => {
for (const filter of this.prefilterConditions) { for (const filter of this.prefilterConditions) {
if (!filter.isVisible(r)) { if (!filter.isVisible(r)) {
@ -675,6 +679,10 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
if (this.algorithm) { if (this.algorithm) {
this.algorithm.addFilterCondition(filter); this.algorithm.addFilterCondition(filter);
} }
// Runtime filters with spaces disable prefiltering for the search all spaces effect
if (SettingsStore.getValue("feature_spaces")) {
promise = this.recalculatePrefiltering();
}
} }
promise.then(() => this.updateFn.trigger()); promise.then(() => this.updateFn.trigger());
} }
@ -698,6 +706,10 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
if (this.algorithm) { if (this.algorithm) {
this.algorithm.removeFilterCondition(filter); this.algorithm.removeFilterCondition(filter);
// Runtime filters with spaces disable prefiltering for the search all spaces effect
if (SettingsStore.getValue("feature_spaces")) {
promise = this.recalculatePrefiltering();
}
} }
} }
idx = this.prefilterConditions.indexOf(filter); idx = this.prefilterConditions.indexOf(filter);

View file

@ -310,8 +310,7 @@ function unpackMegolmKeyFile(data) {
// look for the end line // look for the end line
while (1) { while (1) {
const lineEnd = fileStr.indexOf('\n', lineStart); const lineEnd = fileStr.indexOf('\n', lineStart);
const line = fileStr.slice(lineStart, lineEnd < 0 ? undefined : lineEnd) const line = fileStr.slice(lineStart, lineEnd < 0 ? undefined : lineEnd).trim();
.trim();
if (line === TRAILER_LINE) { if (line === TRAILER_LINE) {
break; break;
} }

View file

@ -177,7 +177,7 @@ describe('QueryMatcher', function() {
const qm = new QueryMatcher(NONWORDOBJECTS, { const qm = new QueryMatcher(NONWORDOBJECTS, {
keys: ["name"], keys: ["name"],
shouldMatchWordsOnly: false, shouldMatchWordsOnly: false,
}); });
const results = qm.match('bob'); const results = qm.match('bob');
expect(results.length).toBe(1); expect(results.length).toBe(1);

View file

@ -26,9 +26,9 @@ describe("AccessSecretStorageDialog", function() {
it("Closes the dialog if _onRecoveryKeyNext is called with a valid key", (done) => { it("Closes the dialog if _onRecoveryKeyNext is called with a valid key", (done) => {
const testInstance = TestRenderer.create( const testInstance = TestRenderer.create(
<AccessSecretStorageDialog <AccessSecretStorageDialog
checkPrivateKey={(p) => p && p.recoveryKey && p.recoveryKey == "a"} checkPrivateKey={(p) => p && p.recoveryKey && p.recoveryKey == "a"}
onFinished={(v) => { onFinished={(v) => {
if (v) { done(); } if (v) { done(); }
}} }}
/>, />,
); );
@ -43,7 +43,7 @@ describe("AccessSecretStorageDialog", function() {
it("Considers a valid key to be valid", async function() { it("Considers a valid key to be valid", async function() {
const testInstance = TestRenderer.create( const testInstance = TestRenderer.create(
<AccessSecretStorageDialog <AccessSecretStorageDialog
checkPrivateKey={() => true} checkPrivateKey={() => true}
/>, />,
); );
const v = "asdf"; const v = "asdf";
@ -61,7 +61,7 @@ describe("AccessSecretStorageDialog", function() {
it("Notifies the user if they input an invalid Security Key", async function(done) { it("Notifies the user if they input an invalid Security Key", async function(done) {
const testInstance = TestRenderer.create( const testInstance = TestRenderer.create(
<AccessSecretStorageDialog <AccessSecretStorageDialog
checkPrivateKey={async () => false} checkPrivateKey={async () => false}
/>, />,
); );
const e = { target: { value: "a" } }; const e = { target: { value: "a" } };
@ -87,12 +87,14 @@ describe("AccessSecretStorageDialog", function() {
it("Notifies the user if they input an invalid passphrase", async function(done) { it("Notifies the user if they input an invalid passphrase", async function(done) {
const testInstance = TestRenderer.create( const testInstance = TestRenderer.create(
<AccessSecretStorageDialog <AccessSecretStorageDialog
checkPrivateKey={() => false} checkPrivateKey={() => false}
onFinished={() => {}} onFinished={() => {}}
keyInfo={ { passphrase: { keyInfo={{
salt: 'nonempty', passphrase: {
iterations: 2, salt: 'nonempty',
} } } iterations: 2,
},
}}
/>, />,
); );
const e = { target: { value: "a" } }; const e = { target: { value: "a" } };

View file

@ -245,8 +245,7 @@ describe('MemberEventListSummary', function() {
); );
}); });
it('truncates multiple sequences of repetitions with other events between', it('truncates multiple sequences of repetitions with other events between', function() {
function() {
const events = generateEvents([ const events = generateEvents([
{ {
userId: "@user_1:some.domain", userId: "@user_1:some.domain",
@ -395,8 +394,7 @@ describe('MemberEventListSummary', function() {
); );
}); });
it('correctly orders sequences of transitions by the order of their first event', it('correctly orders sequences of transitions by the order of their first event', function() {
function() {
const events = generateEvents([ const events = generateEvents([
{ {
userId: "@user_2:some.domain", userId: "@user_2:some.domain",
@ -568,8 +566,7 @@ describe('MemberEventListSummary', function() {
); );
}); });
it('handles invitation plurals correctly when there are multiple invites', it('handles invitation plurals correctly when there are multiple invites', function() {
function() {
const events = generateEvents([ const events = generateEvents([
{ {
userId: "@user_1:some.domain", userId: "@user_1:some.domain",

View file

@ -100,7 +100,7 @@ describe('MemberList', () => {
memberList = r; memberList = r;
}; };
root = ReactDOM.render(<WrappedMemberList roomId={memberListRoom.roomId} root = ReactDOM.render(<WrappedMemberList roomId={memberListRoom.roomId}
wrappedRef={gatherWrappedRef} />, parentDiv); wrappedRef={gatherWrappedRef} />, parentDiv);
}); });
afterEach((done) => { afterEach((done) => {

View file

@ -70,8 +70,9 @@ describe('RoomList', () => {
root = ReactDOM.render( root = ReactDOM.render(
<DragDropContext> <DragDropContext>
<WrappedRoomList searchFilter="" onResize={() => {}} /> <WrappedRoomList searchFilter="" onResize={() => {}} />
</DragDropContext> </DragDropContext>,
, parentDiv); parentDiv,
);
ReactTestUtils.findRenderedComponentWithType(root, RoomList); ReactTestUtils.findRenderedComponentWithType(root, RoomList);
movingRoom = createRoom({name: 'Moving room'}); movingRoom = createRoom({name: 'Moving room'});

View file

@ -93,10 +93,10 @@ module.exports = class ElementSession {
const type = req.resourceType(); const type = req.resourceType();
const response = await req.response(); const response = await req.response();
//if (type === 'xhr' || type === 'fetch') { //if (type === 'xhr' || type === 'fetch') {
buffer += `${type} ${response.status()} ${req.method()} ${req.url()} \n`; buffer += `${type} ${response.status()} ${req.method()} ${req.url()} \n`;
// if (req.method() === "POST") { // if (req.method() === "POST") {
// buffer += " Post data: " + req.postData(); // buffer += " Post data: " + req.postData();
// } // }
//} //}
}); });
return { return {

View file

@ -84,22 +84,22 @@ describe('MegolmExportEncryption', function() {
it('should handle missing header', function() { it('should handle missing header', function() {
const input=stringToArray(`-----`); const input=stringToArray(`-----`);
return MegolmExportEncryption.decryptMegolmKeyFile(input, '') return MegolmExportEncryption.decryptMegolmKeyFile(input, '')
.then((res) => { .then((res) => {
throw new Error('expected to throw'); throw new Error('expected to throw');
}, (error) => { }, (error) => {
expect(error.message).toEqual('Header line not found'); expect(error.message).toEqual('Header line not found');
}); });
}); });
it('should handle missing trailer', function() { it('should handle missing trailer', function() {
const input=stringToArray(`-----BEGIN MEGOLM SESSION DATA----- const input=stringToArray(`-----BEGIN MEGOLM SESSION DATA-----
-----`); -----`);
return MegolmExportEncryption.decryptMegolmKeyFile(input, '') return MegolmExportEncryption.decryptMegolmKeyFile(input, '')
.then((res) => { .then((res) => {
throw new Error('expected to throw'); throw new Error('expected to throw');
}, (error) => { }, (error) => {
expect(error.message).toEqual('Trailer line not found'); expect(error.message).toEqual('Trailer line not found');
}); });
}); });
it('should handle a too-short body', function() { it('should handle a too-short body', function() {
@ -109,11 +109,11 @@ cissyYBxjsfsAn
-----END MEGOLM SESSION DATA----- -----END MEGOLM SESSION DATA-----
`); `);
return MegolmExportEncryption.decryptMegolmKeyFile(input, '') return MegolmExportEncryption.decryptMegolmKeyFile(input, '')
.then((res) => { .then((res) => {
throw new Error('expected to throw'); throw new Error('expected to throw');
}, (error) => { }, (error) => {
expect(error.message).toEqual('Invalid file: too short'); expect(error.message).toEqual('Invalid file: too short');
}); });
}); });
// TODO find a subtlecrypto shim which doesn't break this test // TODO find a subtlecrypto shim which doesn't break this test

View file

@ -26,7 +26,7 @@ describe("mkClient self-test", function() {
["@TF:h", true], ["@TF:h", true],
["@FT:h", false], ["@FT:h", false],
["@FF:h", false]], ["@FF:h", false]],
)("behaves well for user trust %s", (userId, trust) => { )("behaves well for user trust %s", (userId, trust) => {
expect(mkClient().checkUserTrust(userId).isCrossSigningVerified()).toBe(trust); expect(mkClient().checkUserTrust(userId).isCrossSigningVerified()).toBe(trust);
}); });
@ -35,7 +35,7 @@ describe("mkClient self-test", function() {
["@TF:h", false], ["@TF:h", false],
["@FT:h", true], ["@FT:h", true],
["@FF:h", false]], ["@FF:h", false]],
)("behaves well for device trust %s", (userId, trust) => { )("behaves well for device trust %s", (userId, trust) => {
expect(mkClient().checkDeviceTrust(userId, "device").isVerified()).toBe(trust); expect(mkClient().checkDeviceTrust(userId, "device").isVerified()).toBe(trust);
}); });
}); });