Merge branch 'develop' into new-guest-access

Conflicts:
	src/component-index.js
This commit is contained in:
Luke Barnard 2017-05-11 13:22:30 +01:00
commit 5151264f60
33 changed files with 718 additions and 568 deletions

View file

@ -395,9 +395,10 @@ module.exports = React.createClass({
this.notifyNewScreen('forgot_password');
break;
case 'leave_room':
const roomToLeave = MatrixClientPeg.get().getRoom(payload.room_id);
Modal.createDialog(QuestionDialog, {
title: "Leave room",
description: "Are you sure you want to leave the room?",
description: <span>Are you sure you want to leave the room <i>{roomToLeave.name}</i>?</span>,
onFinished: (should_leave) => {
if (should_leave) {
const d = MatrixClientPeg.get().leave(payload.room_id);

View file

@ -354,7 +354,6 @@ module.exports = React.createClass({
<MemberEventListSummary
key={key}
events={summarisedEvents}
data-scroll-token={eventId}
onToggle={this._onWidgetLoad} // Update scroll state
>
{eventTiles}
@ -473,7 +472,7 @@ module.exports = React.createClass({
ret.push(
<li key={eventId}
ref={this._collectEventNode.bind(this, eventId)}
data-scroll-token={scrollToken}>
data-scroll-tokens={scrollToken}>
<EventTile mxEvent={mxEv} continuation={continuation}
isRedacted={mxEv.isRedacted()}
onWidgetLoad={this._onWidgetLoad}

View file

@ -275,6 +275,7 @@ module.exports = React.createClass({
this._updateConfCallNotification();
window.addEventListener('beforeunload', this.onPageUnload);
window.addEventListener('resize', this.onResize);
this.onResize();
@ -357,6 +358,7 @@ module.exports = React.createClass({
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
}
window.removeEventListener('beforeunload', this.onPageUnload);
window.removeEventListener('resize', this.onResize);
document.removeEventListener("keydown", this.onKeyDown);
@ -369,6 +371,17 @@ module.exports = React.createClass({
// Tinter.tint(); // reset colourscheme
},
onPageUnload(event) {
if (ContentMessages.getCurrentUploads().length > 0) {
return event.returnValue =
'You seem to be uploading files, are you sure you want to quit?';
} else if (this._getCallForRoom() && this.state.callState !== 'ended') {
return event.returnValue =
'You seem to be in a call, are you sure you want to quit?';
}
},
onKeyDown: function(ev) {
let handled = false;
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;

View file

@ -46,9 +46,13 @@ if (DEBUG_SCROLL) {
* It also provides a hook which allows parents to provide more list elements
* when we get close to the start or end of the list.
*
* Each child element should have a 'data-scroll-token'. This token is used to
* serialise the scroll state, and returned as the 'trackedScrollToken'
* attribute by getScrollState().
* Each child element should have a 'data-scroll-tokens'. This string of
* comma-separated tokens may contain a single token or many, where many indicates
* that the element contains elements that have scroll tokens themselves. The first
* token in 'data-scroll-tokens' is used to serialise the scroll state, and returned
* as the 'trackedScrollToken' attribute by getScrollState().
*
* IMPORTANT: INDIVIDUAL TOKENS WITHIN 'data-scroll-tokens' MUST NOT CONTAIN COMMAS.
*
* Some notes about the implementation:
*
@ -349,8 +353,8 @@ module.exports = React.createClass({
// Subtract height of tile as if it were unpaginated
excessHeight -= tile.clientHeight;
// The tile may not have a scroll token, so guard it
if (tile.dataset.scrollToken) {
markerScrollToken = tile.dataset.scrollToken;
if (tile.dataset.scrollTokens) {
markerScrollToken = tile.dataset.scrollTokens.split(',')[0];
}
if (tile.clientHeight > excessHeight) {
break;
@ -419,7 +423,8 @@ module.exports = React.createClass({
* scroll. false if we are tracking a particular child.
*
* string trackedScrollToken: undefined if stuckAtBottom is true; if it is
* false, the data-scroll-token of the child which we are tracking.
* false, the first token in data-scroll-tokens of the child which we are
* tracking.
*
* number pixelOffset: undefined if stuckAtBottom is true; if it is false,
* the number of pixels the bottom of the tracked child is above the
@ -551,8 +556,10 @@ module.exports = React.createClass({
var messages = this.refs.itemlist.children;
for (var i = messages.length-1; i >= 0; --i) {
var m = messages[i];
if (!m.dataset.scrollToken) continue;
if (m.dataset.scrollToken == scrollToken) {
// 'data-scroll-tokens' is a DOMString of comma-separated scroll tokens
// There might only be one scroll token
if (m.dataset.scrollTokens &&
m.dataset.scrollTokens.split(',').indexOf(scrollToken) !== -1) {
node = m;
break;
}
@ -568,7 +575,7 @@ module.exports = React.createClass({
var boundingRect = node.getBoundingClientRect();
var scrollDelta = boundingRect.bottom + pixelOffset - wrapperRect.bottom;
debuglog("ScrollPanel: scrolling to token '" + node.dataset.scrollToken + "'+" +
debuglog("ScrollPanel: scrolling to token '" + scrollToken + "'+" +
pixelOffset + " (delta: "+scrollDelta+")");
if(scrollDelta != 0) {
@ -591,12 +598,12 @@ module.exports = React.createClass({
for (var i = messages.length-1; i >= 0; --i) {
var node = messages[i];
if (!node.dataset.scrollToken) continue;
if (!node.dataset.scrollTokens) continue;
var boundingRect = node.getBoundingClientRect();
newScrollState = {
stuckAtBottom: false,
trackedScrollToken: node.dataset.scrollToken,
trackedScrollToken: node.dataset.scrollTokens.split(',')[0],
pixelOffset: wrapperRect.bottom - boundingRect.bottom,
};
// If the bottom of the panel intersects the ClientRect of node, use this node
@ -608,7 +615,7 @@ module.exports = React.createClass({
break;
}
}
// This is only false if there were no nodes with `node.dataset.scrollToken` set.
// This is only false if there were no nodes with `node.dataset.scrollTokens` set.
if (newScrollState) {
this.scrollState = newScrollState;
debuglog("ScrollPanel: saved scroll state", this.scrollState);

View file

@ -170,7 +170,7 @@ var TimelinePanel = React.createClass({
forwardPaginating: false,
// cache of matrixClient.getSyncState() (but from the 'sync' event)
clientSyncState: null,
clientSyncState: MatrixClientPeg.get().getSyncState(),
};
},
@ -503,7 +503,9 @@ var TimelinePanel = React.createClass({
// This happens on user_activity_end which is delayed, and it's
// very possible have logged out within that timeframe, so check
// we still have a client.
if (!MatrixClientPeg.get()) return;
const cli = MatrixClientPeg.get();
// if no client or client is guest don't send RR
if (!cli || cli.isGuest()) return;
var currentReadUpToEventId = this._getCurrentReadReceipt(true);
var currentReadUpToEventIndex = this._indexForEventId(currentReadUpToEventId);

View file

@ -14,31 +14,40 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var React = require('react');
var ReactDOM = require('react-dom');
var sdk = require('../../index');
var MatrixClientPeg = require("../../MatrixClientPeg");
var PlatformPeg = require("../../PlatformPeg");
var Modal = require('../../Modal');
var dis = require("../../dispatcher");
var q = require('q');
var package_json = require('../../../package.json');
var UserSettingsStore = require('../../UserSettingsStore');
var GeminiScrollbar = require('react-gemini-scrollbar');
var Email = require('../../email');
var AddThreepid = require('../../AddThreepid');
var SdkConfig = require('../../SdkConfig');
const React = require('react');
const ReactDOM = require('react-dom');
const sdk = require('../../index');
const MatrixClientPeg = require("../../MatrixClientPeg");
const PlatformPeg = require("../../PlatformPeg");
const Modal = require('../../Modal');
const dis = require("../../dispatcher");
const q = require('q');
const packageJson = require('../../../package.json');
const UserSettingsStore = require('../../UserSettingsStore');
const GeminiScrollbar = require('react-gemini-scrollbar');
const Email = require('../../email');
const AddThreepid = require('../../AddThreepid');
const SdkConfig = require('../../SdkConfig');
import AccessibleButton from '../views/elements/AccessibleButton';
// 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
const REACT_SDK_VERSION = 'dist' in package_json ? `v${package_json.version}` : package_json.gitHead || '<local>';
const REACT_SDK_VERSION = 'dist' in packageJson ? packageJson.version : packageJson.gitHead || '<local>';
// Simple method to help prettify GH Release Tags and Commit Hashes.
const GHVersionUrl = function(repo, token) {
const uriTail = (token.startsWith('v') && token.includes('.')) ? `releases/tag/${token}` : `commit/${token}`;
return `https://github.com/${repo}/${uriTail}`;
}
const semVerRegex = /^v?(\d+\.\d+\.\d+(?:-rc.+)?)(?:-(?:\d+-g)?([0-9a-fA-F]+))?(?:-dirty)?$/i;
const gHVersionLabel = function(repo, token) {
const match = token.match(semVerRegex);
let url;
if (match && match[1]) { // basic semVer string possibly with commit hash
url = (match.length > 1 && match[2])
? `https://github.com/${repo}/commit/${match[2]}`
: `https://github.com/${repo}/releases/tag/v${match[1]}`;
} else {
url = `https://github.com/${repo}/commit/${token.split('-')[0]}`;
}
return <a href={url}>{token}</a>;
};
// Enumerate some simple 'flip a bit' UI settings (if any).
// 'id' gives the key name in the im.vector.web.settings account data event
@ -50,7 +59,7 @@ const SETTINGS_LABELS = [
},
{
id: 'hideReadReceipts',
label: 'Hide read receipts'
label: 'Hide read receipts',
},
{
id: 'dontSendTypingNotifications',
@ -106,7 +115,7 @@ const THEMES = [
id: 'theme',
label: 'Dark theme',
value: 'dark',
}
},
];
@ -180,7 +189,7 @@ module.exports = React.createClass({
});
this._refreshFromServer();
var syncedSettings = UserSettingsStore.getSyncedSettings();
const syncedSettings = UserSettingsStore.getSyncedSettings();
if (!syncedSettings.theme) {
syncedSettings.theme = 'light';
}
@ -202,16 +211,16 @@ module.exports = React.createClass({
middleOpacity: 1.0,
});
dis.unregister(this.dispatcherRef);
let cli = MatrixClientPeg.get();
const cli = MatrixClientPeg.get();
if (cli) {
cli.removeListener("RoomMember.membership", this._onInviteStateChange);
}
},
_refreshFromServer: function() {
var self = this;
const self = this;
q.all([
UserSettingsStore.loadProfileInfo(), UserSettingsStore.loadThreePids()
UserSettingsStore.loadProfileInfo(), UserSettingsStore.loadThreePids(),
]).done(function(resps) {
self.setState({
avatarUrl: resps[0].avatar_url,
@ -219,7 +228,7 @@ module.exports = React.createClass({
phase: "UserSettings.DISPLAY",
});
}, function(error) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to load user settings: " + error);
Modal.createDialog(ErrorDialog, {
title: "Can't load user settings",
@ -236,7 +245,7 @@ module.exports = React.createClass({
onAvatarPickerClick: function(ev) {
if (MatrixClientPeg.get().isGuest()) {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
const NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register",
description: "Guests can't set avatars. Please register.",
@ -250,8 +259,8 @@ module.exports = React.createClass({
},
onAvatarSelected: function(ev) {
var self = this;
var changeAvatar = this.refs.changeAvatar;
const self = this;
const changeAvatar = this.refs.changeAvatar;
if (!changeAvatar) {
console.error("No ChangeAvatar found to upload image to!");
return;
@ -260,9 +269,9 @@ module.exports = React.createClass({
// dunno if the avatar changed, re-check it.
self._refreshFromServer();
}, function(err) {
var errMsg = (typeof err === "string") ? err : (err.error || "");
// const errMsg = (typeof err === "string") ? err : (err.error || "");
console.error("Failed to set avatar: " + err);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to set avatar",
description: ((err && err.message) ? err.message : "Operation failed"),
@ -271,7 +280,7 @@ module.exports = React.createClass({
},
onLogoutClicked: function(ev) {
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createDialog(QuestionDialog, {
title: "Sign out?",
description:
@ -286,7 +295,7 @@ module.exports = React.createClass({
<button key="export" className="mx_Dialog_primary"
onClick={this._onExportE2eKeysClicked}>
Export E2E room keys
</button>
</button>,
],
onFinished: (confirmed) => {
if (confirmed) {
@ -300,34 +309,33 @@ module.exports = React.createClass({
},
onPasswordChangeError: function(err) {
var errMsg = err.error || "";
let errMsg = err.error || "";
if (err.httpStatus === 403) {
errMsg = "Failed to change password. Is your password correct?";
}
else if (err.httpStatus) {
} else if (err.httpStatus) {
errMsg += ` (HTTP status ${err.httpStatus})`;
}
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to change password: " + errMsg);
Modal.createDialog(ErrorDialog, {
title: "Error",
description: errMsg
description: errMsg,
});
},
onPasswordChanged: function() {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Success",
description: `Your password was successfully changed. You will not
receive push notifications on other devices until you
log back in to them.`
log back in to them.`,
});
},
onUpgradeClicked: function() {
dis.dispatch({
action: "start_upgrade_registration"
action: "start_upgrade_registration",
});
},
@ -341,11 +349,11 @@ module.exports = React.createClass({
},
_addEmail: function() {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
var email_address = this.refs.add_email_input.value;
if (!Email.looksValid(email_address)) {
const emailAddress = this.refs.add_email_input.value;
if (!Email.looksValid(emailAddress)) {
Modal.createDialog(ErrorDialog, {
title: "Invalid Email Address",
description: "This doesn't appear to be a valid email address",
@ -355,7 +363,7 @@ module.exports = React.createClass({
this._addThreepid = new AddThreepid();
// we always bind emails when registering, so let's do the
// same here.
this._addThreepid.addEmailAddress(email_address, true).done(() => {
this._addThreepid.addEmailAddress(emailAddress, true).done(() => {
Modal.createDialog(QuestionDialog, {
title: "Verification Pending",
description: "Please check your email and click on the link it contains. Once this is done, click continue.",
@ -364,7 +372,7 @@ module.exports = React.createClass({
});
}, (err) => {
this.setState({email_add_pending: false});
console.error("Unable to add email address " + email_address + " " + err);
console.error("Unable to add email address " + emailAddress + " " + err);
Modal.createDialog(ErrorDialog, {
title: "Unable to add email address",
description: ((err && err.message) ? err.message : "Operation failed"),
@ -418,9 +426,9 @@ module.exports = React.createClass({
this.setState({email_add_pending: false});
}, (err) => {
this.setState({email_add_pending: false});
if (err.errcode == 'M_THREEPID_AUTH_FAILED') {
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
var message = "Unable to verify email address. ";
if (err.errcode === 'M_THREEPID_AUTH_FAILED') {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
let message = "Unable to verify email address. ";
message += "Please check your email and click on the link it contains. Once this is done, click continue.";
Modal.createDialog(QuestionDialog, {
title: "Verification Pending",
@ -429,7 +437,7 @@ module.exports = React.createClass({
onFinished: this.onEmailDialogFinished,
});
} else {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Unable to verify email address: " + err);
Modal.createDialog(ErrorDialog, {
title: "Unable to verify email address",
@ -469,17 +477,17 @@ module.exports = React.createClass({
_onRejectAllInvitesClicked: function(rooms, ev) {
this.setState({
rejectingInvites: true
rejectingInvites: true,
});
// reject the invites
let promises = rooms.map((room) => {
const promises = rooms.map((room) => {
return MatrixClientPeg.get().leave(room.roomId);
});
// purposefully drop errors to the floor: we'll just have a non-zero number on the UI
// after trying to reject all the invites.
q.allSettled(promises).then(() => {
this.setState({
rejectingInvites: false
rejectingInvites: false,
});
}).done();
},
@ -492,7 +500,7 @@ module.exports = React.createClass({
}, "e2e-export");
}, {
matrixClient: MatrixClientPeg.get(),
}
},
);
},
@ -504,7 +512,7 @@ module.exports = React.createClass({
}, "e2e-export");
}, {
matrixClient: MatrixClientPeg.get(),
}
},
);
},
@ -530,8 +538,6 @@ module.exports = React.createClass({
},
_renderUserInterfaceSettings: function() {
var client = MatrixClientPeg.get();
return (
<div>
<h3>User Interface</h3>
@ -549,7 +555,7 @@ module.exports = React.createClass({
<input id="urlPreviewsDisabled"
type="checkbox"
defaultChecked={ UserSettingsStore.getUrlPreviewsDisabled() }
onChange={ e => UserSettingsStore.setUrlPreviewsDisabled(e.target.checked) }
onChange={ (e) => UserSettingsStore.setUrlPreviewsDisabled(e.target.checked) }
/>
<label htmlFor="urlPreviewsDisabled">
Disable inline URL previews by default
@ -562,7 +568,7 @@ module.exports = React.createClass({
<input id={ setting.id }
type="checkbox"
defaultChecked={ this._syncedSettings[setting.id] }
onChange={ e => UserSettingsStore.setSyncedSetting(setting.id, e.target.checked) }
onChange={ (e) => UserSettingsStore.setSyncedSetting(setting.id, e.target.checked) }
/>
<label htmlFor={ setting.id }>
{ setting.label }
@ -577,7 +583,7 @@ module.exports = React.createClass({
name={ setting.id }
value={ setting.value }
defaultChecked={ this._syncedSettings[setting.id] === setting.value }
onChange={ e => {
onChange={ (e) => {
if (e.target.checked) {
UserSettingsStore.setSyncedSetting(setting.id, setting.value);
}
@ -639,8 +645,8 @@ module.exports = React.createClass({
type="checkbox"
defaultChecked={ this._localSettings[setting.id] }
onChange={
e => {
UserSettingsStore.setLocalSetting(setting.id, e.target.checked)
(e) => {
UserSettingsStore.setLocalSetting(setting.id, e.target.checked);
if (setting.id === 'blacklistUnverifiedDevices') { // XXX: this is a bit ugly
client.setGlobalBlacklistUnverifiedDevices(e.target.checked);
}
@ -654,7 +660,7 @@ module.exports = React.createClass({
},
_renderDevicesPanel: function() {
var DevicesPanel = sdk.getComponent('settings.DevicesPanel');
const DevicesPanel = sdk.getComponent('settings.DevicesPanel');
return (
<div>
<h3>Devices</h3>
@ -665,7 +671,7 @@ module.exports = React.createClass({
_renderBugReport: function() {
if (!SdkConfig.get().bug_report_endpoint_url) {
return <div />
return <div />;
}
return (
<div>
@ -684,17 +690,17 @@ module.exports = React.createClass({
// default to enabled if undefined
if (this.props.enableLabs === false) return null;
let features = UserSettingsStore.LABS_FEATURES.map(feature => (
const features = UserSettingsStore.LABS_FEATURES.map((feature) => (
<div key={feature.id} className="mx_UserSettings_toggle">
<input
type="checkbox"
id={feature.id}
name={feature.id}
defaultChecked={ UserSettingsStore.isFeatureEnabled(feature.id) }
onChange={e => {
onChange={(e) => {
if (MatrixClientPeg.get().isGuest()) {
e.target.checked = false;
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
const NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register",
description: "Guests can't use labs features. Please register.",
@ -746,14 +752,14 @@ module.exports = React.createClass({
},
_renderBulkOptions: function() {
let invitedRooms = MatrixClientPeg.get().getRooms().filter((r) => {
const invitedRooms = MatrixClientPeg.get().getRooms().filter((r) => {
return r.hasMembershipState(this._me, "invite");
});
if (invitedRooms.length === 0) {
return null;
}
let Spinner = sdk.getComponent("elements.Spinner");
const Spinner = sdk.getComponent("elements.Spinner");
let reject = <Spinner />;
if (!this.state.rejectingInvites) {
@ -777,9 +783,7 @@ module.exports = React.createClass({
_showSpoiler: function(event) {
const target = event.target;
const hidden = target.getAttribute('data-spoiler');
target.innerHTML = hidden;
target.innerHTML = target.getAttribute('data-spoiler');
const range = document.createRange();
range.selectNodeContents(target);
@ -790,12 +794,12 @@ module.exports = React.createClass({
},
nameForMedium: function(medium) {
if (medium == 'msisdn') return 'Phone';
if (medium === 'msisdn') return 'Phone';
return medium[0].toUpperCase() + medium.slice(1);
},
presentableTextForThreepid: function(threepid) {
if (threepid.medium == 'msisdn') {
if (threepid.medium === 'msisdn') {
return '+' + threepid.address;
} else {
return threepid.address;
@ -803,7 +807,7 @@ module.exports = React.createClass({
},
render: function() {
var Loader = sdk.getComponent("elements.Spinner");
const Loader = sdk.getComponent("elements.Spinner");
switch (this.state.phase) {
case "UserSettings.LOADING":
return (
@ -815,18 +819,18 @@ module.exports = React.createClass({
throw new Error("Unknown state.phase => " + this.state.phase);
}
// can only get here if phase is UserSettings.DISPLAY
var SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
var ChangeDisplayName = sdk.getComponent("views.settings.ChangeDisplayName");
var ChangePassword = sdk.getComponent("views.settings.ChangePassword");
var ChangeAvatar = sdk.getComponent('settings.ChangeAvatar');
var Notifications = sdk.getComponent("settings.Notifications");
var EditableText = sdk.getComponent('elements.EditableText');
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
const ChangeDisplayName = sdk.getComponent("views.settings.ChangeDisplayName");
const ChangePassword = sdk.getComponent("views.settings.ChangePassword");
const ChangeAvatar = sdk.getComponent('settings.ChangeAvatar');
const Notifications = sdk.getComponent("settings.Notifications");
const EditableText = sdk.getComponent('elements.EditableText');
var avatarUrl = (
const avatarUrl = (
this.state.avatarUrl ? MatrixClientPeg.get().mxcUrlToHttp(this.state.avatarUrl) : null
);
var threepidsSection = this.state.threepids.map((val, pidIndex) => {
const threepidsSection = this.state.threepids.map((val, pidIndex) => {
const id = "3pid-" + val.address;
return (
<div className="mx_UserSettings_profileTableRow" key={pidIndex}>
@ -874,7 +878,7 @@ module.exports = React.createClass({
threepidsSection.push(addEmailSection);
threepidsSection.push(addMsisdnSection);
var accountJsx;
let accountJsx;
if (MatrixClientPeg.get().isGuest()) {
accountJsx = (
@ -882,8 +886,7 @@ module.exports = React.createClass({
Create an account
</div>
);
}
else {
} else {
accountJsx = (
<ChangePassword
className="mx_UserSettings_accountTable"
@ -895,9 +898,9 @@ module.exports = React.createClass({
onFinished={this.onPasswordChanged} />
);
}
var notification_area;
let notificationArea;
if (!MatrixClientPeg.get().isGuest() && this.state.threepids !== undefined) {
notification_area = (<div>
notificationArea = (<div>
<h3>Notifications</h3>
<div className="mx_UserSettings_section">
@ -911,7 +914,7 @@ module.exports = React.createClass({
// we are using a version old version of olm. We assume the former.
let olmVersionString = "<not-enabled>";
if (olmVersion !== undefined) {
olmVersionString = `v${olmVersion[0]}.${olmVersion[1]}.${olmVersion[2]}`;
olmVersionString = `${olmVersion[0]}.${olmVersion[1]}.${olmVersion[2]}`;
}
return (
@ -969,7 +972,7 @@ module.exports = React.createClass({
{this._renderReferral()}
{notification_area}
{notificationArea}
{this._renderUserInterfaceSettings()}
{this._renderLabs()}
@ -985,7 +988,10 @@ module.exports = React.createClass({
Logged in as {this._me}
</div>
<div className="mx_UserSettings_advanced">
Access Token: <span className="mx_UserSettings_advanced_spoiler" onClick={this._showSpoiler} data-spoiler={ MatrixClientPeg.get().getAccessToken() }>&lt;click to reveal&gt;</span>
Access Token: <span className="mx_UserSettings_advanced_spoiler"
onClick={this._showSpoiler}
data-spoiler={ MatrixClientPeg.get().getAccessToken() }
>&lt;click to reveal&gt;</span>
</div>
<div className="mx_UserSettings_advanced">
Homeserver is { MatrixClientPeg.get().getHomeserverUrl() }
@ -995,11 +1001,11 @@ module.exports = React.createClass({
</div>
<div className="mx_UserSettings_advanced">
matrix-react-sdk version: {(REACT_SDK_VERSION !== '<local>')
? <a href={ GHVersionUrl('matrix-org/matrix-react-sdk', REACT_SDK_VERSION) }>{REACT_SDK_VERSION}</a>
? gHVersionLabel('matrix-org/matrix-react-sdk', REACT_SDK_VERSION)
: REACT_SDK_VERSION
}<br/>
riot-web version: {(this.state.vectorVersion !== null)
? <a href={ GHVersionUrl('vector-im/riot-web', this.state.vectorVersion.split('-')[0]) }>{this.state.vectorVersion}</a>
? gHVersionLabel('vector-im/riot-web', this.state.vectorVersion)
: 'unknown'
}<br/>
olm version: {olmVersionString}<br/>
@ -1013,5 +1019,5 @@ module.exports = React.createClass({
</GeminiScrollbar>
</div>
);
}
},
});

View file

@ -23,6 +23,9 @@ import url from 'url';
import sdk from '../../../index';
import Login from '../../../Login';
// For validating phone numbers without country codes
const PHONE_NUMBER_REGEX = /^[0-9\(\)\-\s]*$/;
/**
* A wire component which glues together login UI components and Login logic
*/
@ -125,7 +128,16 @@ module.exports = React.createClass({
},
onPhoneNumberChanged: function(phoneNumber) {
this.setState({ phoneNumber: phoneNumber });
// Validate the phone number entered
if (!PHONE_NUMBER_REGEX.test(phoneNumber)) {
this.setState({ errorText: 'The phone number entered looks invalid' });
return;
}
this.setState({
phoneNumber: phoneNumber,
errorText: null,
});
},
onServerConfigChange: function(config) {

View file

@ -57,20 +57,25 @@ export default React.createClass({
}
},
// Don't let key{down,press} events escape the modal. Consume them all.
_eatKeyEvent: function(e) {
e.stopPropagation();
},
// Must be when the key is released (and not pressed) otherwise componentWillUnmount
// will focus another element which will receive future key events
_onKeyUp: function(e) {
if (e.keyCode === KeyCode.ESCAPE) {
e.stopPropagation();
e.preventDefault();
this.props.onFinished();
} else if (e.keyCode === KeyCode.ENTER) {
if (this.props.onEnterPressed) {
e.stopPropagation();
e.preventDefault();
this.props.onEnterPressed(e);
}
}
// Consume all keyup events while Modal is open
e.stopPropagation();
},
_onCancelClick: function(e) {
@ -81,7 +86,11 @@ export default React.createClass({
const TintableSvg = sdk.getComponent("elements.TintableSvg");
return (
<div onKeyUp={this._onKeyUp} className={this.props.className}>
<div onKeyUp={this._onKeyUp}
onKeyDown={this._eatKeyEvent}
onKeyPress={this._eatKeyEvent}
className={this.props.className}
>
<AccessibleButton onClick={this._onCancelClick}
className="mx_Dialog_cancelButton"
>

View file

@ -0,0 +1,80 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import AccessibleButton from './AccessibleButton';
import dis from '../../../dispatcher';
import sdk from '../../../index';
export default React.createClass({
displayName: 'RoleButton',
propTypes: {
size: PropTypes.string,
tooltip: PropTypes.bool,
action: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
iconPath: PropTypes.string.isRequired,
},
getDefaultProps: function() {
return {
size: "25",
tooltip: false,
};
},
getInitialState: function() {
return {
showTooltip: false,
};
},
_onClick: function(ev) {
ev.stopPropagation();
dis.dispatch({action: this.props.action});
},
_onMouseEnter: function() {
if (this.props.tooltip) this.setState({showTooltip: true});
},
_onMouseLeave: function() {
this.setState({showTooltip: false});
},
render: function() {
const TintableSvg = sdk.getComponent("elements.TintableSvg");
let tooltip;
if (this.state.showTooltip) {
const RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
tooltip = <RoomTooltip className="mx_RoleButton_tooltip" label={this.props.label} />;
}
return (
<AccessibleButton className="mx_RoleButton"
onClick={this._onClick}
onMouseEnter={this._onMouseEnter}
onMouseLeave={this._onMouseLeave}
>
<TintableSvg src={this.props.iconPath} width={this.props.size} height={this.props.size} />
{tooltip}
</AccessibleButton>
);
}
});

View file

@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -138,7 +139,7 @@ export default React.createClass({
onClick={this.onClick.bind(this, i)}
onMouseEnter={this.onMouseEnter.bind(this, i)}
onMouseLeave={this.onMouseLeave}
key={this.props.addressList[i].userId}
key={this.props.addressList[i].addressType + "/" + this.props.addressList[i].address}
ref={(ref) => { this.addressListElement = ref; }}
>
<AddressTile address={this.props.addressList[i]} justified={true} networkName="vector" networkUrl="img/search-icon-vector.svg" />

View file

@ -0,0 +1,38 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import sdk from '../../../index';
import PropTypes from 'prop-types';
const CreateRoomButton = function(props) {
const ActionButton = sdk.getComponent('elements.ActionButton');
return (
<ActionButton action="view_create_room"
label="Create new room"
iconPath="img/icons-create-room.svg"
size={props.size}
tooltip={props.tooltip}
/>
);
};
CreateRoomButton.propTypes = {
size: PropTypes.string,
tooltip: PropTypes.bool,
};
export default CreateRoomButton;

View file

@ -0,0 +1,38 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import sdk from '../../../index';
import PropTypes from 'prop-types';
const HomeButton = function(props) {
const ActionButton = sdk.getComponent('elements.ActionButton');
return (
<ActionButton action="view_home_page"
label="Welcome page"
iconPath="img/icons-home.svg"
size={props.size}
tooltip={props.tooltip}
/>
);
};
HomeButton.propTypes = {
size: PropTypes.string,
tooltip: PropTypes.bool,
};
export default HomeButton;

View file

@ -369,6 +369,7 @@ module.exports = React.createClass({
render: function() {
const eventsToRender = this.props.events;
const eventIds = eventsToRender.map(e => e.getId()).join(',');
const fewEvents = eventsToRender.length < this.props.threshold;
const expanded = this.state.expanded || fewEvents;
@ -379,7 +380,7 @@ module.exports = React.createClass({
if (fewEvents) {
return (
<div className="mx_MemberEventListSummary">
<div className="mx_MemberEventListSummary" data-scroll-tokens={eventIds}>
{expandedEvents}
</div>
);
@ -437,7 +438,7 @@ module.exports = React.createClass({
);
return (
<div className="mx_MemberEventListSummary">
<div className="mx_MemberEventListSummary" data-scroll-tokens={eventIds}>
{toggleButton}
{summaryContainer}
{expanded ? <div className="mx_MemberEventListSummary_line">&nbsp;</div> : null}

View file

@ -0,0 +1,38 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import sdk from '../../../index';
import PropTypes from 'prop-types';
const RoomDirectoryButton = function(props) {
const ActionButton = sdk.getComponent('elements.ActionButton');
return (
<ActionButton action="view_room_directory"
label="Room directory"
iconPath="img/icons-directory.svg"
size={props.size}
tooltip={props.tooltip}
/>
);
};
RoomDirectoryButton.propTypes = {
size: PropTypes.string,
tooltip: PropTypes.bool,
};
export default RoomDirectoryButton;

View file

@ -0,0 +1,38 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import sdk from '../../../index';
import PropTypes from 'prop-types';
const SettingsButton = function(props) {
const ActionButton = sdk.getComponent('elements.ActionButton');
return (
<ActionButton action="view_user_settings"
label="Settings"
iconPath="img/icons-settings.svg"
size={props.size}
tooltip={props.tooltip}
/>
);
};
SettingsButton.propTypes = {
size: PropTypes.string,
tooltip: PropTypes.bool,
};
export default SettingsButton;

View file

@ -0,0 +1,38 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import sdk from '../../../index';
import PropTypes from 'prop-types';
const StartChatButton = function(props) {
const ActionButton = sdk.getComponent('elements.ActionButton');
return (
<ActionButton action="view_create_chat"
label="Start chat"
iconPath="img/icons-people.svg"
size={props.size}
tooltip={props.tooltip}
/>
);
};
StartChatButton.propTypes = {
size: PropTypes.string,
tooltip: PropTypes.bool,
};
export default StartChatButton;

View file

@ -100,7 +100,9 @@ module.exports = React.createClass({
render: function() {
var p = this.state.preview;
if (!p) return <div/>;
if (!p || Object.keys(p).length === 0) {
return <div/>;
}
// FIXME: do we want to factor out all image displaying between this and MImageBody - especially for lightboxing?
var image = p["og:image"];

View file

@ -43,6 +43,7 @@ export default class MessageComposer extends React.Component {
this.onToggleMarkdownClicked = this.onToggleMarkdownClicked.bind(this);
this.onInputStateChanged = this.onInputStateChanged.bind(this);
this.onEvent = this.onEvent.bind(this);
this.onPageUnload = this.onPageUnload.bind(this);
this.state = {
autocompleteQuery: '',
@ -64,12 +65,21 @@ export default class MessageComposer extends React.Component {
// marked as encrypted.
// XXX: fragile as all hell - fixme somehow, perhaps with a dedicated Room.encryption event or something.
MatrixClientPeg.get().on("event", this.onEvent);
window.addEventListener('beforeunload', this.onPageUnload);
}
componentWillUnmount() {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("event", this.onEvent);
}
window.removeEventListener('beforeunload', this.onPageUnload);
}
onPageUnload(event) {
if (this.messageComposerInput) {
this.messageComposerInput.sentHistory.saveLastTextEntry();
}
}
onEvent(event) {

View file

@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -28,8 +29,16 @@ var Rooms = require('../../../Rooms');
import DMRoomMap from '../../../utils/DMRoomMap';
var Receipt = require('../../../utils/Receipt');
var constantTimeDispatcher = require('../../../ConstantTimeDispatcher');
import AccessibleButton from '../elements/AccessibleButton';
var HIDE_CONFERENCE_CHANS = true;
const HIDE_CONFERENCE_CHANS = true;
const VERBS = {
'm.favourite': 'favourite',
'im.vector.fake.direct': 'tag direct chat',
'im.vector.fake.recent': 'restore',
'm.lowpriority': 'demote',
};
module.exports = React.createClass({
displayName: 'RoomList',
@ -53,6 +62,7 @@ module.exports = React.createClass({
getInitialState: function() {
return {
isLoadingLeftRooms: false,
totalRoomCount: null,
lists: {},
incomingCall: null,
};
@ -73,8 +83,7 @@ module.exports = React.createClass({
// lookup for which lists a given roomId is currently in.
this.listsForRoomId = {};
var s = this.getRoomLists();
this.setState(s);
this.refreshRoomList();
// order of the sublists
//this.listOrder = [];
@ -97,7 +106,7 @@ module.exports = React.createClass({
if (this.props.selectedRoom) {
constantTimeDispatcher.dispatch(
"RoomTile.select", this.props.selectedRoom, {}
);
);
}
constantTimeDispatcher.dispatch(
"RoomTile.select", nextProps.selectedRoom, { selected: true }
@ -265,7 +274,7 @@ module.exports = React.createClass({
},
onRoomStateMember: function(ev, state, member) {
if (ev.getStateKey() === MatrixClientPeg.get().credentials.userId &&
if (ev.getStateKey() === MatrixClientPeg.get().credentials.userId &&
ev.getPrevContent() && ev.getPrevContent().membership === "invite")
{
this._delayedRefreshRoomList();
@ -290,7 +299,7 @@ module.exports = React.createClass({
this._delayedRefreshRoomList();
}
else if (ev.getType() == 'm.push_rules') {
this._delayedRefreshRoomList();
this._delayedRefreshRoomList();
}
},
@ -317,21 +326,29 @@ module.exports = React.createClass({
// any changes to it incrementally, updating the appropriate sublists
// as needed.
// Alternatively we'd do something magical with Immutable.js or similar.
this.setState(this.getRoomLists());
const lists = this.getRoomLists();
let totalRooms = 0;
for (const l of Object.values(lists)) {
totalRooms += l.length;
}
this.setState({
lists: this.getRoomLists(),
totalRoomCount: totalRooms,
});
// this._lastRefreshRoomListTs = Date.now();
},
getRoomLists: function() {
var self = this;
var s = { lists: {} };
const lists = {};
s.lists["im.vector.fake.invite"] = [];
s.lists["m.favourite"] = [];
s.lists["im.vector.fake.recent"] = [];
s.lists["im.vector.fake.direct"] = [];
s.lists["m.lowpriority"] = [];
s.lists["im.vector.fake.archived"] = [];
lists["im.vector.fake.invite"] = [];
lists["m.favourite"] = [];
lists["im.vector.fake.recent"] = [];
lists["im.vector.fake.direct"] = [];
lists["m.lowpriority"] = [];
lists["im.vector.fake.archived"] = [];
this.listsForRoomId = {};
var otherTagNames = {};
@ -341,7 +358,7 @@ module.exports = React.createClass({
MatrixClientPeg.get().getRooms().forEach(function(room) {
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
if (!me) return;
// console.log("room = " + room.name + ", me.membership = " + me.membership +
// ", sender = " + me.events.member.getSender() +
// ", target = " + me.events.member.getStateKey() +
@ -353,7 +370,7 @@ module.exports = React.createClass({
if (me.membership == "invite") {
self.listsForRoomId[room.roomId].push("im.vector.fake.invite");
s.lists["im.vector.fake.invite"].push(room);
lists["im.vector.fake.invite"].push(room);
}
else if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(room, me, self.props.ConferenceHandler)) {
// skip past this room & don't put it in any lists
@ -366,8 +383,8 @@ module.exports = React.createClass({
if (tagNames.length) {
for (var i = 0; i < tagNames.length; i++) {
var tagName = tagNames[i];
s.lists[tagName] = s.lists[tagName] || [];
s.lists[tagName].push(room);
lists[tagName] = lists[tagName] || [];
lists[tagName].push(room);
self.listsForRoomId[room.roomId].push(tagName);
otherTagNames[tagName] = 1;
}
@ -375,67 +392,26 @@ module.exports = React.createClass({
else if (dmRoomMap.getUserIdForRoomId(room.roomId)) {
// "Direct Message" rooms (that we're still in and that aren't otherwise tagged)
self.listsForRoomId[room.roomId].push("im.vector.fake.direct");
s.lists["im.vector.fake.direct"].push(room);
lists["im.vector.fake.direct"].push(room);
}
else {
self.listsForRoomId[room.roomId].push("im.vector.fake.recent");
s.lists["im.vector.fake.recent"].push(room);
lists["im.vector.fake.recent"].push(room);
}
}
else if (me.membership === "leave") {
self.listsForRoomId[room.roomId].push("im.vector.fake.archived");
s.lists["im.vector.fake.archived"].push(room);
lists["im.vector.fake.archived"].push(room);
}
else {
console.error("unrecognised membership: " + me.membership + " - this should never happen");
}
});
if (s.lists["im.vector.fake.direct"].length == 0 &&
MatrixClientPeg.get().getAccountData('m.direct') === undefined &&
!MatrixClientPeg.get().isGuest())
{
// scan through the 'recents' list for any rooms which look like DM rooms
// and make them DM rooms
const oldRecents = s.lists["im.vector.fake.recent"];
s.lists["im.vector.fake.recent"] = [];
for (const room of oldRecents) {
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
if (me && Rooms.looksLikeDirectMessageRoom(room, me)) {
self.listsForRoomId[room.roomId].push("im.vector.fake.direct");
s.lists["im.vector.fake.direct"].push(room);
} else {
self.listsForRoomId[room.roomId].push("im.vector.fake.recent");
s.lists["im.vector.fake.recent"].push(room);
}
}
// save these new guessed DM rooms into the account data
const newMDirectEvent = {};
for (const room of s.lists["im.vector.fake.direct"]) {
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
const otherPerson = Rooms.getOnlyOtherMember(room, me);
if (!otherPerson) continue;
const roomList = newMDirectEvent[otherPerson.userId] || [];
roomList.push(room.roomId);
newMDirectEvent[otherPerson.userId] = roomList;
}
console.warn("Resetting room DM state to be " + JSON.stringify(newMDirectEvent));
// if this fails, fine, we'll just do the same thing next time we get the room lists
MatrixClientPeg.get().setAccountData('m.direct', newMDirectEvent).done();
}
//console.log("calculated new roomLists; im.vector.fake.recent = " + s.lists["im.vector.fake.recent"]);
// we actually apply the sorting to this when receiving the prop in RoomSubLists.
// we'll need this when we get to iterating through lists programatically - e.g. ctrl-shift-up/down
/*
/*
this.listOrder = [
"im.vector.fake.invite",
"m.favourite",
@ -449,7 +425,7 @@ module.exports = React.createClass({
];
*/
return s;
return lists;
},
_getScrollNode: function() {
@ -479,6 +455,7 @@ module.exports = React.createClass({
var incomingCallBox = document.getElementById("incomingCallBox");
if (incomingCallBox && incomingCallBox.parentElement) {
var scrollArea = this._getScrollNode();
if (!scrollArea) return;
// Use the offset of the top of the scroll area from the window
// as this is used to calculate the CSS fixed top position for the stickies
var scrollAreaOffset = scrollArea.getBoundingClientRect().top + window.pageYOffset;
@ -502,6 +479,7 @@ module.exports = React.createClass({
// properly through React
_initAndPositionStickyHeaders: function(initialise, scrollToPosition) {
var scrollArea = this._getScrollNode();
if (!scrollArea) return;
// Use the offset of the top of the scroll area from the window
// as this is used to calculate the CSS fixed top position for the stickies
var scrollAreaOffset = scrollArea.getBoundingClientRect().top + window.pageYOffset;
@ -599,6 +577,58 @@ module.exports = React.createClass({
this.refs.gemscroll.forceUpdate();
},
_getEmptyContent: function(section) {
const RoomDropTarget = sdk.getComponent('rooms.RoomDropTarget');
if (this.props.collapsed) {
return <RoomDropTarget label="" />;
}
const StartChatButton = sdk.getComponent('elements.StartChatButton');
const RoomDirectoryButton = sdk.getComponent('elements.RoomDirectoryButton');
const CreateRoomButton = sdk.getComponent('elements.CreateRoomButton');
if (this.state.totalRoomCount === 0) {
const TintableSvg = sdk.getComponent('elements.TintableSvg');
switch (section) {
case 'im.vector.fake.direct':
return <div className="mx_RoomList_emptySubListTip">
Press
<StartChatButton size="16" />
to start a chat with someone
</div>;
case 'im.vector.fake.recent':
return <div className="mx_RoomList_emptySubListTip">
You're not in any rooms yet! Press
<CreateRoomButton size="16" />
to make a room or
<RoomDirectoryButton size="16" />
to browse the directory
</div>;
}
}
const labelText = 'Drop here to ' + (VERBS[section] || 'tag ' + section);
return <RoomDropTarget label={labelText} />;
},
_getHeaderItems: function(section) {
const StartChatButton = sdk.getComponent('elements.StartChatButton');
const RoomDirectoryButton = sdk.getComponent('elements.RoomDirectoryButton');
const CreateRoomButton = sdk.getComponent('elements.CreateRoomButton');
switch (section) {
case 'im.vector.fake.direct':
return <span className="mx_RoomList_headerButtons">
<StartChatButton size="16" />
</span>;
case 'im.vector.fake.recent':
return <span className="mx_RoomList_headerButtons">
<RoomDirectoryButton size="16" />
<CreateRoomButton size="16" />
</span>;
}
},
render: function() {
var RoomSubList = sdk.getComponent('structures.RoomSubList');
var self = this;
@ -622,7 +652,7 @@ module.exports = React.createClass({
<RoomSubList list={ self.state.lists['m.favourite'] }
label="Favourites"
tagName="m.favourite"
verb="favourite"
emptyContent={self._getEmptyContent('m.favourite')}
editable={ true }
order="manual"
incomingCall={ self.state.incomingCall }
@ -635,7 +665,8 @@ module.exports = React.createClass({
<RoomSubList list={ self.state.lists['im.vector.fake.direct'] }
label="People"
tagName="im.vector.fake.direct"
verb="tag direct chat"
emptyContent={self._getEmptyContent('im.vector.fake.direct')}
headerItems={self._getHeaderItems('im.vector.fake.direct')}
editable={ true }
order="recent"
incomingCall={ self.state.incomingCall }
@ -650,7 +681,8 @@ module.exports = React.createClass({
label="Rooms"
tagName="im.vector.fake.recent"
editable={ true }
verb="restore"
emptyContent={self._getEmptyContent('im.vector.fake.recent')}
headerItems={self._getHeaderItems('im.vector.fake.recent')}
order="recent"
incomingCall={ self.state.incomingCall }
collapsed={ self.props.collapsed }
@ -665,7 +697,7 @@ module.exports = React.createClass({
key={ tagName }
label={ tagName }
tagName={ tagName }
verb={ "tag as " + tagName }
emptyContent={self._getEmptyContent(tagName)}
editable={ true }
order="manual"
incomingCall={ self.state.incomingCall }
@ -681,7 +713,7 @@ module.exports = React.createClass({
<RoomSubList list={ self.state.lists['m.lowpriority'] }
label="Low priority"
tagName="m.lowpriority"
verb="demote"
emptyContent={self._getEmptyContent('m.lowpriority')}
editable={ true }
order="recent"
incomingCall={ self.state.incomingCall }

View file

@ -926,7 +926,7 @@ module.exports = React.createClass({
<PowerSelector ref="ban" value={ban_level} controlled={false} disabled={!can_change_levels || current_user_level < ban_level} onChange={this.onPowerLevelsChanged}/>
</div>
<div className="mx_RoomSettings_powerLevel">
<span className="mx_RoomSettings_powerLevelKey">To redact messages, you must be a </span>
<span className="mx_RoomSettings_powerLevelKey">To redact other users' messages, you must be a </span>
<PowerSelector ref="redact" value={redact_level} controlled={false} disabled={!can_change_levels || current_user_level < redact_level} onChange={this.onPowerLevelsChanged}/>
</div>

View file

@ -60,7 +60,7 @@ module.exports = React.createClass({
}
}
return (
<li data-scroll-token={eventId+"+"+j}>
<li data-scroll-tokens={eventId+"+"+j}>
{ret}
</li>);
},