Merge branches 'develop' and 't3chguy/composer_demote' of github.com:matrix-org/matrix-react-sdk into t3chguy/composer_demote

# Conflicts:
#	src/components/views/rooms/MessageComposer.js
This commit is contained in:
Michael Telatynski 2019-02-27 00:28:16 +00:00
commit f16011394e
79 changed files with 1809 additions and 1041 deletions

View file

@ -34,6 +34,7 @@ import GroupStore from '../../stores/GroupStore';
import FlairStore from '../../stores/FlairStore';
import { showGroupAddRoomDialog } from '../../GroupAddressPicker';
import {makeGroupPermalink, makeUserPermalink} from "../../matrix-to";
import {Group} from "matrix-js-sdk";
const LONG_DESC_PLACEHOLDER = _td(
`<h1>HTML for your community's page</h1>
@ -569,7 +570,7 @@ export default React.createClass({
_onShareClick: function() {
const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
Modal.createTrackedDialog('share community dialog', '', ShareDialog, {
target: this._matrixClient.getGroup(this.props.groupId),
target: this._matrixClient.getGroup(this.props.groupId) || new Group(this.props.groupId),
});
},

View file

@ -26,6 +26,7 @@ import dis from '../../dispatcher';
import VectorConferenceHandler from '../../VectorConferenceHandler';
import TagPanelButtons from './TagPanelButtons';
import SettingsStore from '../../settings/SettingsStore';
import {_t} from "../../languageHandler";
const LeftPanel = React.createClass({
@ -212,6 +213,7 @@ const LeftPanel = React.createClass({
);
const searchBox = (<SearchBox
placeholder={ _t('Filter room names') }
onSearch={ this.onSearch }
onCleared={ this.onSearchCleared }
collapsed={this.props.collapsed} />);

View file

@ -525,6 +525,7 @@ module.exports = React.createClass({
eventSendStatus={mxEv.status}
tileShape={this.props.tileShape}
isTwelveHour={this.props.isTwelveHour}
permalinkCreator={this.props.permalinkCreator}
last={last} isSelectedEvent={highlight} />
</li>,
);

View file

@ -30,6 +30,7 @@ import Promise from 'bluebird';
import filesize from 'filesize';
const classNames = require("classnames");
import { _t } from '../../languageHandler';
import {RoomPermalinkCreator} from "../../matrix-to";
const MatrixClientPeg = require("../../MatrixClientPeg");
const ContentMessages = require("../../ContentMessages");
@ -441,6 +442,11 @@ module.exports = React.createClass({
RoomScrollStateStore.setScrollState(this.state.roomId, this._getScrollState());
}
// stop tracking room changes to format permalinks
if (this.state.permalinkCreator) {
this.state.permalinkCreator.stop();
}
if (this.refs.roomView) {
// disconnect the D&D event listeners from the room view. This
// is really just for hygiene - we're going to be
@ -537,12 +543,12 @@ module.exports = React.createClass({
case 'picture_snapshot':
this.uploadFile(payload.file);
break;
case 'notifier_enabled':
case 'upload_failed':
// 413: File was too big or upset the server in some way.
if(payload.error.http_status === 413) {
if (payload.error && payload.error.http_status === 413) {
this._fetchMediaConfig(true);
}
case 'notifier_enabled':
case 'upload_started':
case 'upload_finished':
this.forceUpdate();
@ -652,6 +658,11 @@ module.exports = React.createClass({
this._loadMembersIfJoined(room);
this._calculateRecommendedVersion(room);
this._updateE2EStatus(room);
if (!this.state.permalinkCreator) {
const permalinkCreator = new RoomPermalinkCreator(room);
permalinkCreator.start();
this.setState({permalinkCreator});
}
},
_calculateRecommendedVersion: async function(room) {
@ -1219,6 +1230,7 @@ module.exports = React.createClass({
searchResult={result}
searchHighlights={this.state.searchHighlights}
resultLink={resultLink}
permalinkCreator={this.state.permalinkCreator}
onWidgetLoad={onWidgetLoad} />);
}
return ret;
@ -1305,7 +1317,10 @@ module.exports = React.createClass({
},
onSearchClick: function() {
this.setState({ searching: true, showingPinned: false });
this.setState({
searching: !this.state.searching,
showingPinned: false,
});
},
onCancelSearchClick: function() {
@ -1722,6 +1737,7 @@ module.exports = React.createClass({
showApps={this.state.showApps}
uploadAllowed={this.isFileUploadAllowed}
e2eStatus={this.state.e2eStatus}
permalinkCreator={this.state.permalinkCreator}
/>;
}
@ -1823,6 +1839,7 @@ module.exports = React.createClass({
showUrlPreview = {this.state.showUrlPreview}
className="mx_RoomView_messagePanel"
membersLoaded={this.state.membersLoaded}
permalinkCreator={this.state.permalinkCreator}
/>);
let topUnreadMessagesBar = null;

View file

@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,12 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
import React from 'react';
import { _t } from '../../languageHandler';
import PropTypes from 'prop-types';
import { KeyCode } from '../../Keyboard';
import sdk from '../../index';
import dis from '../../dispatcher';
import { throttle } from 'lodash';
import AccessibleButton from '../../components/views/elements/AccessibleButton';
@ -28,8 +26,10 @@ module.exports = React.createClass({
displayName: 'SearchBox',
propTypes: {
onSearch: React.PropTypes.func,
onCleared: React.PropTypes.func,
onSearch: PropTypes.func,
onCleared: PropTypes.func,
className: PropTypes.string,
placeholder: PropTypes.string.isRequired,
},
getInitialState: function() {
@ -102,21 +102,22 @@ module.exports = React.createClass({
const clearButton = this.state.searchTerm.length > 0 ?
(<AccessibleButton key="button"
className="mx_SearchBox_closeButton"
onClick={ () => {this._clearSearch("button")} }>
</AccessibleButton>) : undefined;
onClick={ () => {this._clearSearch("button"); } }>
</AccessibleButton>) : undefined;
const className = this.props.className || "";
return (
<div className="mx_SearchBox mx_textinput">
<input
key="searchfield"
type="text"
ref="search"
className="mx_textinput_icon mx_textinput_search"
className={"mx_textinput_icon mx_textinput_search " + className}
value={ this.state.searchTerm }
onFocus={ this._onFocus }
onChange={ this.onChange }
onKeyDown={ this._onKeyDown }
placeholder={ _t('Filter room names') }
placeholder={ this.props.placeholder }
/>
{ clearButton }
</div>

View file

@ -1202,6 +1202,7 @@ var TimelinePanel = React.createClass({
return (
<MessagePanel ref="messagePanel"
room={this.props.timelineSet.room}
permalinkCreator={this.props.permalinkCreator}
hidden={this.props.hidden}
backPaginating={this.state.backPaginating}
forwardPaginating={forwardPaginating}

View file

@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,11 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
import React from 'react';
import PropTypes from 'prop-types';
import SyntaxHighlight from '../views/elements/SyntaxHighlight';
import {_t} from "../../languageHandler";
import sdk from "../../index";
module.exports = React.createClass({
@ -27,31 +28,24 @@ module.exports = React.createClass({
propTypes: {
content: PropTypes.object.isRequired,
onFinished: PropTypes.func.isRequired,
},
componentDidMount: function() {
document.addEventListener("keydown", this.onKeyDown);
},
componentWillUnmount: function() {
document.removeEventListener("keydown", this.onKeyDown);
},
onKeyDown: function(ev) {
if (ev.keyCode == 27) { // escape
ev.stopPropagation();
ev.preventDefault();
this.props.onFinished();
}
roomId: PropTypes.string.isRequired,
eventId: PropTypes.string.isRequired,
},
render: function() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return (
<div className="mx_ViewSource">
<SyntaxHighlight className="json">
{ JSON.stringify(this.props.content, null, 2) }
</SyntaxHighlight>
</div>
<BaseDialog className="mx_ViewSource" onFinished={this.props.onFinished} title={_t('View Source')}>
<div className="mx_ViewSource_label_left">Room ID: { this.props.roomId }</div>
<div className="mx_ViewSource_label_right">Event ID: { this.props.eventId }</div>
<div className="mx_ViewSource_label_bottom" />
<div className="mx_Dialog_content">
<SyntaxHighlight className="json">
{ JSON.stringify(this.props.content, null, 2) }
</SyntaxHighlight>
</div>
</BaseDialog>
);
},
});

View file

@ -308,7 +308,19 @@ module.exports = React.createClass({
});
},
onFormValidationFailed: function(errCode) {
onFormValidationChange: function(fieldErrors) {
// `fieldErrors` is an object mapping field IDs to error codes when there is an
// error or `null` for no error, so the values array will be something like:
// `[ null, "RegistrationForm.ERR_PASSWORD_MISSING", null]`
// Find the first non-null error code and show that.
const errCode = Object.values(fieldErrors).find(value => !!value);
if (!errCode) {
this.setState({
errorText: null,
});
return;
}
let errMsg;
switch (errCode) {
case "RegistrationForm.ERR_PASSWORD_MISSING":
@ -510,7 +522,7 @@ module.exports = React.createClass({
defaultPhoneNumber={this.state.formVals.phoneNumber}
defaultPassword={this.state.formVals.password}
minPasswordLength={MIN_PASSWORD_LENGTH}
onError={this.onFormValidationFailed}
onValidationChange={this.onFormValidationChange}
onRegisterClick={this.onFormSubmit}
onEditServerDetailsClick={onEditServerDetailsClick}
flows={this.state.flows}

View file

@ -66,7 +66,7 @@ module.exports = React.createClass({
}
const scriptTag = document.createElement('script');
scriptTag.setAttribute(
'src', `${protocol}//www.google.com/recaptcha/api.js?onload=mx_on_recaptcha_loaded&render=explicit`,
'src', `${protocol}//www.recaptcha.net/recaptcha/api.js?onload=mx_on_recaptcha_loaded&render=explicit`,
);
this.refs.recaptchaContainer.appendChild(scriptTag);
}

View file

@ -46,7 +46,7 @@ module.exports = React.createClass({
defaultUsername: PropTypes.string,
defaultPassword: PropTypes.string,
minPasswordLength: PropTypes.number,
onError: PropTypes.func,
onValidationChange: PropTypes.func,
onRegisterClick: PropTypes.func.isRequired, // onRegisterClick(Object) => ?Promise
onEditServerDetailsClick: PropTypes.func,
flows: PropTypes.arrayOf(PropTypes.object).isRequired,
@ -60,15 +60,14 @@ module.exports = React.createClass({
getDefaultProps: function() {
return {
minPasswordLength: 6,
onError: function(e) {
console.error(e);
},
onValidationChange: console.error,
};
},
getInitialState: function() {
return {
fieldValid: {},
// Field error codes by field ID
fieldErrors: {},
// The ISO2 country code selected in the phone number entry
phoneCountry: this.props.defaultPhoneCountry,
};
@ -81,12 +80,12 @@ module.exports = React.createClass({
// the error that ends up being displayed
// is the one from the first invalid field.
// It's not super ideal that this just calls
// onError once for each invalid field.
// onValidationChange once for each invalid field.
this.validateField(FIELD_PHONE_NUMBER, ev.type);
this.validateField(FIELD_EMAIL, ev.type);
this.validateField(FIELD_PASSWORD_CONFIRM, ev.type);
this.validateField(FIELD_PASSWORD, ev.type);
this.validateField(FIELD_USERNAME, ev.type);
this.validateField(FIELD_PHONE_NUMBER, ev.type);
this.validateField(FIELD_EMAIL, ev.type);
const self = this;
if (this.allFieldsValid()) {
@ -134,9 +133,9 @@ module.exports = React.createClass({
* @returns {boolean} true if all fields were valid last time they were validated.
*/
allFieldsValid: function() {
const keys = Object.keys(this.state.fieldValid);
const keys = Object.keys(this.state.fieldErrors);
for (let i = 0; i < keys.length; ++i) {
if (this.state.fieldValid[keys[i]] == false) {
if (this.state.fieldErrors[keys[i]]) {
return false;
}
}
@ -206,21 +205,29 @@ module.exports = React.createClass({
}
break;
case FIELD_PASSWORD_CONFIRM:
this.markFieldValid(
fieldID, pwd1 == pwd2,
"RegistrationForm.ERR_PASSWORD_MISMATCH",
);
if (allowEmpty && pwd2 === "") {
this.markFieldValid(fieldID, true);
} else {
this.markFieldValid(
fieldID, pwd1 == pwd2,
"RegistrationForm.ERR_PASSWORD_MISMATCH",
);
}
break;
}
},
markFieldValid: function(fieldID, val, errorCode) {
const fieldValid = this.state.fieldValid;
fieldValid[fieldID] = val;
this.setState({fieldValid: fieldValid});
if (!val) {
this.props.onError(errorCode);
markFieldValid: function(fieldID, valid, errorCode) {
const { fieldErrors } = this.state;
if (valid) {
fieldErrors[fieldID] = null;
} else {
fieldErrors[fieldID] = errorCode;
}
this.setState({
fieldErrors,
});
this.props.onValidationChange(fieldErrors);
},
fieldElementById(fieldID) {
@ -240,7 +247,7 @@ module.exports = React.createClass({
_classForField: function(fieldID, ...baseClasses) {
let cls = baseClasses.join(' ');
if (this.state.fieldValid[fieldID] === false) {
if (this.state.fieldErrors[fieldID]) {
if (cls) cls += ' ';
cls += 'error';
}

View file

@ -26,7 +26,6 @@ import { _t } from '../../../languageHandler';
import Modal from '../../../Modal';
import Resend from '../../../Resend';
import SettingsStore from '../../../settings/SettingsStore';
import {makeEventPermalink} from '../../../matrix-to';
import { isUrlPermitted } from '../../../HtmlUtils';
module.exports = React.createClass({
@ -98,6 +97,8 @@ module.exports = React.createClass({
onViewSourceClick: function() {
const ViewSource = sdk.getComponent('structures.ViewSource');
Modal.createTrackedDialog('View Event Source', '', ViewSource, {
roomId: this.props.mxEvent.getRoomId(),
eventId: this.props.mxEvent.getId(),
content: this.props.mxEvent.event,
}, 'mx_Dialog_viewsource');
this.closeMenu();
@ -106,6 +107,8 @@ module.exports = React.createClass({
onViewClearSourceClick: function() {
const ViewSource = sdk.getComponent('structures.ViewSource');
Modal.createTrackedDialog('View Clear Event Source', '', ViewSource, {
roomId: this.props.mxEvent.getRoomId(),
eventId: this.props.mxEvent.getId(),
// FIXME: _clearEvent is private
content: this.props.mxEvent._clearEvent,
}, 'mx_Dialog_viewsource');
@ -193,6 +196,7 @@ module.exports = React.createClass({
const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
Modal.createTrackedDialog('share room message dialog', '', ShareDialog, {
target: this.props.mxEvent,
permalinkCreator: this.props.permalinkCreator,
});
this.closeMenu();
},
@ -211,7 +215,8 @@ module.exports = React.createClass({
},
render: function() {
const eventStatus = this.props.mxEvent.status;
const mxEvent = this.props.mxEvent;
const eventStatus = mxEvent.status;
let resendButton;
let redactButton;
let cancelButton;
@ -251,8 +256,8 @@ module.exports = React.createClass({
);
}
if (isSent && this.props.mxEvent.getType() === 'm.room.message') {
const content = this.props.mxEvent.getContent();
if (isSent && mxEvent.getType() === 'm.room.message') {
const content = mxEvent.getContent();
if (content.msgtype && content.msgtype !== 'm.bad.encrypted' && content.hasOwnProperty('body')) {
forwardButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onForwardClick}>
@ -282,7 +287,7 @@ module.exports = React.createClass({
</div>
);
if (this.props.mxEvent.getType() !== this.props.mxEvent.getWireType()) {
if (mxEvent.getType() !== mxEvent.getWireType()) {
viewClearSourceButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onViewClearSourceClick}>
{ _t('View Decrypted Source') }
@ -300,11 +305,21 @@ module.exports = React.createClass({
}
}
let permalink;
if (this.props.permalinkCreator) {
permalink = this.props.permalinkCreator.forEvent(
this.props.mxEvent.getRoomId(),
this.props.mxEvent.getId(),
);
}
// XXX: if we use room ID, we should also include a server where the event can be found (other than in the domain of the event ID)
const permalinkButton = (
<div className="mx_MessageContextMenu_field">
<a href={makeEventPermalink(this.props.mxEvent.getRoomId(), this.props.mxEvent.getId())}
target="_blank" rel="noopener" onClick={this.onPermalinkClick}>{ _t('Share Message') }</a>
<a href={permalink}
target="_blank" rel="noopener" onClick={this.onPermalinkClick}>
{ mxEvent.isRedacted() || mxEvent.getType() !== 'm.room.message'
? _t('Share Permalink') : _t('Share Message') }
</a>
</div>
);
@ -318,12 +333,12 @@ module.exports = React.createClass({
// Bridges can provide a 'external_url' to link back to the source.
if (
typeof(this.props.mxEvent.event.content.external_url) === "string" &&
isUrlPermitted(this.props.mxEvent.event.content.external_url)
typeof(mxEvent.event.content.external_url) === "string" &&
isUrlPermitted(mxEvent.event.content.external_url)
) {
externalURLButton = (
<div className="mx_MessageContextMenu_field">
<a href={this.props.mxEvent.event.content.external_url}
<a href={mxEvent.event.content.external_url}
rel="noopener" target="_blank" onClick={this.closeMenu}>{ _t('Source URL') }</a>
</div>
);

View file

@ -271,6 +271,27 @@ module.exports = React.createClass({
);
},
_onClickSettings: function() {
dis.dispatch({
action: 'open_room_settings',
room_id: this.props.room.roomId,
});
if (this.props.onFinished) {
this.props.onFinished();
}
},
_renderSettingsMenu: function() {
return (
<div>
<div className="mx_RoomTileContextMenu_tag_field" onClick={this._onClickSettings} >
<img className="mx_RoomTileContextMenu_tag_icon" src={require("../../../../res/img/icons-settings-room.svg")} width="15" height="15" />
{ _t('Settings') }
</div>
</div>
);
},
_renderLeaveMenu: function(membership) {
if (!membership) {
return null;
@ -350,7 +371,11 @@ module.exports = React.createClass({
// Can't set notif level or tags on non-join rooms
if (myMembership !== 'join') {
return this._renderLeaveMenu(myMembership);
return <div>
{ this._renderLeaveMenu(myMembership) }
<hr className="mx_RoomTileContextMenu_separator" />
{ this._renderSettingsMenu() }
</div>;
}
return (
@ -360,6 +385,8 @@ module.exports = React.createClass({
{ this._renderLeaveMenu(myMembership) }
<hr className="mx_RoomTileContextMenu_separator" />
{ this._renderRoomTagMenu() }
<hr className="mx_RoomTileContextMenu_separator" />
{ this._renderSettingsMenu() }
</div>
);
},

View file

@ -20,6 +20,7 @@ import sdk from '../../../index';
import SyntaxHighlight from '../elements/SyntaxHighlight';
import { _t } from '../../../languageHandler';
import MatrixClientPeg from '../../../MatrixClientPeg';
import Field from "../elements/Field";
class DevtoolsComponent extends React.Component {
static contextTypes = {
@ -56,14 +57,8 @@ class GenericEditor extends DevtoolsComponent {
}
textInput(id, label) {
return <div className="mx_DevTools_inputRow">
<div className="mx_DevTools_inputLabelCell">
<label htmlFor={id}>{ label }</label>
</div>
<div className="mx_DevTools_inputCell">
<input id={id} className="mx_TextInputDialog_input" onChange={this._onChange} value={this.state[id]} size="32" autoFocus={true} />
</div>
</div>;
return <Field id={id} label={label} size="42" autoFocus={true} type="text" autoComplete="on"
value={this.state[id]} onChange={this._onChange} />;
}
}
@ -138,12 +133,8 @@ class SendCustomEvent extends GenericEditor {
<br />
<div className="mx_DevTools_inputLabelCell">
<label htmlFor="evContent"> { _t('Event Content') } </label>
</div>
<div>
<textarea id="evContent" onChange={this._onChange} value={this.state.evContent} className="mx_DevTools_textarea" />
</div>
<Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea"
autoComplete="off" value={this.state.evContent} onChange={this._onChange} element="textarea" />
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.onBack}>{ _t('Back') }</button>
@ -223,12 +214,8 @@ class SendAccountData extends GenericEditor {
{ this.textInput('eventType', _t('Event Type')) }
<br />
<div className="mx_DevTools_inputLabelCell">
<label htmlFor="evContent"> { _t('Event Content') } </label>
</div>
<div>
<textarea id="evContent" onChange={this._onChange} value={this.state.evContent} className="mx_DevTools_textarea" />
</div>
<Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea"
autoComplete="off" value={this.state.evContent} onChange={this._onChange} element="textarea" />
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.onBack}>{ _t('Back') }</button>
@ -302,14 +289,12 @@ class FilteredList extends React.Component {
render() {
const TruncatedList = sdk.getComponent("elements.TruncatedList");
return <div>
<input size="64"
autoFocus={true}
onChange={this.onQuery}
value={this.props.query}
placeholder={_t('Filter results')}
<Field id="DevtoolsDialog_FilteredList_filter" label={_t('Filter results')} autoFocus={true} size={64}
type="text" autoComplete="off" value={this.props.query} onChange={this.onQuery}
className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query"
// force re-render so that autoFocus is applied when this component is re-used
key={this.props.children[0] ? this.props.children[0].key : ''} />
<TruncatedList getChildren={this.getChildren}
getChildCount={this.getChildCount}
truncateAt={this.state.truncateAt}

View file

@ -18,11 +18,12 @@ import React from 'react';
import PropTypes from 'prop-types';
import {Tab, TabbedView} from "../../structures/TabbedView";
import {_t, _td} from "../../../languageHandler";
import AdvancedRoomSettingsTab from "../settings/tabs/AdvancedRoomSettingsTab";
import RolesRoomSettingsTab from "../settings/tabs/RolesRoomSettingsTab";
import GeneralRoomSettingsTab from "../settings/tabs/GeneralRoomSettingsTab";
import SecurityRoomSettingsTab from "../settings/tabs/SecurityRoomSettingsTab";
import AdvancedRoomSettingsTab from "../settings/tabs/room/AdvancedRoomSettingsTab";
import RolesRoomSettingsTab from "../settings/tabs/room/RolesRoomSettingsTab";
import GeneralRoomSettingsTab from "../settings/tabs/room/GeneralRoomSettingsTab";
import SecurityRoomSettingsTab from "../settings/tabs/room/SecurityRoomSettingsTab";
import sdk from "../../../index";
import MatrixClientPeg from "../../../MatrixClientPeg";
export default class RoomSettingsDialog extends React.Component {
static propTypes = {
@ -60,9 +61,10 @@ export default class RoomSettingsDialog extends React.Component {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const roomName = MatrixClientPeg.get().getRoom(this.props.roomId).name;
return (
<BaseDialog className='mx_RoomSettingsDialog' hasCancel={true}
onFinished={this.props.onFinished} title={_t("Room Settings")}>
onFinished={this.props.onFinished} title={_t("Room Settings - %(roomName)s", {roomName})}>
<div className='ms_SettingsDialog_content'>
<TabbedView tabs={this._getTabs()} />
</div>

View file

@ -20,7 +20,7 @@ import {Room, User, Group, RoomMember, MatrixEvent} from 'matrix-js-sdk';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import QRCode from 'qrcode-react';
import {makeEventPermalink, makeGroupPermalink, makeRoomPermalink, makeUserPermalink} from "../../../matrix-to";
import {RoomPermalinkCreator, makeGroupPermalink, makeUserPermalink} from "../../../matrix-to";
import * as ContextualMenu from "../../structures/ContextualMenu";
const socials = [
@ -123,6 +123,14 @@ export default class ShareDialog extends React.Component {
});
}
componentWillMount() {
if (this.props.target instanceof Room) {
const permalinkCreator = new RoomPermalinkCreator(this.props.target);
permalinkCreator.load();
this.setState({permalinkCreator});
}
}
render() {
let title;
let matrixToUrl;
@ -146,9 +154,9 @@ export default class ShareDialog extends React.Component {
}
if (this.state.linkSpecificEvent) {
matrixToUrl = makeEventPermalink(this.props.target.roomId, events[events.length - 1].getId());
matrixToUrl = this.state.permalinkCreator.forEvent(events[events.length - 1].getId());
} else {
matrixToUrl = makeRoomPermalink(this.props.target.roomId);
matrixToUrl = this.state.permalinkCreator.forRoom();
}
} else if (this.props.target instanceof User || this.props.target instanceof RoomMember) {
title = _t('Share User');
@ -169,9 +177,9 @@ export default class ShareDialog extends React.Component {
</div>;
if (this.state.linkSpecificEvent) {
matrixToUrl = makeEventPermalink(this.props.target.getRoomId(), this.props.target.getId());
matrixToUrl = this.props.permalinkCreator.forEvent(this.props.target.getId());
} else {
matrixToUrl = makeRoomPermalink(this.props.target.getRoomId());
matrixToUrl = this.props.permalinkCreator.forRoom();
}
}

View file

@ -18,15 +18,15 @@ import React from 'react';
import PropTypes from 'prop-types';
import {Tab, TabbedView} from "../../structures/TabbedView";
import {_t, _td} from "../../../languageHandler";
import GeneralUserSettingsTab from "../settings/tabs/GeneralUserSettingsTab";
import GeneralUserSettingsTab from "../settings/tabs/user/GeneralUserSettingsTab";
import SettingsStore from "../../../settings/SettingsStore";
import LabsSettingsTab from "../settings/tabs/LabsSettingsTab";
import SecuritySettingsTab from "../settings/tabs/SecuritySettingsTab";
import NotificationSettingsTab from "../settings/tabs/NotificationSettingsTab";
import PreferencesSettingsTab from "../settings/tabs/PreferencesSettingsTab";
import VoiceSettingsTab from "../settings/tabs/VoiceSettingsTab";
import HelpSettingsTab from "../settings/tabs/HelpSettingsTab";
import FlairSettingsTab from "../settings/tabs/FlairSettingsTab";
import LabsUserSettingsTab from "../settings/tabs/user/LabsUserSettingsTab";
import SecurityUserSettingsTab from "../settings/tabs/user/SecurityUserSettingsTab";
import NotificationUserSettingsTab from "../settings/tabs/user/NotificationUserSettingsTab";
import PreferencesUserSettingsTab from "../settings/tabs/user/PreferencesUserSettingsTab";
import VoiceUserSettingsTab from "../settings/tabs/user/VoiceUserSettingsTab";
import HelpUserSettingsTab from "../settings/tabs/user/HelpUserSettingsTab";
import FlairUserSettingsTab from "../settings/tabs/user/FlairUserSettingsTab";
import sdk from "../../../index";
export default class UserSettingsDialog extends React.Component {
@ -45,39 +45,39 @@ export default class UserSettingsDialog extends React.Component {
tabs.push(new Tab(
_td("Flair"),
"mx_UserSettingsDialog_flairIcon",
<FlairSettingsTab />,
<FlairUserSettingsTab />,
));
tabs.push(new Tab(
_td("Notifications"),
"mx_UserSettingsDialog_bellIcon",
<NotificationSettingsTab />,
<NotificationUserSettingsTab />,
));
tabs.push(new Tab(
_td("Preferences"),
"mx_UserSettingsDialog_preferencesIcon",
<PreferencesSettingsTab />,
<PreferencesUserSettingsTab />,
));
tabs.push(new Tab(
_td("Voice & Video"),
"mx_UserSettingsDialog_voiceIcon",
<VoiceSettingsTab />,
<VoiceUserSettingsTab />,
));
tabs.push(new Tab(
_td("Security & Privacy"),
"mx_UserSettingsDialog_securityIcon",
<SecuritySettingsTab />,
<SecurityUserSettingsTab />,
));
if (SettingsStore.getLabsFeatures().length > 0) {
tabs.push(new Tab(
_td("Labs"),
"mx_UserSettingsDialog_labsIcon",
<LabsSettingsTab />,
<LabsUserSettingsTab />,
));
}
tabs.push(new Tab(
_td("Help & About"),
"mx_UserSettingsDialog_helpIcon",
<HelpSettingsTab closeSettingsFn={this.props.onFinished} />,
<HelpUserSettingsTab closeSettingsFn={this.props.onFinished} />,
));
return tabs;

View file

@ -20,7 +20,7 @@ import PropTypes from 'prop-types';
import dis from '../../../dispatcher';
import {wantsDateSeparator} from '../../../DateUtils';
import {MatrixEvent, MatrixClient} from 'matrix-js-sdk';
import {makeEventPermalink, makeUserPermalink} from "../../../matrix-to";
import {makeUserPermalink} from "../../../matrix-to";
import SettingsStore from "../../../settings/SettingsStore";
// This component does no cycle detection, simply because the only way to make such a cycle would be to
@ -32,6 +32,7 @@ export default class ReplyThread extends React.Component {
parentEv: PropTypes.instanceOf(MatrixEvent),
// called when the ReplyThread contents has changed, including EventTiles thereof
onWidgetLoad: PropTypes.func.isRequired,
permalinkCreator: PropTypes.object.isRequired,
};
static contextTypes = {
@ -85,7 +86,7 @@ export default class ReplyThread extends React.Component {
}
// Part of Replies fallback support
static getNestedReplyText(ev) {
static getNestedReplyText(ev, permalinkCreator) {
if (!ev) return null;
let {body, formatted_body: html} = ev.getContent();
@ -94,7 +95,7 @@ export default class ReplyThread extends React.Component {
if (html) html = this.stripHTMLReply(html);
}
const evLink = makeEventPermalink(ev.getRoomId(), ev.getId());
const evLink = permalinkCreator.forEvent(ev.getId());
const userLink = makeUserPermalink(ev.getSender());
const mxid = ev.getSender();
@ -159,11 +160,12 @@ export default class ReplyThread extends React.Component {
};
}
static makeThread(parentEv, onWidgetLoad, ref) {
static makeThread(parentEv, onWidgetLoad, permalinkCreator, ref) {
if (!ReplyThread.getParentEventId(parentEv)) {
return <div />;
}
return <ReplyThread parentEv={parentEv} onWidgetLoad={onWidgetLoad} ref={ref} />;
return <ReplyThread parentEv={parentEv} onWidgetLoad={onWidgetLoad}
ref={ref} permalinkCreator={permalinkCreator} />;
}
componentWillMount() {
@ -294,6 +296,7 @@ export default class ReplyThread extends React.Component {
<EventTile mxEvent={ev}
tileShape="reply"
onWidgetLoad={this.props.onWidgetLoad}
permalinkCreator={this.props.permalinkCreator}
isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")} />
</blockquote>;
});

View file

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

View file

@ -33,6 +33,7 @@ const GroupTile = React.createClass({
showDescription: PropTypes.bool,
// Height of the group avatar in pixels
avatarHeight: PropTypes.number,
draggable: PropTypes.bool,
},
contextTypes: {
@ -49,6 +50,7 @@ const GroupTile = React.createClass({
return {
showDescription: true,
avatarHeight: 50,
draggable: true,
};
},
@ -78,54 +80,54 @@ const GroupTile = React.createClass({
<div className="mx_GroupTile_desc">{ profile.shortDescription }</div> :
<div />;
const httpUrl = profile.avatarUrl ? this.context.matrixClient.mxcUrlToHttp(
profile.avatarUrl, avatarHeight, avatarHeight, "crop",
) : null;
profile.avatarUrl, avatarHeight, avatarHeight, "crop") : null;
let avatarElement = (
<div className="mx_GroupTile_avatar">
<BaseAvatar
name={name}
idName={this.props.groupId}
url={httpUrl}
width={avatarHeight}
height={avatarHeight} />
</div>
);
if (this.props.draggable) {
const avatarClone = avatarElement;
avatarElement = (
<Droppable droppableId="my-groups-droppable" type="draggable-TagTile">
{ (droppableProvided, droppableSnapshot) => (
<div ref={droppableProvided.innerRef}>
<Draggable
key={"GroupTile " + this.props.groupId}
draggableId={"GroupTile " + this.props.groupId}
index={this.props.groupId}
type="draggable-TagTile"
>
{ (provided, snapshot) => (
<div>
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
{avatarClone}
</div>
{ /* Instead of a blank placeholder, use a copy of the avatar itself. */ }
{ provided.placeholder ? avatarClone : <div /> }
</div>
) }
</Draggable>
</div>
) }
</Droppable>
);
}
// XXX: Use onMouseDown as a workaround for https://github.com/atlassian/react-beautiful-dnd/issues/273
// instead of onClick. Otherwise we experience https://github.com/vector-im/riot-web/issues/6156
return <AccessibleButton className="mx_GroupTile" onMouseDown={this.onMouseDown} onClick={nop}>
<Droppable droppableId="my-groups-droppable" type="draggable-TagTile">
{ (droppableProvided, droppableSnapshot) => (
<div ref={droppableProvided.innerRef}>
<Draggable
key={"GroupTile " + this.props.groupId}
draggableId={"GroupTile " + this.props.groupId}
index={this.props.groupId}
type="draggable-TagTile"
>
{ (provided, snapshot) => (
<div>
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<div className="mx_GroupTile_avatar">
<BaseAvatar
name={name}
idName={this.props.groupId}
url={httpUrl}
width={avatarHeight}
height={avatarHeight} />
</div>
</div>
{ /* Instead of a blank placeholder, use a copy of the avatar itself. */ }
{ provided.placeholder ?
<div className="mx_GroupTile_avatar">
<BaseAvatar
name={name}
idName={this.props.groupId}
url={httpUrl}
width={avatarHeight}
height={avatarHeight} />
</div> :
<div />
}
</div>
) }
</Draggable>
</div>
) }
</Droppable>
{ avatarElement }
<div className="mx_GroupTile_profile">
<div className="mx_GroupTile_name">{ name }</div>
{ descElement }

View file

@ -18,8 +18,9 @@ import React from 'react';
import PropTypes from 'prop-types';
import dis from '../../../dispatcher';
import { makeEventPermalink } from '../../../matrix-to';
import { RoomPermalinkCreator } from '../../../matrix-to';
import { _t } from '../../../languageHandler';
import MatrixClientPeg from '../../../MatrixClientPeg';
module.exports = React.createClass({
displayName: 'RoomCreate',
@ -47,13 +48,17 @@ module.exports = React.createClass({
if (predecessor === undefined) {
return <div />; // We should never have been instaniated in this case
}
const prevRoom = MatrixClientPeg.get().getRoom(predecessor['room_id']);
const permalinkCreator = new RoomPermalinkCreator(prevRoom);
permalinkCreator.load();
const predecessorPermalink = permalinkCreator.forEvent(predecessor['event_id']);
return <div className="mx_CreateEvent">
<img className="mx_CreateEvent_image" src={require("../../../../res/img/room-continuation.svg")} />
<div className="mx_CreateEvent_header">
{_t("This room is a continuation of another conversation.")}
</div>
<a className="mx_CreateEvent_link"
href={makeEventPermalink(predecessor['room_id'], predecessor['event_id'])}
href={predecessorPermalink}
onClick={this._onLinkClicked}
>
{_t("Click here to see older messages.")}

View file

@ -32,7 +32,6 @@ import withMatrixClient from '../../../wrappers/withMatrixClient';
const ContextualMenu = require('../../structures/ContextualMenu');
import dis from '../../../dispatcher';
import {makeEventPermalink} from "../../../matrix-to";
import SettingsStore from "../../../settings/SettingsStore";
import {EventStatus} from 'matrix-js-sdk';
@ -321,14 +320,18 @@ module.exports = withMatrixClient(React.createClass({
const {tile, replyThread} = this.refs;
let e2eInfoCallback = null;
if (this.props.mxEvent.isEncrypted()) e2eInfoCallback = () => this.onCryptoClicked();
ContextualMenu.createMenu(MessageContextMenu, {
chevronOffset: 10,
mxEvent: this.props.mxEvent,
left: x,
top: y,
permalinkCreator: this.props.permalinkCreator,
eventTileOps: tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined,
collapseReplyThread: replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined,
e2eInfoCallback: () => this.onCryptoClicked(),
e2eInfoCallback: e2eInfoCallback,
onFinished: function() {
self.setState({menu: false});
},
@ -541,7 +544,10 @@ module.exports = withMatrixClient(React.createClass({
mx_EventTile_redacted: isRedacted,
});
const permalink = makeEventPermalink(this.props.mxEvent.getRoomId(), this.props.mxEvent.getId());
let permalink = "#";
if (this.props.permalinkCreator) {
permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId());
}
const readAvatars = this.getReadAvatars();
@ -694,6 +700,15 @@ module.exports = withMatrixClient(React.createClass({
case 'reply':
case 'reply_preview': {
let thread;
if (this.props.tileShape === 'reply_preview') {
thread = ReplyThread.makeThread(
this.props.mxEvent,
this.props.onWidgetLoad,
this.props.permalinkCreator,
'replyThread',
);
}
return (
<div className={classes}>
{ avatar }
@ -703,10 +718,7 @@ module.exports = withMatrixClient(React.createClass({
{ timestamp }
</a>
{ this._renderE2EPadlock() }
{
this.props.tileShape === 'reply_preview'
&& ReplyThread.makeThread(this.props.mxEvent, this.props.onWidgetLoad, 'replyThread')
}
{ thread }
<EventTileType ref="tile"
mxEvent={this.props.mxEvent}
highlights={this.props.highlights}
@ -718,6 +730,12 @@ module.exports = withMatrixClient(React.createClass({
);
}
default: {
const thread = ReplyThread.makeThread(
this.props.mxEvent,
this.props.onWidgetLoad,
this.props.permalinkCreator,
'replyThread',
);
return (
<div className={classes}>
<div className="mx_EventTile_msgOption">
@ -729,7 +747,7 @@ module.exports = withMatrixClient(React.createClass({
{ timestamp }
</a>
{ this._renderE2EPadlock() }
{ ReplyThread.makeThread(this.props.mxEvent, this.props.onWidgetLoad, 'replyThread') }
{ thread }
<EventTileType ref="tile"
mxEvent={this.props.mxEvent}
highlights={this.props.highlights}

View file

@ -339,12 +339,11 @@ module.exports = React.createClass({
return nameA.localeCompare(nameB);
},
onSearchQueryChanged: function(ev) {
const q = ev.target.value;
onSearchQueryChanged: function(searchQuery) {
this.setState({
searchQuery: q,
filteredJoinedMembers: this._filterMembers(this.state.members, 'join', q),
filteredInvitedMembers: this._filterMembers(this.state.members, 'invite', q),
searchQuery,
filteredJoinedMembers: this._filterMembers(this.state.members, 'join', searchQuery),
filteredInvitedMembers: this._filterMembers(this.state.members, 'invite', searchQuery),
});
},
@ -438,6 +437,7 @@ module.exports = React.createClass({
return <div className="mx_MemberList"><Spinner /></div>;
}
const SearchBox = sdk.getComponent('structures.SearchBox');
const TruncatedList = sdk.getComponent("elements.TruncatedList");
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
@ -445,7 +445,6 @@ module.exports = React.createClass({
const room = cli.getRoom(this.props.roomId);
let inviteButton;
if (room && room.getMyMembership() === 'join') {
const TintableSvg = sdk.getComponent("elements.TintableSvg");
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
inviteButton =
<AccessibleButton className="mx_MemberList_invite" onClick={this.onInviteButtonClick}>
@ -477,9 +476,10 @@ module.exports = React.createClass({
{ invitedSection }
</div>
</GeminiScrollbarWrapper>
<input className="mx_MemberList_query mx_textinput_icon mx_textinput_search" id="mx_MemberList_query" type="text"
onChange={this.onSearchQueryChanged} value={this.state.searchQuery}
placeholder={_t('Filter room members')} />
<SearchBox className="mx_MemberList_query mx_textinput_icon mx_textinput_search"
placeholder={ _t('Filter room members') }
onSearch={ this.onSearchQueryChanged } />
</div>
);
},

View file

@ -363,34 +363,6 @@ export default class MessageComposer extends React.Component {
</AccessibleButton>;
}
// TODO: Remove temporary logging for riot-web#7838
// Note: we rip apart the power level event ourselves because we don't want to
// log too much data about it - just the bits we care about. Many of the variables
// logged here are to help figure out where in the stack the 'cannot post in room'
// warning is coming from. This means logging various numbers from the PL event to
// verify RoomState._maySendEventOfType is doing the right thing.
const room = this.props.room;
const plEvent = room.currentState.getStateEvents('m.room.power_levels', '');
let plEventString = "<no power level event>";
if (plEvent) {
const content = plEvent.getContent();
if (!content) {
plEventString = "<no event content>";
} else {
const stringifyFalsey = (v) => v === null ? '<null>' : (v === undefined ? '<undefined>' : v);
const actualUserPl = stringifyFalsey(content.users ? content.users[room.myUserId] : "<no users in content>");
const usersPl = stringifyFalsey(content.users_default);
const actualEventPl = stringifyFalsey(content.events ? content.events['m.room.message'] : "<no events in content>");
const eventPl = stringifyFalsey(content.events_default);
plEventString = `actualUserPl=${actualUserPl} defaultUserPl=${usersPl} actualEventPl=${actualEventPl} defaultEventPl=${eventPl}`;
}
}
console.log(
`[riot-web#7838] renderComposer() hasTombstone=${!!this.state.tombstone} maySendMessage=${room.maySendMessage()}` +
` myMembership=${room.getMyMembership()} maySendEvent=${room.currentState.maySendEvent('m.room.message', room.myUserId)}` +
` myUserId=${room.myUserId} roomId=${room.roomId} hasPlEvent=${!!plEvent} powerLevels='${plEventString}'`
);
if (!this.state.tombstone && this.state.canSendMessages) {
// This also currently includes the call buttons. Really we should
// check separately for whether we can call, but this is slightly
@ -444,7 +416,8 @@ export default class MessageComposer extends React.Component {
room={this.props.room}
placeholder={placeholderText}
onFilesPasted={this.uploadFiles}
onInputStateChanged={this.onInputStateChanged} />,
onInputStateChanged={this.onInputStateChanged}
permalinkCreator={this.props.permalinkCreator} />,
formattingButton,
stickerpickerButton,
uploadButton,
@ -470,8 +443,6 @@ export default class MessageComposer extends React.Component {
</div>
</div>);
} else {
// TODO: Remove temporary logging for riot-web#7838
console.log("[riot-web#7838] Falling back to showing cannot post in room error");
controls.push(
<div key="controls_error" className="mx_MessageComposer_noperm_error">
{ _t('You do not have permission to post to this room') }

View file

@ -1195,7 +1195,7 @@ export default class MessageComposerInput extends React.Component {
// Part of Replies fallback support - prepend the text we're sending
// with the text we're replying to
const nestedReply = ReplyThread.getNestedReplyText(replyingToEv);
const nestedReply = ReplyThread.getNestedReplyText(replyingToEv, this.props.permalinkCreator);
if (nestedReply) {
if (content.formatted_body) {
content.formatted_body = nestedReply.html + content.formatted_body;

View file

@ -56,6 +56,7 @@ module.exports = React.createClass({
}
if (EventTile.haveTileForEvent(ev)) {
ret.push(<EventTile key={eventId+"+"+j} mxEvent={ev} contextual={contextual} highlights={highlights}
permalinkCreator={this.props.permalinkCreator}
highlightLink={this.props.resultLink}
onWidgetLoad={this.props.onWidgetLoad} />);
}

View file

@ -170,6 +170,7 @@ module.exports = React.createClass({
width={24}
height={24}
resizeMethod="crop"
viewUserOnClick={true}
/>
);
});

View file

@ -16,11 +16,11 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import {_t} from "../../../../languageHandler";
import MatrixClientPeg from "../../../../MatrixClientPeg";
import sdk from "../../../../index";
import AccessibleButton from "../../elements/AccessibleButton";
import Modal from "../../../../Modal";
import {_t} from "../../../../../languageHandler";
import MatrixClientPeg from "../../../../../MatrixClientPeg";
import sdk from "../../../../..";
import AccessibleButton from "../../../elements/AccessibleButton";
import Modal from "../../../../../Modal";
export default class AdvancedRoomSettingsTab extends React.Component {
static propTypes = {

View file

@ -16,14 +16,14 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import {_t} from "../../../../languageHandler";
import RoomProfileSettings from "../../room_settings/RoomProfileSettings";
import MatrixClientPeg from "../../../../MatrixClientPeg";
import sdk from "../../../../index";
import AccessibleButton from "../../elements/AccessibleButton";
import {_t} from "../../../../../languageHandler";
import RoomProfileSettings from "../../../room_settings/RoomProfileSettings";
import MatrixClientPeg from "../../../../../MatrixClientPeg";
import sdk from "../../../../..";
import AccessibleButton from "../../../elements/AccessibleButton";
import {MatrixClient} from "matrix-js-sdk";
import dis from "../../../../dispatcher";
import LabelledToggleSwitch from "../../elements/LabelledToggleSwitch";
import dis from "../../../../../dispatcher";
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
export default class GeneralRoomSettingsTab extends React.Component {
static childContextTypes = {

View file

@ -16,11 +16,11 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import {_t, _td} from "../../../../languageHandler";
import MatrixClientPeg from "../../../../MatrixClientPeg";
import sdk from "../../../../index";
import AccessibleButton from "../../elements/AccessibleButton";
import Modal from "../../../../Modal";
import {_t, _td} from "../../../../../languageHandler";
import MatrixClientPeg from "../../../../../MatrixClientPeg";
import sdk from "../../../../..";
import AccessibleButton from "../../../elements/AccessibleButton";
import Modal from "../../../../../Modal";
const plEventsToLabels = {
// These will be translated for us later.

View file

@ -16,11 +16,11 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import {_t} from "../../../../languageHandler";
import MatrixClientPeg from "../../../../MatrixClientPeg";
import sdk from "../../../../index";
import LabelledToggleSwitch from "../../elements/LabelledToggleSwitch";
import {SettingLevel} from "../../../../settings/SettingsStore";
import {_t} from "../../../../../languageHandler";
import MatrixClientPeg from "../../../../../MatrixClientPeg";
import sdk from "../../../../..";
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
import {SettingLevel} from "../../../../../settings/SettingsStore";
export default class SecurityRoomSettingsTab extends React.Component {
static propTypes = {
@ -188,7 +188,7 @@ export default class SecurityRoomSettingsTab extends React.Component {
if (joinRule !== 'public' && guestAccess === 'forbidden') {
guestWarning = (
<div className='mx_SecurityRoomSettingsTab_warning'>
<img src={require("../../../../../res/img/warning.svg")} width={15} height={15} />
<img src={require("../../../../../../res/img/warning.svg")} width={15} height={15} />
<span>
{_t("Guests cannot join this room even if explicitly invited.")}&nbsp;
<a href="" onClick={this._fixGuestAccess}>{_t("Click here to fix")}</a>
@ -201,7 +201,7 @@ export default class SecurityRoomSettingsTab extends React.Component {
if (joinRule === 'public' && !hasAliases) {
aliasWarning = (
<div className='mx_SecurityRoomSettingsTab_warning'>
<img src={require("../../../../../res/img/warning.svg")} width={15} height={15} />
<img src={require("../../../../../../res/img/warning.svg")} width={15} height={15} />
<span>
{_t("To link to this room, please add an alias.")}
</span>

View file

@ -15,14 +15,13 @@ limitations under the License.
*/
import React from 'react';
import {_t} from "../../../../languageHandler";
import {DragDropContext} from "react-beautiful-dnd";
import GroupUserSettings from "../../groups/GroupUserSettings";
import MatrixClientPeg from "../../../../MatrixClientPeg";
import {_t} from "../../../../../languageHandler";
import GroupUserSettings from "../../../groups/GroupUserSettings";
import MatrixClientPeg from "../../../../../MatrixClientPeg";
import PropTypes from "prop-types";
import {MatrixClient} from "matrix-js-sdk";
export default class FlairSettingsTab extends React.Component {
export default class FlairUserSettingsTab extends React.Component {
static childContextTypes = {
matrixClient: PropTypes.instanceOf(MatrixClient),
};
@ -42,9 +41,7 @@ export default class FlairSettingsTab extends React.Component {
<div className="mx_SettingsTab">
<span className="mx_SettingsTab_heading">{_t("Flair")}</span>
<div className="mx_SettingsTab_section">
<DragDropContext>
<GroupUserSettings />
</DragDropContext>
<GroupUserSettings />
</div>
</div>
);

View file

@ -15,21 +15,21 @@ limitations under the License.
*/
import React from 'react';
import {_t} from "../../../../languageHandler";
import ProfileSettings from "../ProfileSettings";
import EmailAddresses from "../EmailAddresses";
import PhoneNumbers from "../PhoneNumbers";
import Field from "../../elements/Field";
import * as languageHandler from "../../../../languageHandler";
import {SettingLevel} from "../../../../settings/SettingsStore";
import SettingsStore from "../../../../settings/SettingsStore";
import LanguageDropdown from "../../elements/LanguageDropdown";
import AccessibleButton from "../../elements/AccessibleButton";
import DeactivateAccountDialog from "../../dialogs/DeactivateAccountDialog";
const PlatformPeg = require("../../../../PlatformPeg");
const sdk = require('../../../../index');
const Modal = require("../../../../Modal");
const dis = require("../../../../dispatcher");
import {_t} from "../../../../../languageHandler";
import ProfileSettings from "../../ProfileSettings";
import EmailAddresses from "../../EmailAddresses";
import PhoneNumbers from "../../PhoneNumbers";
import Field from "../../../elements/Field";
import * as languageHandler from "../../../../../languageHandler";
import {SettingLevel} from "../../../../../settings/SettingsStore";
import SettingsStore from "../../../../../settings/SettingsStore";
import LanguageDropdown from "../../../elements/LanguageDropdown";
import AccessibleButton from "../../../elements/AccessibleButton";
import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog";
const PlatformPeg = require("../../../../../PlatformPeg");
const sdk = require('../../../../..');
const Modal = require("../../../../../Modal");
const dis = require("../../../../../dispatcher");
export default class GeneralUserSettingsTab extends React.Component {
constructor() {

View file

@ -16,15 +16,15 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import {_t, getCurrentLanguage} from "../../../../languageHandler";
import MatrixClientPeg from "../../../../MatrixClientPeg";
import AccessibleButton from "../../elements/AccessibleButton";
import SdkConfig from "../../../../SdkConfig";
import createRoom from "../../../../createRoom";
const packageJson = require('../../../../../package.json');
const Modal = require("../../../../Modal");
const sdk = require("../../../../index");
const PlatformPeg = require("../../../../PlatformPeg");
import {_t, getCurrentLanguage} from "../../../../../languageHandler";
import MatrixClientPeg from "../../../../../MatrixClientPeg";
import AccessibleButton from "../../../elements/AccessibleButton";
import SdkConfig from "../../../../../SdkConfig";
import createRoom from "../../../../../createRoom";
const packageJson = require('../../../../../../package.json');
const Modal = require("../../../../../Modal");
const sdk = require("../../../../..");
const PlatformPeg = require("../../../../../PlatformPeg");
// if this looks like a release, use the 'version' from package.json; else use
// the git sha. Prepend version with v, to look like riot-web version
@ -45,7 +45,7 @@ const ghVersionLabel = function(repo, token='') {
return <a target="_blank" rel="noopener" href={url}>{ token }</a>;
};
export default class HelpSettingsTab extends React.Component {
export default class HelpUserSettingsTab extends React.Component {
static propTypes = {
closeSettingsFn: PropTypes.func.isRequired,
};
@ -117,7 +117,7 @@ export default class HelpSettingsTab extends React.Component {
}
return (
<div className='mx_SettingsTab_section mx_HelpSettingsTab_versions'>
<div className='mx_SettingsTab_section mx_HelpUserSettingsTab_versions'>
<span className='mx_SettingsTab_subheading'>{_t("Legal")}</span>
<div className='mx_SettingsTab_subsectionText'>
{legalLinks}
@ -190,7 +190,7 @@ export default class HelpSettingsTab extends React.Component {
}
return (
<div className="mx_SettingsTab mx_HelpSettingsTab">
<div className="mx_SettingsTab mx_HelpUserSettingsTab">
<div className="mx_SettingsTab_heading">{_t("Help & About")}</div>
<div className="mx_SettingsTab_section">
<span className='mx_SettingsTab_subheading'>{_t('Bug reporting')}</span>
@ -203,12 +203,12 @@ export default class HelpSettingsTab extends React.Component {
"other users. They do not contain messages.",
)
}
<div className='mx_HelpSettingsTab_debugButton'>
<div className='mx_HelpUserSettingsTab_debugButton'>
<AccessibleButton onClick={this._onBugReport} kind='primary'>
{_t("Submit debug logs")}
</AccessibleButton>
</div>
<div className='mx_HelpSettingsTab_debugButton'>
<div className='mx_HelpUserSettingsTab_debugButton'>
<AccessibleButton onClick={this._onClearCacheAndReload} kind='danger'>
{_t("Clear Cache and Reload")}
</AccessibleButton>
@ -221,7 +221,7 @@ export default class HelpSettingsTab extends React.Component {
{faqText}
</div>
</div>
<div className='mx_SettingsTab_section mx_HelpSettingsTab_versions'>
<div className='mx_SettingsTab_section mx_HelpUserSettingsTab_versions'>
<span className='mx_SettingsTab_subheading'>{_t("Versions")}</span>
<div className='mx_SettingsTab_subsectionText'>
{_t("matrix-react-sdk version:")} {reactSdkVersion}<br />
@ -232,7 +232,7 @@ export default class HelpSettingsTab extends React.Component {
</div>
{this._renderLegal()}
{this._renderCredits()}
<div className='mx_SettingsTab_section mx_HelpSettingsTab_versions'>
<div className='mx_SettingsTab_section mx_HelpUserSettingsTab_versions'>
<span className='mx_SettingsTab_subheading'>{_t("Advanced")}</span>
<div className='mx_SettingsTab_subsectionText'>
{_t("Homeserver is")} {MatrixClientPeg.get().getHomeserverUrl()}<br />

View file

@ -15,11 +15,11 @@ limitations under the License.
*/
import React from 'react';
import {_t} from "../../../../languageHandler";
import {_t} from "../../../../../languageHandler";
import PropTypes from "prop-types";
import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore";
import LabelledToggleSwitch from "../../elements/LabelledToggleSwitch";
const sdk = require("../../../../index");
import SettingsStore, {SettingLevel} from "../../../../../settings/SettingsStore";
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
const sdk = require("../../../../..");
export class LabsSettingToggle extends React.Component {
static propTypes = {
@ -38,7 +38,7 @@ export class LabsSettingToggle extends React.Component {
}
}
export default class LabsSettingsTab extends React.Component {
export default class LabsUserSettingsTab extends React.Component {
constructor() {
super();
}

View file

@ -15,10 +15,10 @@ limitations under the License.
*/
import React from 'react';
import {_t} from "../../../../languageHandler";
const sdk = require("../../../../index");
import {_t} from "../../../../../languageHandler";
const sdk = require("../../../../..");
export default class NotificationSettingsTab extends React.Component {
export default class NotificationUserSettingsTab extends React.Component {
constructor() {
super();
}
@ -26,7 +26,7 @@ export default class NotificationSettingsTab extends React.Component {
render() {
const Notifications = sdk.getComponent("views.settings.Notifications");
return (
<div className="mx_SettingsTab mx_NotificationSettingsTab">
<div className="mx_SettingsTab mx_NotificationUserSettingsTab">
<div className="mx_SettingsTab_heading">{_t("Notifications")}</div>
<div className="mx_SettingsTab_section mx_SettingsTab_subsectionText">
<Notifications />

View file

@ -15,15 +15,15 @@ limitations under the License.
*/
import React from 'react';
import {_t} from "../../../../languageHandler";
import {SettingLevel} from "../../../../settings/SettingsStore";
import LabelledToggleSwitch from "../../elements/LabelledToggleSwitch";
import SettingsStore from "../../../../settings/SettingsStore";
import Field from "../../elements/Field";
const sdk = require("../../../../index");
const PlatformPeg = require("../../../../PlatformPeg");
import {_t} from "../../../../../languageHandler";
import {SettingLevel} from "../../../../../settings/SettingsStore";
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
import SettingsStore from "../../../../../settings/SettingsStore";
import Field from "../../../elements/Field";
const sdk = require("../../../../..");
const PlatformPeg = require("../../../../../PlatformPeg");
export default class PreferencesSettingsTab extends React.Component {
export default class PreferencesUserSettingsTab extends React.Component {
static COMPOSER_SETTINGS = [
'MessageComposerInput.autoReplaceEmoji',
'MessageComposerInput.suggestEmoji',
@ -44,6 +44,10 @@ export default class PreferencesSettingsTab extends React.Component {
'showDisplaynameChanges',
];
static ROOM_LIST_SETTINGS = [
'RoomList.orderByImportance',
];
static ADVANCED_SETTINGS = [
'alwaysShowEncryptionIcons',
'Pill.shouldShowPillAvatar',
@ -59,24 +63,39 @@ export default class PreferencesSettingsTab extends React.Component {
this.state = {
autoLaunch: false,
autoLaunchSupported: false,
minimizeToTray: true,
minimizeToTraySupported: false,
};
}
async componentWillMount(): void {
const autoLaunchSupported = await PlatformPeg.get().supportsAutoLaunch();
const platform = PlatformPeg.get();
const autoLaunchSupported = await platform.supportsAutoLaunch();
let autoLaunch = false;
if (autoLaunchSupported) {
autoLaunch = await PlatformPeg.get().getAutoLaunchEnabled();
autoLaunch = await platform.getAutoLaunchEnabled();
}
this.setState({autoLaunch, autoLaunchSupported});
const minimizeToTraySupported = await platform.supportsMinimizeToTray();
let minimizeToTray = true;
if (minimizeToTraySupported) {
minimizeToTray = await platform.getMinimizeToTrayEnabled();
}
this.setState({autoLaunch, autoLaunchSupported, minimizeToTraySupported, minimizeToTray});
}
_onAutoLaunchChange = (checked) => {
PlatformPeg.get().setAutoLaunchEnabled(checked).then(() => this.setState({autoLaunch: checked}));
};
_onMinimizeToTrayChange = (checked) => {
PlatformPeg.get().setMinimizeToTrayEnabled(checked).then(() => this.setState({minimizeToTray: checked}));
};
_onAutocompleteDelayChange = (e) => {
SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.value);
};
@ -94,18 +113,29 @@ export default class PreferencesSettingsTab extends React.Component {
label={_t('Start automatically after system login')} />;
}
let minimizeToTrayOption = null;
if (this.state.minimizeToTraySupported) {
minimizeToTrayOption = <LabelledToggleSwitch value={this.state.minimizeToTray}
onChange={this._onMinimizeToTrayChange}
label={_t('Close button should minimize window to tray')} />;
}
return (
<div className="mx_SettingsTab mx_PreferencesSettingsTab">
<div className="mx_SettingsTab mx_PreferencesUserSettingsTab">
<div className="mx_SettingsTab_heading">{_t("Preferences")}</div>
<div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Composer")}</span>
{this._renderGroup(PreferencesSettingsTab.COMPOSER_SETTINGS)}
{this._renderGroup(PreferencesUserSettingsTab.COMPOSER_SETTINGS)}
<span className="mx_SettingsTab_subheading">{_t("Timeline")}</span>
{this._renderGroup(PreferencesSettingsTab.TIMELINE_SETTINGS)}
{this._renderGroup(PreferencesUserSettingsTab.TIMELINE_SETTINGS)}
<span className="mx_SettingsTab_subheading">{_t("Room list")}</span>
{this._renderGroup(PreferencesUserSettingsTab.ROOM_LIST_SETTINGS)}
<span className="mx_SettingsTab_subheading">{_t("Advanced")}</span>
{this._renderGroup(PreferencesSettingsTab.ADVANCED_SETTINGS)}
{this._renderGroup(PreferencesUserSettingsTab.ADVANCED_SETTINGS)}
{minimizeToTrayOption}
{autoLaunchOption}
<Field id={"autocompleteDelay"} label={_t('Autocomplete delay (ms)')} type='number'
value={SettingsStore.getValueAt(SettingLevel.DEVICE, 'autocompleteDelay')}

View file

@ -16,15 +16,15 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import {_t} from "../../../../languageHandler";
import {SettingLevel} from "../../../../settings/SettingsStore";
import MatrixClientPeg from "../../../../MatrixClientPeg";
import * as FormattingUtils from "../../../../utils/FormattingUtils";
import AccessibleButton from "../../elements/AccessibleButton";
import Analytics from "../../../../Analytics";
import {_t} from "../../../../../languageHandler";
import {SettingLevel} from "../../../../../settings/SettingsStore";
import MatrixClientPeg from "../../../../../MatrixClientPeg";
import * as FormattingUtils from "../../../../../utils/FormattingUtils";
import AccessibleButton from "../../../elements/AccessibleButton";
import Analytics from "../../../../../Analytics";
import Promise from "bluebird";
import Modal from "../../../../Modal";
import sdk from "../../../../index";
import Modal from "../../../../../Modal";
import sdk from "../../../../..";
export class IgnoredUser extends React.Component {
static propTypes = {
@ -38,7 +38,7 @@ export class IgnoredUser extends React.Component {
render() {
return (
<div className='mx_SecuritySettingsTab_ignoredUser'>
<div className='mx_SecurityUserSettingsTab_ignoredUser'>
<AccessibleButton onClick={this._onUnignoreClicked} kind='primary_sm'>
{_t('Unignore')}
</AccessibleButton>
@ -48,7 +48,7 @@ export class IgnoredUser extends React.Component {
}
}
export default class SecuritySettingsTab extends React.Component {
export default class SecurityUserSettingsTab extends React.Component {
constructor() {
super();
@ -68,14 +68,14 @@ export default class SecuritySettingsTab extends React.Component {
_onExportE2eKeysClicked = () => {
Modal.createTrackedDialogAsync('Export E2E Keys', '',
import('../../../../async-components/views/dialogs/ExportE2eKeysDialog'),
import('../../../../../async-components/views/dialogs/ExportE2eKeysDialog'),
{matrixClient: MatrixClientPeg.get()},
);
};
_onImportE2eKeysClicked = () => {
Modal.createTrackedDialogAsync('Import E2E Keys', '',
import('../../../../async-components/views/dialogs/ImportE2eKeysDialog'),
import('../../../../../async-components/views/dialogs/ImportE2eKeysDialog'),
{matrixClient: MatrixClientPeg.get()},
);
};
@ -126,7 +126,7 @@ export default class SecuritySettingsTab extends React.Component {
let importExportButtons = null;
if (client.isCryptoEnabled()) {
importExportButtons = (
<div className='mx_SecuritySettingsTab_importExportButtons'>
<div className='mx_SecurityUserSettingsTab_importExportButtons'>
<AccessibleButton kind='primary' onClick={this._onExportE2eKeysClicked}>
{_t("Export E2E room keys")}
</AccessibleButton>
@ -140,7 +140,7 @@ export default class SecuritySettingsTab extends React.Component {
return (
<div className='mx_SettingsTab_section'>
<span className='mx_SettingsTab_subheading'>{_t("Cryptography")}</span>
<ul className='mx_SettingsTab_subsectionText mx_SecuritySettingsTab_deviceInfo'>
<ul className='mx_SettingsTab_subsectionText mx_SecurityUserSettingsTab_deviceInfo'>
<li>
<label>{_t("Device ID:")}</label>
<span><code>{deviceId}</code></span>
@ -207,7 +207,7 @@ export default class SecuritySettingsTab extends React.Component {
);
return (
<div className="mx_SettingsTab mx_SecuritySettingsTab">
<div className="mx_SettingsTab mx_SecurityUserSettingsTab">
<div className="mx_SettingsTab_heading">{_t("Security & Privacy")}</div>
<div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Devices")}</span>

View file

@ -15,16 +15,16 @@ limitations under the License.
*/
import React from 'react';
import {_t} from "../../../../languageHandler";
import CallMediaHandler from "../../../../CallMediaHandler";
import Field from "../../elements/Field";
import AccessibleButton from "../../elements/AccessibleButton";
import {SettingLevel} from "../../../../settings/SettingsStore";
const Modal = require("../../../../Modal");
const sdk = require("../../../../index");
const MatrixClientPeg = require("../../../../MatrixClientPeg");
import {_t} from "../../../../../languageHandler";
import CallMediaHandler from "../../../../../CallMediaHandler";
import Field from "../../../elements/Field";
import AccessibleButton from "../../../elements/AccessibleButton";
import {SettingLevel} from "../../../../../settings/SettingsStore";
const Modal = require("../../../../../Modal");
const sdk = require("../../../../..");
const MatrixClientPeg = require("../../../../../MatrixClientPeg");
export default class VoiceSettingsTab extends React.Component {
export default class VoiceUserSettingsTab extends React.Component {
constructor() {
super();
@ -103,7 +103,7 @@ export default class VoiceSettingsTab extends React.Component {
let webcamDropdown = null;
if (this.state.mediaDevices === false) {
requestButton = (
<div className='mx_VoiceSettingsTab_missingMediaPermissions'>
<div className='mx_VoiceUserSettingsTab_missingMediaPermissions'>
<p>{_t("Missing media permissions, click the button below to request.")}</p>
<AccessibleButton onClick={this._requestMediaPermissions} kind="primary">
{_t("Request media permissions")}
@ -166,7 +166,7 @@ export default class VoiceSettingsTab extends React.Component {
}
return (
<div className="mx_SettingsTab mx_VoiceSettingsTab">
<div className="mx_SettingsTab mx_VoiceUserSettingsTab">
<div className="mx_SettingsTab_heading">{_t("Voice & Video")}</div>
<div className="mx_SettingsTab_section">
{requestButton}