Merge remote-tracking branch 'upstream/develop' into feature-autocomplete
This commit is contained in:
commit
cd928fe6f5
20 changed files with 464 additions and 290 deletions
104
.eslintrc
Normal file
104
.eslintrc
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
{
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"plugins": [
|
||||||
|
"react",
|
||||||
|
"flowtype"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 6,
|
||||||
|
"sourceType": "module",
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"jsx": true,
|
||||||
|
"impliedStrict": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"amd": true,
|
||||||
|
"es6": true,
|
||||||
|
"node": true,
|
||||||
|
"mocha": true
|
||||||
|
},
|
||||||
|
"extends": ["eslint:recommended", "plugin:react/recommended"],
|
||||||
|
"rules": {
|
||||||
|
"no-undef": ["warn"],
|
||||||
|
"global-strict": ["off"],
|
||||||
|
"no-extra-semi": ["warn"],
|
||||||
|
"no-underscore-dangle": ["off"],
|
||||||
|
"no-console": ["off"],
|
||||||
|
"no-unused-vars": ["off"],
|
||||||
|
"no-trailing-spaces": ["warn", {
|
||||||
|
"skipBlankLines": true
|
||||||
|
}],
|
||||||
|
"no-unreachable": ["warn"],
|
||||||
|
"no-spaced-func": ["warn"],
|
||||||
|
"no-new-func": ["error"],
|
||||||
|
"no-new-wrappers": ["error"],
|
||||||
|
"no-invalid-regexp": ["error"],
|
||||||
|
"no-extra-bind": ["error"],
|
||||||
|
"no-magic-numbers": ["error"],
|
||||||
|
"consistent-return": ["error"],
|
||||||
|
"valid-jsdoc": ["error"],
|
||||||
|
"no-use-before-define": ["error"],
|
||||||
|
"camelcase": ["warn"],
|
||||||
|
"array-callback-return": ["error"],
|
||||||
|
"dot-location": ["warn", "property"],
|
||||||
|
"guard-for-in": ["error"],
|
||||||
|
"no-useless-call": ["warn"],
|
||||||
|
"no-useless-escape": ["warn"],
|
||||||
|
"no-useless-concat": ["warn"],
|
||||||
|
"brace-style": ["warn", "1tbs"],
|
||||||
|
"comma-style": ["warn", "last"],
|
||||||
|
"space-before-function-paren": ["warn", "never"],
|
||||||
|
"space-before-blocks": ["warn", "always"],
|
||||||
|
"keyword-spacing": ["warn", {
|
||||||
|
"before": true,
|
||||||
|
"after": true
|
||||||
|
}],
|
||||||
|
|
||||||
|
// dangling commas required, but only for multiline objects/arrays
|
||||||
|
"comma-dangle": ["warn", "always-multiline"],
|
||||||
|
// always === instead of ==, unless dealing with null/undefined
|
||||||
|
"eqeqeq": ["error", "smart"],
|
||||||
|
// always use curly braces, even with single statements
|
||||||
|
"curly": ["error", "all"],
|
||||||
|
// phasing out var in favour of let/const is a good idea
|
||||||
|
"no-var": ["warn"],
|
||||||
|
// always require semicolons
|
||||||
|
"semi": ["error", "always"],
|
||||||
|
// prefer rest and spread over the Old Ways
|
||||||
|
"prefer-spread": ["warn"],
|
||||||
|
"prefer-rest-params": ["warn"],
|
||||||
|
|
||||||
|
/** react **/
|
||||||
|
|
||||||
|
// bind or arrow function in props causes performance issues
|
||||||
|
"react/jsx-no-bind": ["error"],
|
||||||
|
"react/jsx-key": ["error"],
|
||||||
|
"react/prefer-stateless-function": ["warn"],
|
||||||
|
"react/sort-comp": ["warn"],
|
||||||
|
|
||||||
|
/** flowtype **/
|
||||||
|
"flowtype/require-parameter-type": 1,
|
||||||
|
"flowtype/require-return-type": [
|
||||||
|
1,
|
||||||
|
"always",
|
||||||
|
{
|
||||||
|
"annotateUndefined": "never"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"flowtype/space-after-type-colon": [
|
||||||
|
1,
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
"flowtype/space-before-type-colon": [
|
||||||
|
1,
|
||||||
|
"never"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"flowtype": {
|
||||||
|
"onlyFilesWithFlowAnnotation": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1,5 @@
|
||||||
|
npm-debug.log
|
||||||
|
|
||||||
/node_modules
|
/node_modules
|
||||||
/lib
|
/lib
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
export KARMAFLAGS="--no-colors"
|
||||||
export NVM_DIR="/home/jenkins/.nvm"
|
export NVM_DIR="/home/jenkins/.nvm"
|
||||||
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
|
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
|
||||||
nvm use 4
|
nvm use 4
|
||||||
|
@ -14,6 +15,9 @@ npm install
|
||||||
# run the mocha tests
|
# run the mocha tests
|
||||||
npm run test
|
npm run test
|
||||||
|
|
||||||
|
# run eslint
|
||||||
|
npm run lint -- -f checkstyle -o eslint.xml || true
|
||||||
|
|
||||||
# delete the old tarball, if it exists
|
# delete the old tarball, if it exists
|
||||||
rm -f matrix-react-sdk-*.tgz
|
rm -f matrix-react-sdk-*.tgz
|
||||||
|
|
||||||
|
|
10
package.json
10
package.json
|
@ -16,10 +16,12 @@
|
||||||
"reskindex": "reskindex -h header",
|
"reskindex": "reskindex -h header",
|
||||||
"build": "babel src -d lib --source-maps",
|
"build": "babel src -d lib --source-maps",
|
||||||
"start": "babel src -w -d lib --source-maps",
|
"start": "babel src -w -d lib --source-maps",
|
||||||
|
"lint": "eslint src/",
|
||||||
|
"lintall": "eslint src/ test/",
|
||||||
"clean": "rimraf lib",
|
"clean": "rimraf lib",
|
||||||
"prepublish": "npm run build && git rev-parse HEAD > git-revision.txt",
|
"prepublish": "npm run build && git rev-parse HEAD > git-revision.txt",
|
||||||
"test": "karma start --browsers PhantomJS",
|
"test": "karma start $KARMAFLAGS --browsers PhantomJS",
|
||||||
"test-multi": "karma start --single-run=false"
|
"test-multi": "karma start $KARMAFLAGS --single-run=false"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"classnames": "^2.1.2",
|
"classnames": "^2.1.2",
|
||||||
|
@ -55,8 +57,12 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel": "^5.8.23",
|
"babel": "^5.8.23",
|
||||||
"babel-core": "^5.8.38",
|
"babel-core": "^5.8.38",
|
||||||
|
"babel-eslint": "^6.1.0",
|
||||||
"babel-loader": "^5.4.0",
|
"babel-loader": "^5.4.0",
|
||||||
"babel-polyfill": "^6.5.0",
|
"babel-polyfill": "^6.5.0",
|
||||||
|
"eslint": "^2.13.1",
|
||||||
|
"eslint-plugin-flowtype": "^2.3.0",
|
||||||
|
"eslint-plugin-react": "^5.2.2",
|
||||||
"expect": "^1.16.0",
|
"expect": "^1.16.0",
|
||||||
"json-loader": "^0.5.3",
|
"json-loader": "^0.5.3",
|
||||||
"karma": "^0.13.22",
|
"karma": "^0.13.22",
|
||||||
|
|
|
@ -24,30 +24,5 @@ module.exports = {
|
||||||
getDisplayAliasForRoom: function(room) {
|
getDisplayAliasForRoom: function(room) {
|
||||||
return room.getCanonicalAlias() || room.getAliases()[0];
|
return room.getCanonicalAlias() || room.getAliases()[0];
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a list of room objects, return the room which has the given alias,
|
|
||||||
* else null.
|
|
||||||
*/
|
|
||||||
getRoomForAlias: function(rooms, room_alias) {
|
|
||||||
var room;
|
|
||||||
for (var i = 0; i < rooms.length; i++) {
|
|
||||||
var aliasEvents = rooms[i].currentState.getStateEvents(
|
|
||||||
"m.room.aliases"
|
|
||||||
);
|
|
||||||
for (var j = 0; j < aliasEvents.length; j++) {
|
|
||||||
var aliases = aliasEvents[j].getContent().aliases || [];
|
|
||||||
for (var k = 0; k < aliases.length; k++) {
|
|
||||||
if (aliases[k] === room_alias) {
|
|
||||||
room = rooms[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (room) { break; }
|
|
||||||
}
|
|
||||||
if (room) { break; }
|
|
||||||
}
|
|
||||||
return room || null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
47
src/SdkConfig.js
Normal file
47
src/SdkConfig.js
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 OpenMarket 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var DEFAULTS = {
|
||||||
|
// URL to a page we show in an iframe to configure integrations
|
||||||
|
//integrations_ui_url: "https://scalar.vector.im/",
|
||||||
|
integrations_ui_url: "http://127.0.0.1:5051/",
|
||||||
|
// Base URL to the REST interface of the integrations server
|
||||||
|
//integrations_rest_url: "https://scalar.vector.im/api",
|
||||||
|
integrations_rest_url: "http://127.0.0.1:5050",
|
||||||
|
};
|
||||||
|
|
||||||
|
class SdkConfig {
|
||||||
|
|
||||||
|
static get() {
|
||||||
|
return global.mxReactSdkConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
static put(cfg) {
|
||||||
|
var defaultKeys = Object.keys(DEFAULTS);
|
||||||
|
for (var i = 0; i < defaultKeys.length; ++i) {
|
||||||
|
if (cfg[defaultKeys[i]] === undefined) {
|
||||||
|
cfg[defaultKeys[i]] = DEFAULTS[defaultKeys[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
global.mxReactSdkConfig = cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unset() {
|
||||||
|
global.mxReactSdkConfig = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = SdkConfig;
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
||||||
var MatrixClientPeg = require("./MatrixClientPeg");
|
var MatrixClientPeg = require("./MatrixClientPeg");
|
||||||
var MatrixTools = require("./MatrixTools");
|
var MatrixTools = require("./MatrixTools");
|
||||||
var dis = require("./dispatcher");
|
var dis = require("./dispatcher");
|
||||||
var encryption = require("./encryption");
|
|
||||||
var Tinter = require("./Tinter");
|
var Tinter = require("./Tinter");
|
||||||
|
|
||||||
|
|
||||||
|
@ -89,25 +88,6 @@ var commands = {
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
}),
|
}),
|
||||||
|
|
||||||
encrypt: new Command("encrypt", "<on|off>", function(room_id, args) {
|
|
||||||
if (args == "on") {
|
|
||||||
var client = MatrixClientPeg.get();
|
|
||||||
var members = client.getRoom(room_id).currentState.members;
|
|
||||||
var user_ids = Object.keys(members);
|
|
||||||
return success(
|
|
||||||
encryption.enableEncryption(client, room_id, user_ids)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (args == "off") {
|
|
||||||
var client = MatrixClientPeg.get();
|
|
||||||
return success(
|
|
||||||
encryption.disableEncryption(client, room_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
return reject(this.getUsage());
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Change the room topic
|
// Change the room topic
|
||||||
topic: new Command("topic", "<topic>", function(room_id, args) {
|
topic: new Command("topic", "<topic>", function(room_id, args) {
|
||||||
if (args) {
|
if (args) {
|
||||||
|
@ -132,46 +112,25 @@ var commands = {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Join a room
|
// Join a room
|
||||||
join: new Command("join", "<room_alias>", function(room_id, args) {
|
join: new Command("join", "#alias:domain", function(room_id, args) {
|
||||||
if (args) {
|
if (args) {
|
||||||
var matches = args.match(/^(\S+)$/);
|
var matches = args.match(/^(\S+)$/);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
var room_alias = matches[1];
|
var room_alias = matches[1];
|
||||||
if (room_alias[0] !== '#') {
|
if (room_alias[0] !== '#') {
|
||||||
return reject("Usage: /join #alias:domain");
|
return reject(this.getUsage());
|
||||||
}
|
}
|
||||||
if (!room_alias.match(/:/)) {
|
if (!room_alias.match(/:/)) {
|
||||||
room_alias += ':' + MatrixClientPeg.get().getDomain();
|
room_alias += ':' + MatrixClientPeg.get().getDomain();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to find a room with this alias
|
dis.dispatch({
|
||||||
// XXX: do we need to do this? Doesn't the JS SDK suppress duplicate attempts to join the same room?
|
action: 'view_room',
|
||||||
var foundRoom = MatrixTools.getRoomForAlias(
|
room_alias: room_alias,
|
||||||
MatrixClientPeg.get().getRooms(),
|
auto_join: true,
|
||||||
room_alias
|
});
|
||||||
);
|
|
||||||
|
|
||||||
if (foundRoom) { // we've already joined this room, view it if it's not archived.
|
return success();
|
||||||
var me = foundRoom.getMember(MatrixClientPeg.get().credentials.userId);
|
|
||||||
if (me && me.membership !== "leave") {
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'view_room',
|
|
||||||
room_id: foundRoom.roomId
|
|
||||||
});
|
|
||||||
return success();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise attempt to join this alias.
|
|
||||||
return success(
|
|
||||||
MatrixClientPeg.get().joinRoom(room_alias).then(
|
|
||||||
function(room) {
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'view_room',
|
|
||||||
room_id: room.roomId
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
|
|
|
@ -24,7 +24,6 @@ var PresetValues = {
|
||||||
Custom: "custom",
|
Custom: "custom",
|
||||||
};
|
};
|
||||||
var q = require('q');
|
var q = require('q');
|
||||||
var encryption = require("../../encryption");
|
|
||||||
var sdk = require('../../index');
|
var sdk = require('../../index');
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
|
@ -108,17 +107,8 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
var deferred = cli.createRoom(options);
|
var deferred = cli.createRoom(options);
|
||||||
|
|
||||||
var response;
|
|
||||||
|
|
||||||
if (this.state.encrypt) {
|
if (this.state.encrypt) {
|
||||||
deferred = deferred.then(function(res) {
|
// TODO
|
||||||
response = res;
|
|
||||||
return encryption.enableEncryption(
|
|
||||||
cli, response.room_id, options.invite
|
|
||||||
);
|
|
||||||
}).then(function() {
|
|
||||||
return q(response) }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|
|
@ -108,10 +108,14 @@ module.exports = React.createClass({
|
||||||
return window.localStorage.getItem("mx_hs_url");
|
return window.localStorage.getItem("mx_hs_url");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return this.props.config.default_hs_url || "https://matrix.org";
|
return this.getDefaultHsUrl();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getDefaultHsUrl() {
|
||||||
|
return this.props.config.default_hs_url || "https://matrix.org";
|
||||||
|
},
|
||||||
|
|
||||||
getFallbackHsUrl: function() {
|
getFallbackHsUrl: function() {
|
||||||
return this.props.config.fallback_hs_url;
|
return this.props.config.fallback_hs_url;
|
||||||
},
|
},
|
||||||
|
@ -126,10 +130,14 @@ module.exports = React.createClass({
|
||||||
return window.localStorage.getItem("mx_is_url");
|
return window.localStorage.getItem("mx_is_url");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return this.props.config.default_is_url || "https://vector.im"
|
return this.getDefaultIsUrl();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getDefaultIsUrl() {
|
||||||
|
return this.props.config.default_is_url || "https://vector.im";
|
||||||
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
this.favicon = new Favico({animation: 'none'});
|
this.favicon = new Favico({animation: 'none'});
|
||||||
},
|
},
|
||||||
|
@ -151,8 +159,8 @@ module.exports = React.createClass({
|
||||||
this.onLoggedIn({
|
this.onLoggedIn({
|
||||||
userId: this.props.startingQueryParams.guest_user_id,
|
userId: this.props.startingQueryParams.guest_user_id,
|
||||||
accessToken: this.props.startingQueryParams.guest_access_token,
|
accessToken: this.props.startingQueryParams.guest_access_token,
|
||||||
homeserverUrl: this.props.config.default_hs_url,
|
homeserverUrl: this.getDefaultHsUrl(),
|
||||||
identityServerUrl: this.props.config.default_is_url,
|
identityServerUrl: this.getDefaultIsUrl(),
|
||||||
guest: true
|
guest: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -403,10 +411,7 @@ module.exports = React.createClass({
|
||||||
// known to be in (eg. user clicks on a room in the recents panel), supply the ID
|
// known to be in (eg. user clicks on a room in the recents panel), supply the ID
|
||||||
// If the user is clicking on a room in the context of the alias being presented
|
// If the user is clicking on a room in the context of the alias being presented
|
||||||
// to them, supply the room alias. If both are supplied, the room ID will be ignored.
|
// to them, supply the room alias. If both are supplied, the room ID will be ignored.
|
||||||
this._viewRoom(
|
this._viewRoom(payload);
|
||||||
payload.room_id, payload.room_alias, payload.show_settings, payload.event_id,
|
|
||||||
payload.third_party_invite, payload.oob_data
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case 'view_prev_room':
|
case 'view_prev_room':
|
||||||
roomIndexDelta = -1;
|
roomIndexDelta = -1;
|
||||||
|
@ -423,7 +428,7 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
roomIndex = (roomIndex + roomIndexDelta) % allRooms.length;
|
roomIndex = (roomIndex + roomIndexDelta) % allRooms.length;
|
||||||
if (roomIndex < 0) roomIndex = allRooms.length - 1;
|
if (roomIndex < 0) roomIndex = allRooms.length - 1;
|
||||||
this._viewRoom(allRooms[roomIndex].roomId);
|
this._viewRoom({ room_id: allRooms[roomIndex].roomId });
|
||||||
break;
|
break;
|
||||||
case 'view_indexed_room':
|
case 'view_indexed_room':
|
||||||
var allRooms = RoomListSorter.mostRecentActivityFirst(
|
var allRooms = RoomListSorter.mostRecentActivityFirst(
|
||||||
|
@ -431,7 +436,7 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
var roomIndex = payload.roomIndex;
|
var roomIndex = payload.roomIndex;
|
||||||
if (allRooms[roomIndex]) {
|
if (allRooms[roomIndex]) {
|
||||||
this._viewRoom(allRooms[roomIndex].roomId);
|
this._viewRoom({ room_id: allRooms[roomIndex].roomId });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'view_user_settings':
|
case 'view_user_settings':
|
||||||
|
@ -491,39 +496,45 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// switch view to the given room
|
// switch view to the given room
|
||||||
//
|
//
|
||||||
// eventId is optional and will cause a switch to the context of that
|
// @param {Object} room_info Object containing data about the room to be joined
|
||||||
// particular event.
|
// @param {string=} room_info.room_id ID of the room to join. One of room_id or room_alias must be given.
|
||||||
// @param {Object} thirdPartyInvite Object containing data about the third party
|
// @param {string=} room_info.room_alias Alias of the room to join. One of room_id or room_alias must be given.
|
||||||
|
// @param {boolean=} room_info.auto_join If true, automatically attempt to join the room if not already a member.
|
||||||
|
// @param {boolean=} room_info.show_settings Makes RoomView show the room settings dialog.
|
||||||
|
// @param {string=} room_info.event_id ID of the event in this room to show: this will cause a switch to the
|
||||||
|
// context of that particular event.
|
||||||
|
// @param {Object=} room_info.third_party_invite Object containing data about the third party
|
||||||
// we received to join the room, if any.
|
// we received to join the room, if any.
|
||||||
// @param {string} thirdPartyInvite.inviteSignUrl 3pid invite sign URL
|
// @param {string=} room_info.third_party_invite.inviteSignUrl 3pid invite sign URL
|
||||||
// @param {string} thirdPartyInvite.invitedwithEmail The email address the invite was sent to
|
// @param {string=} room_info.third_party_invite.invitedEmail The email address the invite was sent to
|
||||||
// @param {Object} oob_data Object of additional data about the room
|
// @param {Object=} room_info.oob_data Object of additional data about the room
|
||||||
// that has been passed out-of-band (eg.
|
// that has been passed out-of-band (eg.
|
||||||
// room name and avatar from an invite email)
|
// room name and avatar from an invite email)
|
||||||
_viewRoom: function(roomId, roomAlias, showSettings, eventId, thirdPartyInvite, oob_data) {
|
_viewRoom: function(room_info) {
|
||||||
// before we switch room, record the scroll state of the current room
|
// before we switch room, record the scroll state of the current room
|
||||||
this._updateScrollMap();
|
this._updateScrollMap();
|
||||||
|
|
||||||
this.focusComposer = true;
|
this.focusComposer = true;
|
||||||
|
|
||||||
var newState = {
|
var newState = {
|
||||||
initialEventId: eventId,
|
initialEventId: room_info.event_id,
|
||||||
highlightedEventId: eventId,
|
highlightedEventId: room_info.event_id,
|
||||||
initialEventPixelOffset: undefined,
|
initialEventPixelOffset: undefined,
|
||||||
page_type: this.PageTypes.RoomView,
|
page_type: this.PageTypes.RoomView,
|
||||||
thirdPartyInvite: thirdPartyInvite,
|
thirdPartyInvite: room_info.third_party_invite,
|
||||||
roomOobData: oob_data,
|
roomOobData: room_info.oob_data,
|
||||||
currentRoomAlias: roomAlias,
|
currentRoomAlias: room_info.room_alias,
|
||||||
|
autoJoin: room_info.auto_join,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!roomAlias) {
|
if (!room_info.room_alias) {
|
||||||
newState.currentRoomId = roomId;
|
newState.currentRoomId = room_info.room_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we aren't given an explicit event id, look for one in the
|
// if we aren't given an explicit event id, look for one in the
|
||||||
// scrollStateMap.
|
// scrollStateMap.
|
||||||
if (!eventId) {
|
if (!room_info.event_id) {
|
||||||
var scrollState = this.scrollStateMap[roomId];
|
var scrollState = this.scrollStateMap[room_info.room_id];
|
||||||
if (scrollState) {
|
if (scrollState) {
|
||||||
newState.initialEventId = scrollState.focussedEvent;
|
newState.initialEventId = scrollState.focussedEvent;
|
||||||
newState.initialEventPixelOffset = scrollState.pixelOffset;
|
newState.initialEventPixelOffset = scrollState.pixelOffset;
|
||||||
|
@ -536,8 +547,8 @@ module.exports = React.createClass({
|
||||||
// the new screen yet (we won't be showing it yet)
|
// the new screen yet (we won't be showing it yet)
|
||||||
// The normal case where this happens is navigating
|
// The normal case where this happens is navigating
|
||||||
// to the room in the URL bar on page load.
|
// to the room in the URL bar on page load.
|
||||||
var presentedId = roomAlias || roomId;
|
var presentedId = room_info.room_alias || room_info.room_id;
|
||||||
var room = MatrixClientPeg.get().getRoom(roomId);
|
var room = MatrixClientPeg.get().getRoom(room_info.room_id);
|
||||||
if (room) {
|
if (room) {
|
||||||
var theAlias = MatrixTools.getDisplayAliasForRoom(room);
|
var theAlias = MatrixTools.getDisplayAliasForRoom(room);
|
||||||
if (theAlias) presentedId = theAlias;
|
if (theAlias) presentedId = theAlias;
|
||||||
|
@ -553,15 +564,15 @@ module.exports = React.createClass({
|
||||||
// Tinter.tint(color_scheme.primary_color, color_scheme.secondary_color);
|
// Tinter.tint(color_scheme.primary_color, color_scheme.secondary_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventId) {
|
if (room_info.event_id) {
|
||||||
presentedId += "/"+eventId;
|
presentedId += "/"+room_info.event_id;
|
||||||
}
|
}
|
||||||
this.notifyNewScreen('room/'+presentedId);
|
this.notifyNewScreen('room/'+presentedId);
|
||||||
newState.ready = true;
|
newState.ready = true;
|
||||||
}
|
}
|
||||||
this.setState(newState);
|
this.setState(newState);
|
||||||
|
|
||||||
if (this.refs.roomView && showSettings) {
|
if (this.refs.roomView && room_info.showSettings) {
|
||||||
this.refs.roomView.showSettings(true);
|
this.refs.roomView.showSettings(true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1030,6 +1041,7 @@ module.exports = React.createClass({
|
||||||
<RoomView
|
<RoomView
|
||||||
ref="roomView"
|
ref="roomView"
|
||||||
roomAddress={this.state.currentRoomAlias || this.state.currentRoomId}
|
roomAddress={this.state.currentRoomAlias || this.state.currentRoomId}
|
||||||
|
autoJoin={this.state.autoJoin}
|
||||||
onRoomIdResolved={this.onRoomIdResolved}
|
onRoomIdResolved={this.onRoomIdResolved}
|
||||||
eventId={this.state.initialEventId}
|
eventId={this.state.initialEventId}
|
||||||
thirdPartyInvite={this.state.thirdPartyInvite}
|
thirdPartyInvite={this.state.thirdPartyInvite}
|
||||||
|
@ -1109,8 +1121,8 @@ module.exports = React.createClass({
|
||||||
email={this.props.startingQueryParams.email}
|
email={this.props.startingQueryParams.email}
|
||||||
username={this.state.upgradeUsername}
|
username={this.state.upgradeUsername}
|
||||||
guestAccessToken={this.state.guestAccessToken}
|
guestAccessToken={this.state.guestAccessToken}
|
||||||
defaultHsUrl={this.props.config.default_hs_url}
|
defaultHsUrl={this.getDefaultHsUrl()}
|
||||||
defaultIsUrl={this.props.config.default_is_url}
|
defaultIsUrl={this.getDefaultIsUrl()}
|
||||||
brand={this.props.config.brand}
|
brand={this.props.config.brand}
|
||||||
customHsUrl={this.getCurrentHsUrl()}
|
customHsUrl={this.getCurrentHsUrl()}
|
||||||
customIsUrl={this.getCurrentIsUrl()}
|
customIsUrl={this.getCurrentIsUrl()}
|
||||||
|
@ -1124,8 +1136,8 @@ module.exports = React.createClass({
|
||||||
} else if (this.state.screen == 'forgot_password') {
|
} else if (this.state.screen == 'forgot_password') {
|
||||||
return (
|
return (
|
||||||
<ForgotPassword
|
<ForgotPassword
|
||||||
defaultHsUrl={this.props.config.default_hs_url}
|
defaultHsUrl={this.getDefaultHsUrl()}
|
||||||
defaultIsUrl={this.props.config.default_is_url}
|
defaultIsUrl={this.getDefaultIsUrl()}
|
||||||
customHsUrl={this.getCurrentHsUrl()}
|
customHsUrl={this.getCurrentHsUrl()}
|
||||||
customIsUrl={this.getCurrentIsUrl()}
|
customIsUrl={this.getCurrentIsUrl()}
|
||||||
onComplete={this.onLoginClick}
|
onComplete={this.onLoginClick}
|
||||||
|
@ -1136,13 +1148,13 @@ module.exports = React.createClass({
|
||||||
<Login
|
<Login
|
||||||
onLoggedIn={this.onLoggedIn}
|
onLoggedIn={this.onLoggedIn}
|
||||||
onRegisterClick={this.onRegisterClick}
|
onRegisterClick={this.onRegisterClick}
|
||||||
defaultHsUrl={this.props.config.default_hs_url}
|
defaultHsUrl={this.getDefaultHsUrl()}
|
||||||
defaultIsUrl={this.props.config.default_is_url}
|
defaultIsUrl={this.getDefaultIsUrl()}
|
||||||
customHsUrl={this.getCurrentHsUrl()}
|
customHsUrl={this.getCurrentHsUrl()}
|
||||||
customIsUrl={this.getCurrentIsUrl()}
|
customIsUrl={this.getCurrentIsUrl()}
|
||||||
fallbackHsUrl={this.getFallbackHsUrl()}
|
fallbackHsUrl={this.getFallbackHsUrl()}
|
||||||
onForgotPasswordClick={this.onForgotPasswordClick}
|
onForgotPasswordClick={this.onForgotPasswordClick}
|
||||||
onLoginAsGuestClick={this.props.enableGuest && this.props.config && this.props.config.default_hs_url ? this._registerAsGuest.bind(this, true) : undefined}
|
onLoginAsGuestClick={this.props.enableGuest && this.props.config && this._registerAsGuest.bind(this, true)}
|
||||||
onCancelClick={ this.state.guestCreds ? this.onReturnToGuestClick : null }
|
onCancelClick={ this.state.guestCreds ? this.onReturnToGuestClick : null }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -119,6 +119,11 @@ module.exports = React.createClass({
|
||||||
guestsCanJoin: false,
|
guestsCanJoin: false,
|
||||||
canPeek: false,
|
canPeek: false,
|
||||||
|
|
||||||
|
// error object, as from the matrix client/server API
|
||||||
|
// If we failed to load information about the room,
|
||||||
|
// store the error here.
|
||||||
|
roomLoadError: null,
|
||||||
|
|
||||||
// this is true if we are fully scrolled-down, and are looking at
|
// this is true if we are fully scrolled-down, and are looking at
|
||||||
// the end of the live timeline. It has the effect of hiding the
|
// the end of the live timeline. It has the effect of hiding the
|
||||||
// 'scroll to bottom' knob, among a couple of other things.
|
// 'scroll to bottom' knob, among a couple of other things.
|
||||||
|
@ -161,10 +166,11 @@ module.exports = React.createClass({
|
||||||
roomId: result.room_id,
|
roomId: result.room_id,
|
||||||
roomLoading: !room,
|
roomLoading: !room,
|
||||||
hasUnsentMessages: this._hasUnsentMessages(room),
|
hasUnsentMessages: this._hasUnsentMessages(room),
|
||||||
}, this._updatePeeking);
|
}, this._onHaveRoom);
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
roomLoading: false,
|
roomLoading: false,
|
||||||
|
roomLoadError: err,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -174,11 +180,11 @@ module.exports = React.createClass({
|
||||||
room: room,
|
room: room,
|
||||||
roomLoading: !room,
|
roomLoading: !room,
|
||||||
hasUnsentMessages: this._hasUnsentMessages(room),
|
hasUnsentMessages: this._hasUnsentMessages(room),
|
||||||
}, this._updatePeeking);
|
}, this._onHaveRoom);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_updatePeeking: function() {
|
_onHaveRoom: function() {
|
||||||
// if this is an unknown room then we're in one of three states:
|
// if this is an unknown room then we're in one of three states:
|
||||||
// - This is a room we can peek into (search engine) (we can /peek)
|
// - This is a room we can peek into (search engine) (we can /peek)
|
||||||
// - This is a room we can publicly join or were invited to. (we can /join)
|
// - This is a room we can publicly join or were invited to. (we can /join)
|
||||||
|
@ -189,29 +195,44 @@ module.exports = React.createClass({
|
||||||
// Note that peeking works by room ID and room ID only, as opposed to joining
|
// Note that peeking works by room ID and room ID only, as opposed to joining
|
||||||
// which must be by alias or invite wherever possible (peeking currently does
|
// which must be by alias or invite wherever possible (peeking currently does
|
||||||
// not work over federation).
|
// not work over federation).
|
||||||
if (!this.state.room && this.state.roomId) {
|
|
||||||
console.log("Attempting to peek into room %s", this.state.roomId);
|
|
||||||
|
|
||||||
MatrixClientPeg.get().peekInRoom(this.state.roomId).then((room) => {
|
// NB. We peek if we are not in the room, although if we try to peek into
|
||||||
this.setState({
|
// a room in which we have a member event (ie. we've left) synapse will just
|
||||||
room: room,
|
// send us the same data as we get in the sync (ie. the last events we saw).
|
||||||
roomLoading: false,
|
var user_is_in_room = null;
|
||||||
});
|
if (this.state.room) {
|
||||||
this._onRoomLoaded(room);
|
user_is_in_room = this.state.room.hasMembershipState(
|
||||||
}, (err) => {
|
MatrixClientPeg.get().credentials.userId, 'join'
|
||||||
// This won't necessarily be a MatrixError, but we duck-type
|
);
|
||||||
// here and say if it's got an 'errcode' key with the right value,
|
}
|
||||||
// it means we can't peek.
|
|
||||||
if (err.errcode == "M_GUEST_ACCESS_FORBIDDEN") {
|
if (!user_is_in_room && this.state.roomId) {
|
||||||
// This is fine: the room just isn't peekable (we assume).
|
if (this.props.autoJoin) {
|
||||||
|
this.onJoinButtonClicked();
|
||||||
|
} else if (this.state.roomId) {
|
||||||
|
console.log("Attempting to peek into room %s", this.state.roomId);
|
||||||
|
|
||||||
|
MatrixClientPeg.get().peekInRoom(this.state.roomId).then((room) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
room: room,
|
||||||
roomLoading: false,
|
roomLoading: false,
|
||||||
});
|
});
|
||||||
} else {
|
this._onRoomLoaded(room);
|
||||||
throw err;
|
}, (err) => {
|
||||||
}
|
// This won't necessarily be a MatrixError, but we duck-type
|
||||||
}).done();
|
// here and say if it's got an 'errcode' key with the right value,
|
||||||
} else if (this.state.room) {
|
// it means we can't peek.
|
||||||
|
if (err.errcode == "M_GUEST_ACCESS_FORBIDDEN") {
|
||||||
|
// This is fine: the room just isn't peekable (we assume).
|
||||||
|
this.setState({
|
||||||
|
roomLoading: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}).done();
|
||||||
|
}
|
||||||
|
} else if (user_is_in_room) {
|
||||||
MatrixClientPeg.get().stopPeeking();
|
MatrixClientPeg.get().stopPeeking();
|
||||||
this._onRoomLoaded(this.state.room);
|
this._onRoomLoaded(this.state.room);
|
||||||
}
|
}
|
||||||
|
@ -999,7 +1020,7 @@ module.exports = React.createClass({
|
||||||
this.setState({
|
this.setState({
|
||||||
rejecting: true
|
rejecting: true
|
||||||
});
|
});
|
||||||
MatrixClientPeg.get().leave(this.props.roomAddress).done(function() {
|
MatrixClientPeg.get().leave(this.state.roomId).done(function() {
|
||||||
dis.dispatch({ action: 'view_next_room' });
|
dis.dispatch({ action: 'view_next_room' });
|
||||||
self.setState({
|
self.setState({
|
||||||
rejecting: false
|
rejecting: false
|
||||||
|
@ -1274,6 +1295,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// We have no room object for this room, only the ID.
|
// We have no room object for this room, only the ID.
|
||||||
// We've got to this room by following a link, possibly a third party invite.
|
// We've got to this room by following a link, possibly a third party invite.
|
||||||
|
var room_alias = this.props.roomAddress[0] == '#' ? this.props.roomAddress : null;
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomView">
|
<div className="mx_RoomView">
|
||||||
<RoomHeader ref="header"
|
<RoomHeader ref="header"
|
||||||
|
@ -1284,7 +1306,8 @@ module.exports = React.createClass({
|
||||||
<div className="mx_RoomView_auxPanel">
|
<div className="mx_RoomView_auxPanel">
|
||||||
<RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
|
<RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
|
||||||
onRejectClick={ this.onRejectThreepidInviteButtonClicked }
|
onRejectClick={ this.onRejectThreepidInviteButtonClicked }
|
||||||
canJoin={ true } canPreview={ false }
|
canPreview={ false } error={ this.state.roomLoadError }
|
||||||
|
roomAlias={room_alias}
|
||||||
spinner={this.state.joining}
|
spinner={this.state.joining}
|
||||||
inviterName={inviterName}
|
inviterName={inviterName}
|
||||||
invitedEmail={invitedEmail}
|
invitedEmail={invitedEmail}
|
||||||
|
@ -1322,7 +1345,7 @@ module.exports = React.createClass({
|
||||||
<RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
|
<RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
|
||||||
onRejectClick={ this.onRejectButtonClicked }
|
onRejectClick={ this.onRejectButtonClicked }
|
||||||
inviterName={ inviterName }
|
inviterName={ inviterName }
|
||||||
canJoin={ true } canPreview={ false }
|
canPreview={ false }
|
||||||
spinner={this.state.joining}
|
spinner={this.state.joining}
|
||||||
room={this.state.room}
|
room={this.state.room}
|
||||||
/>
|
/>
|
||||||
|
@ -1392,7 +1415,7 @@ module.exports = React.createClass({
|
||||||
invitedEmail = this.props.thirdPartyInvite.invitedEmail;
|
invitedEmail = this.props.thirdPartyInvite.invitedEmail;
|
||||||
}
|
}
|
||||||
aux = (
|
aux = (
|
||||||
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked} canJoin={true}
|
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
|
||||||
onRejectClick={this.onRejectThreepidInviteButtonClicked}
|
onRejectClick={this.onRejectThreepidInviteButtonClicked}
|
||||||
spinner={this.state.joining}
|
spinner={this.state.joining}
|
||||||
inviterName={inviterName}
|
inviterName={inviterName}
|
||||||
|
|
|
@ -232,7 +232,9 @@ module.exports = React.createClass({displayName: 'Login',
|
||||||
<div className="mx_Login_box">
|
<div className="mx_Login_box">
|
||||||
<LoginHeader />
|
<LoginHeader />
|
||||||
<div>
|
<div>
|
||||||
<h2>Sign in</h2>
|
<h2>Sign in
|
||||||
|
{ loader }
|
||||||
|
</h2>
|
||||||
{ this.componentForStep(this._getCurrentFlowStep()) }
|
{ this.componentForStep(this._getCurrentFlowStep()) }
|
||||||
<ServerConfig ref="serverConfig"
|
<ServerConfig ref="serverConfig"
|
||||||
withToggleButton={true}
|
withToggleButton={true}
|
||||||
|
@ -244,7 +246,6 @@ module.exports = React.createClass({displayName: 'Login',
|
||||||
onIsUrlChanged={this.onIsUrlChanged}
|
onIsUrlChanged={this.onIsUrlChanged}
|
||||||
delayTimeMs={1000}/>
|
delayTimeMs={1000}/>
|
||||||
<div className="mx_Login_error">
|
<div className="mx_Login_error">
|
||||||
{ loader }
|
|
||||||
{ this.state.errorText }
|
{ this.state.errorText }
|
||||||
</div>
|
</div>
|
||||||
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
|
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
|
||||||
|
|
|
@ -34,10 +34,15 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
value: React.PropTypes.number.isRequired,
|
value: React.PropTypes.number.isRequired,
|
||||||
|
|
||||||
// if true, the <select/> should be a 'controlled' form element and updated by React
|
// if true, the <select/> should be a 'controlled' form element and updated by React
|
||||||
// to reflect the current value, rather than left freeform.
|
// to reflect the current value, rather than left freeform.
|
||||||
// MemberInfo uses controlled; RoomSettings uses non-controlled.
|
// MemberInfo uses controlled; RoomSettings uses non-controlled.
|
||||||
controlled: React.PropTypes.bool.isRequired,
|
//
|
||||||
|
// ignored if disabled is truthy. false by default.
|
||||||
|
controlled: React.PropTypes.bool,
|
||||||
|
|
||||||
|
// should the user be able to change the value? false by default.
|
||||||
disabled: React.PropTypes.bool,
|
disabled: React.PropTypes.bool,
|
||||||
onChange: React.PropTypes.func,
|
onChange: React.PropTypes.func,
|
||||||
},
|
},
|
||||||
|
|
|
@ -139,7 +139,8 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
this._suppressReadReceiptAnimation = false;
|
this._suppressReadReceiptAnimation = false;
|
||||||
MatrixClientPeg.get().on("deviceVerified", this.onDeviceVerified);
|
MatrixClientPeg.get().on("deviceVerificationChanged",
|
||||||
|
this.onDeviceVerificationChanged);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillReceiveProps: function (nextProps) {
|
componentWillReceiveProps: function (nextProps) {
|
||||||
|
@ -163,11 +164,12 @@ module.exports = React.createClass({
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
var client = MatrixClientPeg.get();
|
var client = MatrixClientPeg.get();
|
||||||
if (client) {
|
if (client) {
|
||||||
client.removeListener("deviceVerified", this.onDeviceVerified);
|
client.removeListener("deviceVerificationChanged",
|
||||||
|
this.onDeviceVerificationChanged);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onDeviceVerified: function(userId, device) {
|
onDeviceVerificationChanged: function(userId, device) {
|
||||||
if (userId == this.props.mxEvent.getSender()) {
|
if (userId == this.props.mxEvent.getSender()) {
|
||||||
this._verifyEvent(this.props.mxEvent);
|
this._verifyEvent(this.props.mxEvent);
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,32 +36,73 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onBlockClick: function() {
|
||||||
|
MatrixClientPeg.get().setDeviceBlocked(
|
||||||
|
this.props.userId, this.props.device.id, true
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
onUnblockClick: function() {
|
||||||
|
MatrixClientPeg.get().setDeviceBlocked(
|
||||||
|
this.props.userId, this.props.device.id, false
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var indicator = null, button = null;
|
var indicator = null, blockButton = null, verifyButton = null;
|
||||||
if (this.props.device.verified) {
|
if (this.props.device.blocked) {
|
||||||
indicator = (
|
blockButton = (
|
||||||
<div className="mx_MemberDeviceInfo_verified">✔</div>
|
<div className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unblock"
|
||||||
|
onClick={this.onUnblockClick}>
|
||||||
|
Unblock
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
button = (
|
} else {
|
||||||
|
blockButton = (
|
||||||
|
<div className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_block"
|
||||||
|
onClick={this.onBlockClick}>
|
||||||
|
Block
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.device.verified) {
|
||||||
|
verifyButton = (
|
||||||
<div className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unverify"
|
<div className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unverify"
|
||||||
onClick={this.onUnverifyClick}>
|
onClick={this.onUnverifyClick}>
|
||||||
Unverify
|
Unverify
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
button = (
|
verifyButton = (
|
||||||
<div className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_verify"
|
<div className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_verify"
|
||||||
onClick={this.onVerifyClick}>
|
onClick={this.onVerifyClick}>
|
||||||
Verify
|
Verify
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.props.device.blocked) {
|
||||||
|
indicator = (
|
||||||
|
<div className="mx_MemberDeviceInfo_blocked">✖</div>
|
||||||
|
);
|
||||||
|
} else if (this.props.device.verified) {
|
||||||
|
indicator = (
|
||||||
|
<div className="mx_MemberDeviceInfo_verified">✔</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
indicator = (
|
||||||
|
<div className="mx_MemberDeviceInfo_unverified">?</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_MemberDeviceInfo">
|
<div className="mx_MemberDeviceInfo">
|
||||||
<div className="mx_MemberDeviceInfo_deviceId">{this.props.device.id}</div>
|
<div className="mx_MemberDeviceInfo_deviceId">{this.props.device.id}</div>
|
||||||
<div className="mx_MemberDeviceInfo_deviceKey">{this.props.device.key}</div>
|
|
||||||
{indicator}
|
{indicator}
|
||||||
{button}
|
{verifyButton}
|
||||||
|
{blockButton}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -70,7 +70,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
this._updateStateForNewMember(this.props.member);
|
this._updateStateForNewMember(this.props.member);
|
||||||
MatrixClientPeg.get().on("deviceVerified", this.onDeviceVerified);
|
MatrixClientPeg.get().on("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillReceiveProps: function(newProps) {
|
componentWillReceiveProps: function(newProps) {
|
||||||
|
@ -82,14 +82,14 @@ module.exports = React.createClass({
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
var client = MatrixClientPeg.get();
|
var client = MatrixClientPeg.get();
|
||||||
if (client) {
|
if (client) {
|
||||||
client.removeListener("deviceVerified", this.onDeviceVerified);
|
client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||||
}
|
}
|
||||||
if (this._cancelDeviceList) {
|
if (this._cancelDeviceList) {
|
||||||
this._cancelDeviceList();
|
this._cancelDeviceList();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onDeviceVerified: function(userId, device) {
|
onDeviceVerificationChanged: function(userId, device) {
|
||||||
if (userId == this.props.member.userId) {
|
if (userId == this.props.member.userId) {
|
||||||
// no need to re-download the whole thing; just update our copy of
|
// no need to re-download the whole thing; just update our copy of
|
||||||
// the list.
|
// the list.
|
||||||
|
@ -358,10 +358,15 @@ module.exports = React.createClass({
|
||||||
];
|
];
|
||||||
var existingRoomId;
|
var existingRoomId;
|
||||||
|
|
||||||
var currentRoom = MatrixClientPeg.get().getRoom(this.props.member.roomId);
|
// roomId can be null here because of a hack in MatrixChat.onUserClick where we
|
||||||
var currentMembers = currentRoom.getJoinedMembers();
|
// abuse this to view users rather than room members.
|
||||||
|
var currentMembers;
|
||||||
|
if (this.props.member.roomId) {
|
||||||
|
var currentRoom = MatrixClientPeg.get().getRoom(this.props.member.roomId);
|
||||||
|
currentMembers = currentRoom.getJoinedMembers();
|
||||||
|
}
|
||||||
// if we're currently in a 1:1 with this user, start a new chat
|
// if we're currently in a 1:1 with this user, start a new chat
|
||||||
if (currentMembers.length === 2 &&
|
if (currentMembers && currentMembers.length === 2 &&
|
||||||
userIds.indexOf(currentMembers[0].userId) !== -1 &&
|
userIds.indexOf(currentMembers[0].userId) !== -1 &&
|
||||||
userIds.indexOf(currentMembers[1].userId) !== -1)
|
userIds.indexOf(currentMembers[1].userId) !== -1)
|
||||||
{
|
{
|
||||||
|
@ -535,7 +540,9 @@ module.exports = React.createClass({
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h3>Devices</h3>
|
<h3>Devices</h3>
|
||||||
{devComponents}
|
<div className="mx_MemberInfo_devices">
|
||||||
|
{devComponents}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -425,27 +425,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// For now, let's just order things by timestamp. It's really annoying
|
// For now, let's just order things by timestamp. It's really annoying
|
||||||
// that a user disappears from sight just because they temporarily go offline
|
// that a user disappears from sight just because they temporarily go offline
|
||||||
/*
|
return userB.getLastActiveTs() - userA.getLastActiveTs();
|
||||||
var presenceMap = {
|
|
||||||
online: 3,
|
|
||||||
unavailable: 2,
|
|
||||||
offline: 1
|
|
||||||
};
|
|
||||||
|
|
||||||
var presenceOrdA = userA ? presenceMap[userA.presence] : 0;
|
|
||||||
var presenceOrdB = userB ? presenceMap[userB.presence] : 0;
|
|
||||||
|
|
||||||
if (presenceOrdA != presenceOrdB) {
|
|
||||||
return presenceOrdB - presenceOrdA;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
var lastActiveTsA = userA && userA.lastActiveTs ? userA.lastActiveTs : 0;
|
|
||||||
var lastActiveTsB = userB && userB.lastActiveTs ? userB.lastActiveTs : 0;
|
|
||||||
|
|
||||||
// console.log("comparing ts: " + lastActiveTsA + " and " + lastActiveTsB);
|
|
||||||
|
|
||||||
return lastActiveTsB - lastActiveTsA;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onSearchQueryChanged: function(input) {
|
onSearchQueryChanged: function(input) {
|
||||||
|
|
|
@ -33,16 +33,24 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// If invited by 3rd party invite, the email address the invite was sent to
|
// If invited by 3rd party invite, the email address the invite was sent to
|
||||||
invitedEmail: React.PropTypes.string,
|
invitedEmail: React.PropTypes.string,
|
||||||
canJoin: React.PropTypes.bool,
|
|
||||||
|
// A standard client/server API error object. If supplied, indicates that the
|
||||||
|
// caller was unable to fetch details about the room for the given reason.
|
||||||
|
error: React.PropTypes.object,
|
||||||
|
|
||||||
canPreview: React.PropTypes.bool,
|
canPreview: React.PropTypes.bool,
|
||||||
spinner: React.PropTypes.bool,
|
spinner: React.PropTypes.bool,
|
||||||
room: React.PropTypes.object,
|
room: React.PropTypes.object,
|
||||||
|
|
||||||
|
// The alias that was used to access this room, if appropriate
|
||||||
|
// If given, this will be how the room is referred to (eg.
|
||||||
|
// in error messages).
|
||||||
|
roomAlias: React.PropTypes.object,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
onJoinClick: function() {},
|
onJoinClick: function() {},
|
||||||
canJoin: false,
|
|
||||||
canPreview: true,
|
canPreview: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -115,8 +123,24 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (this.props.canJoin) {
|
else if (this.props.error) {
|
||||||
var name = this.props.room ? this.props.room.name : "";
|
var name = this.props.roomAlias || "This room";
|
||||||
|
var error;
|
||||||
|
if (this.props.error.errcode == 'M_NOT_FOUND') {
|
||||||
|
error = name + " does not exist";
|
||||||
|
} else {
|
||||||
|
error = name + " is not accessible at this time";
|
||||||
|
}
|
||||||
|
joinBlock = (
|
||||||
|
<div>
|
||||||
|
<div className="mx_RoomPreviewBar_join_text">
|
||||||
|
{ error }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var name = this.props.room ? this.props.room.name : (this.props.room_alias || "");
|
||||||
name = name ? <b>{ name }</b> : "a room";
|
name = name ? <b>{ name }</b> : "a room";
|
||||||
joinBlock = (
|
joinBlock = (
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -21,6 +21,14 @@ var sdk = require('../../../index');
|
||||||
var Modal = require('../../../Modal');
|
var Modal = require('../../../Modal');
|
||||||
var ObjectUtils = require("../../../ObjectUtils");
|
var ObjectUtils = require("../../../ObjectUtils");
|
||||||
var dis = require("../../../dispatcher");
|
var dis = require("../../../dispatcher");
|
||||||
|
var UserSettingsStore = require('../../../UserSettingsStore');
|
||||||
|
|
||||||
|
// parse a string as an integer; if the input is undefined, or cannot be parsed
|
||||||
|
// as an integer, return a default.
|
||||||
|
function parseIntWithDefault(val, def) {
|
||||||
|
var res = parseInt(val);
|
||||||
|
return isNaN(res) ? def : res;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RoomSettings',
|
displayName: 'RoomSettings',
|
||||||
|
@ -57,7 +65,7 @@ module.exports = React.createClass({
|
||||||
tags_changed: false,
|
tags_changed: false,
|
||||||
tags: tags,
|
tags: tags,
|
||||||
areNotifsMuted: areNotifsMuted,
|
areNotifsMuted: areNotifsMuted,
|
||||||
isRoomPublished: this._originalIsRoomPublished, // loaded async in componentWillMount
|
isRoomPublished: false, // loaded async in componentWillMount
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -199,11 +207,14 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log("Performing %s operations", promises.length);
|
|
||||||
|
|
||||||
// color scheme
|
// color scheme
|
||||||
promises.push(this.saveColor());
|
promises.push(this.saveColor());
|
||||||
|
|
||||||
|
// encryption
|
||||||
|
promises.push(this.saveEncryption());
|
||||||
|
|
||||||
|
console.log("Performing %s operations", promises.length);
|
||||||
return q.allSettled(promises);
|
return q.allSettled(promises);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -217,6 +228,19 @@ module.exports = React.createClass({
|
||||||
return this.refs.color_settings.saveSettings();
|
return this.refs.color_settings.saveSettings();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
saveEncryption: function () {
|
||||||
|
if (!this.refs.encrypt) { return q(); }
|
||||||
|
|
||||||
|
var encrypt = this.refs.encrypt.checked;
|
||||||
|
if (!encrypt) { return q(); }
|
||||||
|
|
||||||
|
var roomId = this.props.room.roomId;
|
||||||
|
return MatrixClientPeg.get().sendStateEvent(
|
||||||
|
roomId, "m.room.encryption",
|
||||||
|
{ algorithm: "m.olm.v1.curve25519-aes-sha2" }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
_hasDiff: function(strA, strB) {
|
_hasDiff: function(strA, strB) {
|
||||||
// treat undefined as an empty string because other components may blindly
|
// treat undefined as an empty string because other components may blindly
|
||||||
// call setName("") when there has been no diff made to the name!
|
// call setName("") when there has been no diff made to the name!
|
||||||
|
@ -359,6 +383,39 @@ module.exports = React.createClass({
|
||||||
roomState.mayClientSendStateEvent("m.room.guest_access", cli))
|
roomState.mayClientSendStateEvent("m.room.guest_access", cli))
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_renderEncryptionSection: function() {
|
||||||
|
if (!UserSettingsStore.isFeatureEnabled("e2e_encryption")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cli = MatrixClientPeg.get();
|
||||||
|
var roomState = this.props.room.currentState;
|
||||||
|
var isEncrypted = cli.isRoomEncrypted(this.props.room.roomId);
|
||||||
|
|
||||||
|
var text = "Encryption is " + (isEncrypted ? "" : "not ") +
|
||||||
|
"enabled in this room.";
|
||||||
|
|
||||||
|
var button;
|
||||||
|
if (!isEncrypted &&
|
||||||
|
roomState.mayClientSendStateEvent("m.room.encryption", cli)) {
|
||||||
|
button = (
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" ref="encrypt" />
|
||||||
|
Enable encryption (warning: cannot be disabled again!)
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx_RoomSettings_toggles">
|
||||||
|
<h3>Encryption</h3>
|
||||||
|
<label>{text}</label>
|
||||||
|
{button}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
// TODO: go through greying out things you don't have permission to change
|
// TODO: go through greying out things you don't have permission to change
|
||||||
// (or turning them into informative stuff)
|
// (or turning them into informative stuff)
|
||||||
|
@ -368,58 +425,29 @@ module.exports = React.createClass({
|
||||||
var EditableText = sdk.getComponent('elements.EditableText');
|
var EditableText = sdk.getComponent('elements.EditableText');
|
||||||
var PowerSelector = sdk.getComponent('elements.PowerSelector');
|
var PowerSelector = sdk.getComponent('elements.PowerSelector');
|
||||||
|
|
||||||
var power_levels = this.props.room.currentState.getStateEvents('m.room.power_levels', '');
|
|
||||||
var events_levels = (power_levels ? power_levels.getContent().events : {}) || {};
|
|
||||||
var cli = MatrixClientPeg.get();
|
var cli = MatrixClientPeg.get();
|
||||||
var roomState = this.props.room.currentState;
|
var roomState = this.props.room.currentState;
|
||||||
var user_id = cli.credentials.userId;
|
var user_id = cli.credentials.userId;
|
||||||
|
|
||||||
if (power_levels) {
|
var power_level_event = roomState.getStateEvents('m.room.power_levels', '');
|
||||||
power_levels = power_levels.getContent();
|
var power_levels = power_level_event ? power_level_event.getContent() : {};
|
||||||
|
var events_levels = power_levels.events || {};
|
||||||
|
var user_levels = power_levels.users || {};
|
||||||
|
|
||||||
var ban_level = parseInt(power_levels.ban);
|
var ban_level = parseIntWithDefault(power_levels.ban, 50);
|
||||||
var kick_level = parseInt(power_levels.kick);
|
var kick_level = parseIntWithDefault(power_levels.kick, 50);
|
||||||
var redact_level = parseInt(power_levels.redact);
|
var redact_level = parseIntWithDefault(power_levels.redact, 50);
|
||||||
var invite_level = parseInt(power_levels.invite || 0);
|
var invite_level = parseIntWithDefault(power_levels.invite, 50);
|
||||||
var send_level = parseInt(power_levels.events_default || 0);
|
var send_level = parseIntWithDefault(power_levels.events_default, 0);
|
||||||
var state_level = parseInt(power_levels.state_default || 50);
|
var state_level = power_level_event ? parseIntWithDefault(power_levels.state_default, 50) : 0;
|
||||||
var default_user_level = parseInt(power_levels.users_default || 0);
|
var default_user_level = parseIntWithDefault(power_levels.users_default, 0);
|
||||||
|
|
||||||
if (power_levels.ban == undefined) ban_level = 50;
|
var current_user_level = user_levels[user_id];
|
||||||
if (power_levels.kick == undefined) kick_level = 50;
|
if (current_user_level === undefined) {
|
||||||
if (power_levels.redact == undefined) redact_level = 50;
|
current_user_level = default_user_level;
|
||||||
|
|
||||||
var user_levels = power_levels.users || {};
|
|
||||||
|
|
||||||
var current_user_level = user_levels[user_id];
|
|
||||||
if (current_user_level == undefined) current_user_level = default_user_level;
|
|
||||||
|
|
||||||
var power_level_level = events_levels["m.room.power_levels"];
|
|
||||||
if (power_level_level == undefined) {
|
|
||||||
power_level_level = state_level;
|
|
||||||
}
|
|
||||||
|
|
||||||
var can_change_levels = current_user_level >= power_level_level;
|
|
||||||
} else {
|
|
||||||
var ban_level = 50;
|
|
||||||
var kick_level = 50;
|
|
||||||
var redact_level = 50;
|
|
||||||
var invite_level = 0;
|
|
||||||
var send_level = 0;
|
|
||||||
var state_level = 0;
|
|
||||||
var default_user_level = 0;
|
|
||||||
|
|
||||||
var user_levels = [];
|
|
||||||
var events_levels = [];
|
|
||||||
|
|
||||||
var current_user_level = 0;
|
|
||||||
|
|
||||||
var power_level_level = 0;
|
|
||||||
|
|
||||||
var can_change_levels = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var state_default = (parseInt(power_levels ? power_levels.state_default : 0) || 0);
|
var can_change_levels = roomState.mayClientSendStateEvent("m.room.power_levels", cli);
|
||||||
|
|
||||||
var canSetTag = !cli.isGuest();
|
var canSetTag = !cli.isGuest();
|
||||||
|
|
||||||
|
@ -609,10 +637,6 @@ module.exports = React.createClass({
|
||||||
Members only (since they joined)
|
Members only (since they joined)
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<label className="mx_RoomSettings_encrypt">
|
|
||||||
<input type="checkbox" />
|
|
||||||
Encrypt room
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
@ -677,6 +701,8 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
{ bannedUsersSection }
|
{ bannedUsersSection }
|
||||||
|
|
||||||
|
{ this._renderEncryptionSection() }
|
||||||
|
|
||||||
<h3>Advanced</h3>
|
<h3>Advanced</h3>
|
||||||
<div className="mx_RoomSettings_settings">
|
<div className="mx_RoomSettings_settings">
|
||||||
This room's internal ID is <code>{ this.props.room.roomId }</code>
|
This room's internal ID is <code>{ this.props.room.roomId }</code>
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015, 2016 OpenMarket 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function enableEncyption(client, roomId, members) {
|
|
||||||
members = members.slice(0);
|
|
||||||
members.push(client.credentials.userId);
|
|
||||||
// TODO: Check the keys actually match what keys the user has.
|
|
||||||
// TODO: Don't redownload keys each time.
|
|
||||||
return client.downloadKeys(members, "forceDownload").then(function(res) {
|
|
||||||
return client.setRoomEncryption(roomId, {
|
|
||||||
algorithm: "m.olm.v1.curve25519-aes-sha2",
|
|
||||||
members: members,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function disableEncryption(client, roomId) {
|
|
||||||
return client.disableRoomEncryption(roomId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
enableEncryption: enableEncyption,
|
|
||||||
disableEncryption: disableEncryption,
|
|
||||||
}
|
|
|
@ -210,7 +210,7 @@ describe('TimelinePanel', function() {
|
||||||
var N_EVENTS = 600;
|
var N_EVENTS = 600;
|
||||||
|
|
||||||
// sadly, loading all those events takes a while
|
// sadly, loading all those events takes a while
|
||||||
this.timeout(N_EVENTS * 20);
|
this.timeout(N_EVENTS * 40);
|
||||||
|
|
||||||
// client.getRoom is called a /lot/ in this test, so replace
|
// client.getRoom is called a /lot/ in this test, so replace
|
||||||
// sinon's spy with a fast noop.
|
// sinon's spy with a fast noop.
|
||||||
|
@ -220,12 +220,14 @@ describe('TimelinePanel', function() {
|
||||||
for (var i = 0; i < N_EVENTS; i++) {
|
for (var i = 0; i < N_EVENTS; i++) {
|
||||||
timeline.addEvent(mkMessage());
|
timeline.addEvent(mkMessage());
|
||||||
}
|
}
|
||||||
|
console.log("added events to timeline");
|
||||||
|
|
||||||
var scrollDefer;
|
var scrollDefer;
|
||||||
var panel = ReactDOM.render(
|
var panel = ReactDOM.render(
|
||||||
<TimelinePanel room={room} onScroll={()=>{scrollDefer.resolve()}} />,
|
<TimelinePanel room={room} onScroll={()=>{scrollDefer.resolve()}} />,
|
||||||
parentDiv
|
parentDiv
|
||||||
);
|
);
|
||||||
|
console.log("TimelinePanel rendered");
|
||||||
|
|
||||||
var messagePanel = ReactTestUtils.findRenderedComponentWithType(
|
var messagePanel = ReactTestUtils.findRenderedComponentWithType(
|
||||||
panel, sdk.getComponent('structures.MessagePanel'));
|
panel, sdk.getComponent('structures.MessagePanel'));
|
||||||
|
@ -246,6 +248,7 @@ describe('TimelinePanel', function() {
|
||||||
// need to go further
|
// need to go further
|
||||||
return backPaginate();
|
return backPaginate();
|
||||||
}
|
}
|
||||||
|
console.log("paginated to end.");
|
||||||
|
|
||||||
// hopefully, we got to the start of the timeline
|
// hopefully, we got to the start of the timeline
|
||||||
expect(messagePanel.props.backPaginating).toBe(false);
|
expect(messagePanel.props.backPaginating).toBe(false);
|
||||||
|
@ -259,6 +262,7 @@ describe('TimelinePanel', function() {
|
||||||
expect(messagePanel.props.suppressFirstDateSeparator).toBe(true);
|
expect(messagePanel.props.suppressFirstDateSeparator).toBe(true);
|
||||||
|
|
||||||
// back-paginate until we hit the start
|
// back-paginate until we hit the start
|
||||||
|
console.log("back paginating...");
|
||||||
return backPaginate();
|
return backPaginate();
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
expect(messagePanel.props.suppressFirstDateSeparator).toBe(false);
|
expect(messagePanel.props.suppressFirstDateSeparator).toBe(false);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue