Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into forward_message
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
commit
a2ab36f598
58 changed files with 844 additions and 1044 deletions
|
@ -64,7 +64,7 @@ module.exports = {
|
||||||
// to JSX.
|
// to JSX.
|
||||||
ignorePattern: '^\\s*<',
|
ignorePattern: '^\\s*<',
|
||||||
ignoreComments: true,
|
ignoreComments: true,
|
||||||
code: 90,
|
code: 120,
|
||||||
}],
|
}],
|
||||||
"valid-jsdoc": ["warn"],
|
"valid-jsdoc": ["warn"],
|
||||||
"new-cap": ["warn"],
|
"new-cap": ["warn"],
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -11,3 +11,4 @@ npm-debug.log
|
||||||
/karma-reports
|
/karma-reports
|
||||||
|
|
||||||
/.idea
|
/.idea
|
||||||
|
/src/component-index.js
|
||||||
|
|
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -1,3 +1,24 @@
|
||||||
|
Changes in [0.8.8](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.8.8) (2017-04-25)
|
||||||
|
===================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.8.8-rc.2...v0.8.8)
|
||||||
|
|
||||||
|
* No changes
|
||||||
|
|
||||||
|
|
||||||
|
Changes in [0.8.8-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.8.8-rc.2) (2017-04-24)
|
||||||
|
=============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.8.8-rc.1...v0.8.8-rc.2)
|
||||||
|
|
||||||
|
* Fix bug where links to Riot would fail to open.
|
||||||
|
|
||||||
|
|
||||||
|
Changes in [0.8.8-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.8.8-rc.1) (2017-04-21)
|
||||||
|
=============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.8.7...v0.8.8-rc.1)
|
||||||
|
|
||||||
|
* Update js-sdk to fix registration without a captcha (https://github.com/vector-im/riot-web/issues/3621)
|
||||||
|
|
||||||
|
|
||||||
Changes in [0.8.7](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.8.7) (2017-04-12)
|
Changes in [0.8.7](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.8.7) (2017-04-12)
|
||||||
===================================================================================================
|
===================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.8.7-rc.4...v0.8.7)
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.8.7-rc.4...v0.8.7)
|
||||||
|
|
|
@ -69,25 +69,41 @@ General Style
|
||||||
console.log("I am a fish"); // Bad
|
console.log("I am a fish"); // Bad
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
- No new line before else, catch, finally, etc:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (x) {
|
||||||
|
console.log("I am a fish");
|
||||||
|
} else {
|
||||||
|
console.log("I am a chimp"); // Good
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x) {
|
||||||
|
console.log("I am a fish");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("I am a chimp"); // Bad
|
||||||
|
}
|
||||||
|
```
|
||||||
- Declare one variable per var statement (consistent with Node). Unless they
|
- Declare one variable per var statement (consistent with Node). Unless they
|
||||||
are simple and closely related. If you put the next declaration on a new line,
|
are simple and closely related. If you put the next declaration on a new line,
|
||||||
treat yourself to another `var`:
|
treat yourself to another `var`:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var key = "foo",
|
const key = "foo",
|
||||||
comparator = function(x, y) {
|
comparator = function(x, y) {
|
||||||
return x - y;
|
return x - y;
|
||||||
}; // Bad
|
}; // Bad
|
||||||
|
|
||||||
var key = "foo";
|
const key = "foo";
|
||||||
var comparator = function(x, y) {
|
const comparator = function(x, y) {
|
||||||
return x - y;
|
return x - y;
|
||||||
}; // Good
|
}; // Good
|
||||||
|
|
||||||
var x = 0, y = 0; // Fine
|
let x = 0, y = 0; // Fine
|
||||||
|
|
||||||
var x = 0;
|
let x = 0;
|
||||||
var y = 0; // Also fine
|
let y = 0; // Also fine
|
||||||
```
|
```
|
||||||
- A single line `if` is fine, all others have braces. This prevents errors when adding to the code.:
|
- A single line `if` is fine, all others have braces. This prevents errors when adding to the code.:
|
||||||
|
|
||||||
|
|
1
header
1
header
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|
16
package.json
16
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "0.8.7",
|
"version": "0.8.8",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -31,9 +31,11 @@
|
||||||
"reskindex": "scripts/reskindex.js"
|
"reskindex": "scripts/reskindex.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"reskindex": "scripts/reskindex.js -h header",
|
"reskindex": "node scripts/reskindex.js -h header",
|
||||||
"build": "node scripts/babelcheck.js && babel src -d lib --source-maps",
|
"reskindex:watch": "node scripts/reskindex.js -h header -w",
|
||||||
"start": "node scripts/babelcheck.js && babel src -w -d lib --source-maps",
|
"build": "npm run reskindex && babel src -d lib --source-maps",
|
||||||
|
"build:watch": "babel src -w -d lib --source-maps",
|
||||||
|
"start": "parallelshell \"npm run build:watch\" \"npm run reskindex:watch\"",
|
||||||
"lint": "eslint src/",
|
"lint": "eslint src/",
|
||||||
"lintall": "eslint src/ test/",
|
"lintall": "eslint src/ test/",
|
||||||
"clean": "rimraf lib",
|
"clean": "rimraf lib",
|
||||||
|
@ -53,7 +55,7 @@
|
||||||
"draft-js-export-markdown": "^0.2.0",
|
"draft-js-export-markdown": "^0.2.0",
|
||||||
"emojione": "2.2.3",
|
"emojione": "2.2.3",
|
||||||
"file-saver": "^1.3.3",
|
"file-saver": "^1.3.3",
|
||||||
"filesize": "^3.1.2",
|
"filesize": "3.5.6",
|
||||||
"flux": "^2.0.3",
|
"flux": "^2.0.3",
|
||||||
"fuse.js": "^2.2.0",
|
"fuse.js": "^2.2.0",
|
||||||
"glob": "^5.0.14",
|
"glob": "^5.0.14",
|
||||||
|
@ -67,7 +69,7 @@
|
||||||
"react": "^15.4.0",
|
"react": "^15.4.0",
|
||||||
"react-addons-css-transition-group": "15.3.2",
|
"react-addons-css-transition-group": "15.3.2",
|
||||||
"react-dom": "^15.4.0",
|
"react-dom": "^15.4.0",
|
||||||
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#39d858c",
|
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#5e97aef",
|
||||||
"sanitize-html": "^1.11.1",
|
"sanitize-html": "^1.11.1",
|
||||||
"text-encoding-utf-8": "^1.0.1",
|
"text-encoding-utf-8": "^1.0.1",
|
||||||
"velocity-vector": "vector-im/velocity#059e3b2",
|
"velocity-vector": "vector-im/velocity#059e3b2",
|
||||||
|
@ -88,6 +90,7 @@
|
||||||
"babel-preset-es2016": "^6.11.3",
|
"babel-preset-es2016": "^6.11.3",
|
||||||
"babel-preset-es2017": "^6.14.0",
|
"babel-preset-es2017": "^6.14.0",
|
||||||
"babel-preset-react": "^6.11.1",
|
"babel-preset-react": "^6.11.1",
|
||||||
|
"chokidar": "^1.6.1",
|
||||||
"eslint": "^3.13.1",
|
"eslint": "^3.13.1",
|
||||||
"eslint-config-google": "^0.7.1",
|
"eslint-config-google": "^0.7.1",
|
||||||
"eslint-plugin-babel": "^4.0.1",
|
"eslint-plugin-babel": "^4.0.1",
|
||||||
|
@ -104,6 +107,7 @@
|
||||||
"karma-sourcemap-loader": "^0.3.7",
|
"karma-sourcemap-loader": "^0.3.7",
|
||||||
"karma-webpack": "^1.7.0",
|
"karma-webpack": "^1.7.0",
|
||||||
"mocha": "^2.4.5",
|
"mocha": "^2.4.5",
|
||||||
|
"parallelshell": "^1.2.0",
|
||||||
"phantomjs-prebuilt": "^2.1.7",
|
"phantomjs-prebuilt": "^2.1.7",
|
||||||
"react-addons-test-utils": "^15.4.0",
|
"react-addons-test-utils": "^15.4.0",
|
||||||
"require-json": "0.0.1",
|
"require-json": "0.0.1",
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
var exec = require('child_process').exec;
|
|
||||||
|
|
||||||
// Makes sure the babel executable in the path is babel 6 (or greater), not
|
|
||||||
// babel 5, which it is if you upgrade from an older version of react-sdk and
|
|
||||||
// run 'npm install' since the package has changed to babel-cli, so 'babel'
|
|
||||||
// remains installed and the executable in node_modules/.bin remains as babel
|
|
||||||
// 5.
|
|
||||||
|
|
||||||
exec("babel -V", function (error, stdout, stderr) {
|
|
||||||
if ((error && error.code) || parseInt(stdout.substr(0,1), 10) < 6) {
|
|
||||||
console.log("\033[31m\033[1m"+
|
|
||||||
'*****************************************\n'+
|
|
||||||
'* matrix-react-sdk has moved to babel 6 *\n'+
|
|
||||||
'* Please "rm -rf node_modules && npm i" *\n'+
|
|
||||||
'* then restore links as appropriate *\n'+
|
|
||||||
'*****************************************\n'+
|
|
||||||
"\033[91m");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,44 +1,50 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var glob = require('glob');
|
var glob = require('glob');
|
||||||
|
|
||||||
var args = require('optimist').argv;
|
var args = require('optimist').argv;
|
||||||
|
var chokidar = require('chokidar');
|
||||||
var header = args.h || args.header;
|
|
||||||
|
|
||||||
var componentsDir = path.join('src', 'components');
|
|
||||||
|
|
||||||
var componentIndex = path.join('src', 'component-index.js');
|
var componentIndex = path.join('src', 'component-index.js');
|
||||||
|
var componentsDir = path.join('src', 'components');
|
||||||
|
var componentGlob = '**/*.js';
|
||||||
|
var prevFiles = [];
|
||||||
|
|
||||||
var packageJson = JSON.parse(fs.readFileSync('./package.json'));
|
function reskindex() {
|
||||||
|
var files = glob.sync(componentGlob, {cwd: componentsDir}).sort();
|
||||||
|
if (!filesHaveChanged(files, prevFiles)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
prevFiles = files;
|
||||||
|
|
||||||
var strm = fs.createWriteStream(componentIndex);
|
var header = args.h || args.header;
|
||||||
|
var packageJson = JSON.parse(fs.readFileSync('./package.json'));
|
||||||
|
|
||||||
if (header) {
|
var strm = fs.createWriteStream(componentIndex);
|
||||||
|
|
||||||
|
if (header) {
|
||||||
strm.write(fs.readFileSync(header));
|
strm.write(fs.readFileSync(header));
|
||||||
strm.write('\n');
|
strm.write('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
strm.write("/*\n");
|
strm.write("/*\n");
|
||||||
strm.write(" * THIS FILE IS AUTO-GENERATED\n");
|
strm.write(" * THIS FILE IS AUTO-GENERATED\n");
|
||||||
strm.write(" * You can edit it you like, but your changes will be overwritten,\n");
|
strm.write(" * You can edit it you like, but your changes will be overwritten,\n");
|
||||||
strm.write(" * so you'd just be trying to swim upstream like a salmon.\n");
|
strm.write(" * so you'd just be trying to swim upstream like a salmon.\n");
|
||||||
strm.write(" * You are not a salmon.\n");
|
strm.write(" * You are not a salmon.\n");
|
||||||
strm.write(" *\n");
|
strm.write(" */\n\n");
|
||||||
strm.write(" * To update it, run:\n");
|
|
||||||
strm.write(" * ./reskindex.js -h header\n");
|
|
||||||
strm.write(" */\n\n");
|
|
||||||
|
|
||||||
if (packageJson['matrix-react-parent']) {
|
if (packageJson['matrix-react-parent']) {
|
||||||
strm.write("module.exports.components = require('"+packageJson['matrix-react-parent']+"/lib/component-index').components;\n\n");
|
strm.write(
|
||||||
} else {
|
"module.exports.components = require('"+
|
||||||
|
packageJson['matrix-react-parent']+
|
||||||
|
"/lib/component-index').components;\n\n"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
strm.write("module.exports.components = {};\n");
|
strm.write("module.exports.components = {};\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
var files = glob.sync('**/*.js', {cwd: componentsDir}).sort();
|
for (var i = 0; i < files.length; ++i) {
|
||||||
for (var i = 0; i < files.length; ++i) {
|
|
||||||
var file = files[i].replace('.js', '');
|
var file = files[i].replace('.js', '');
|
||||||
|
|
||||||
var moduleName = (file.replace(/\//g, '.'));
|
var moduleName = (file.replace(/\//g, '.'));
|
||||||
|
@ -48,6 +54,35 @@ for (var i = 0; i < files.length; ++i) {
|
||||||
strm.write(importName + " && (module.exports.components['"+moduleName+"'] = " + importName + ");");
|
strm.write(importName + " && (module.exports.components['"+moduleName+"'] = " + importName + ");");
|
||||||
strm.write('\n');
|
strm.write('\n');
|
||||||
strm.uncork();
|
strm.uncork();
|
||||||
|
}
|
||||||
|
|
||||||
|
strm.end();
|
||||||
|
console.log('Reskindex: completed');
|
||||||
}
|
}
|
||||||
|
|
||||||
strm.end();
|
// Expects both arrays of file names to be sorted
|
||||||
|
function filesHaveChanged(files, prevFiles) {
|
||||||
|
if (files.length !== prevFiles.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Check for name changes
|
||||||
|
for (var i = 0; i < files.length; i++) {
|
||||||
|
if (prevFiles[i] !== files[i]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -w indicates watch mode where any FS events will trigger reskindex
|
||||||
|
if (!args.w) {
|
||||||
|
reskindex();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var watchDebouncer = null;
|
||||||
|
chokidar.watch(path.join(componentsDir, componentGlob)).on('all', (event, path) => {
|
||||||
|
if (path === componentIndex) return;
|
||||||
|
if (watchDebouncer) clearTimeout(watchDebouncer);
|
||||||
|
watchDebouncer = setTimeout(reskindex, 1000);
|
||||||
|
});
|
||||||
|
|
|
@ -22,8 +22,8 @@ module.exports = {
|
||||||
avatarUrlForMember: function(member, width, height, resizeMethod) {
|
avatarUrlForMember: function(member, width, height, resizeMethod) {
|
||||||
var url = member.getAvatarUrl(
|
var url = member.getAvatarUrl(
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
MatrixClientPeg.get().getHomeserverUrl(),
|
||||||
width,
|
Math.floor(width * window.devicePixelRatio),
|
||||||
height,
|
Math.floor(height * window.devicePixelRatio),
|
||||||
resizeMethod,
|
resizeMethod,
|
||||||
false,
|
false,
|
||||||
false
|
false
|
||||||
|
@ -40,7 +40,9 @@ module.exports = {
|
||||||
avatarUrlForUser: function(user, width, height, resizeMethod) {
|
avatarUrlForUser: function(user, width, height, resizeMethod) {
|
||||||
var url = ContentRepo.getHttpUriForMxc(
|
var url = ContentRepo.getHttpUriForMxc(
|
||||||
MatrixClientPeg.get().getHomeserverUrl(), user.avatarUrl,
|
MatrixClientPeg.get().getHomeserverUrl(), user.avatarUrl,
|
||||||
width, height, resizeMethod
|
Math.floor(width * window.devicePixelRatio),
|
||||||
|
Math.floor(height * window.devicePixelRatio),
|
||||||
|
resizeMethod
|
||||||
);
|
);
|
||||||
if (!url || url.length === 0) {
|
if (!url || url.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -57,4 +59,3 @@ module.exports = {
|
||||||
return 'img/' + images[total % images.length] + '.png';
|
return 'img/' + images[total % images.length] + '.png';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
/*
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// singleton which dispatches invocations of a given type & argument
|
|
||||||
// rather than just a type (as per EventEmitter and Flux's dispatcher etc)
|
|
||||||
//
|
|
||||||
// This means you can have a single point which listens for an EventEmitter event
|
|
||||||
// and then dispatches out to one of thousands of RoomTiles (for instance) rather than
|
|
||||||
// having each RoomTile register for the EventEmitter event and having to
|
|
||||||
// iterate over all of them.
|
|
||||||
class ConstantTimeDispatcher {
|
|
||||||
constructor() {
|
|
||||||
// type -> arg -> [ listener(arg, params) ]
|
|
||||||
this.listeners = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
register(type, arg, listener) {
|
|
||||||
if (!this.listeners[type]) this.listeners[type] = {};
|
|
||||||
if (!this.listeners[type][arg]) this.listeners[type][arg] = [];
|
|
||||||
this.listeners[type][arg].push(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
unregister(type, arg, listener) {
|
|
||||||
if (this.listeners[type] && this.listeners[type][arg]) {
|
|
||||||
var i = this.listeners[type][arg].indexOf(listener);
|
|
||||||
if (i > -1) {
|
|
||||||
this.listeners[type][arg].splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.warn("Unregistering unrecognised listener (type=" + type + ", arg=" + arg + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(type, arg, params) {
|
|
||||||
if (!this.listeners[type] || !this.listeners[type][arg]) {
|
|
||||||
console.warn("No registered listeners for dispatch (type=" + type + ", arg=" + arg + ")");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.listeners[type][arg].forEach(listener=>{
|
|
||||||
listener.call(arg, params);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!global.constantTimeDispatcher) {
|
|
||||||
global.constantTimeDispatcher = new ConstantTimeDispatcher();
|
|
||||||
}
|
|
||||||
module.exports = global.constantTimeDispatcher;
|
|
|
@ -19,13 +19,14 @@ limitations under the License.
|
||||||
var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||||
var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||||
|
|
||||||
|
function pad(n) {
|
||||||
|
return (n < 10 ? '0' : '') + n;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
formatDate: function(date) {
|
formatDate: function(date) {
|
||||||
// date.toLocaleTimeString is completely system dependent.
|
// date.toLocaleTimeString is completely system dependent.
|
||||||
// just go 24h for now
|
// just go 24h for now
|
||||||
function pad(n) {
|
|
||||||
return (n < 10 ? '0' : '') + n;
|
|
||||||
}
|
|
||||||
|
|
||||||
var now = new Date();
|
var now = new Date();
|
||||||
if (date.toDateString() === now.toDateString()) {
|
if (date.toDateString() === now.toDateString()) {
|
||||||
|
@ -34,19 +35,20 @@ module.exports = {
|
||||||
else if (now.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) {
|
else if (now.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) {
|
||||||
return days[date.getDay()] + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
return days[date.getDay()] + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||||
}
|
}
|
||||||
else /* if (now.getFullYear() === date.getFullYear()) */ {
|
else if (now.getFullYear() === date.getFullYear()) {
|
||||||
return days[date.getDay()] + ", " + months[date.getMonth()] + " " + date.getDate() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
return days[date.getDay()] + ", " + months[date.getMonth()] + " " + date.getDate() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
else {
|
else {
|
||||||
return days[date.getDay()] + ", " + months[date.getMonth()] + " " + date.getDate() + " " + date.getFullYear() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
return this.formatFullDate(date);
|
||||||
}
|
}
|
||||||
*/
|
},
|
||||||
|
|
||||||
|
formatFullDate: function(date) {
|
||||||
|
return days[date.getDay()] + ", " + months[date.getMonth()] + " " + date.getDate() + " " + date.getFullYear() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||||
},
|
},
|
||||||
|
|
||||||
formatTime: function(date) {
|
formatTime: function(date) {
|
||||||
//return pad(date.getHours()) + ':' + pad(date.getMinutes());
|
return pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||||
return ('00' + date.getHours()).slice(-2) + ':' + ('00' + date.getMinutes()).slice(-2);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -111,8 +111,7 @@ var sanitizeHtmlParams = {
|
||||||
allowedTags: [
|
allowedTags: [
|
||||||
'font', // custom to matrix for IRC-style font coloring
|
'font', // custom to matrix for IRC-style font coloring
|
||||||
'del', // for markdown
|
'del', // for markdown
|
||||||
// deliberately no h1/h2 to stop people shouting.
|
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
|
||||||
'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
|
|
||||||
'nl', 'li', 'b', 'i', 'u', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div',
|
'nl', 'li', 'b', 'i', 'u', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div',
|
||||||
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'span',
|
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'span',
|
||||||
],
|
],
|
||||||
|
@ -149,7 +148,7 @@ var sanitizeHtmlParams = {
|
||||||
attribs.href = m[1];
|
attribs.href = m[1];
|
||||||
delete attribs.target;
|
delete attribs.target;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
m = attribs.href.match(linkifyMatrix.MATRIXTO_URL_PATTERN);
|
m = attribs.href.match(linkifyMatrix.MATRIXTO_URL_PATTERN);
|
||||||
if (m) {
|
if (m) {
|
||||||
var entity = m[1];
|
var entity = m[1];
|
||||||
|
@ -162,6 +161,7 @@ var sanitizeHtmlParams = {
|
||||||
delete attribs.target;
|
delete attribs.target;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
attribs.rel = 'noopener'; // https://mathiasbynens.github.io/rel-noopener/
|
attribs.rel = 'noopener'; // https://mathiasbynens.github.io/rel-noopener/
|
||||||
return { tagName: tagName, attribs : attribs };
|
return { tagName: tagName, attribs : attribs };
|
||||||
},
|
},
|
||||||
|
|
|
@ -32,5 +32,4 @@ module.exports = {
|
||||||
DELETE: 46,
|
DELETE: 46,
|
||||||
KEY_D: 68,
|
KEY_D: 68,
|
||||||
KEY_E: 69,
|
KEY_E: 69,
|
||||||
KEY_K: 75,
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -49,7 +49,7 @@ import sdk from './index';
|
||||||
* If any of steps 1-4 are successful, it will call {setLoggedIn}, which in
|
* If any of steps 1-4 are successful, it will call {setLoggedIn}, which in
|
||||||
* turn will raise on_logged_in and will_start_client events.
|
* turn will raise on_logged_in and will_start_client events.
|
||||||
*
|
*
|
||||||
* It returns a promise which resolves when the above process completes.
|
* @param {object} opts
|
||||||
*
|
*
|
||||||
* @param {object} opts.realQueryParams: string->string map of the
|
* @param {object} opts.realQueryParams: string->string map of the
|
||||||
* query-parameters extracted from the real query-string of the starting
|
* query-parameters extracted from the real query-string of the starting
|
||||||
|
@ -67,6 +67,7 @@ import sdk from './index';
|
||||||
* @params {string} opts.guestIsUrl: homeserver URL. Only used if enableGuest is
|
* @params {string} opts.guestIsUrl: homeserver URL. Only used if enableGuest is
|
||||||
* true; defines the IS to use.
|
* true; defines the IS to use.
|
||||||
*
|
*
|
||||||
|
* @returns {Promise} a promise which resolves when the above process completes.
|
||||||
*/
|
*/
|
||||||
export function loadSession(opts) {
|
export function loadSession(opts) {
|
||||||
const realQueryParams = opts.realQueryParams || {};
|
const realQueryParams = opts.realQueryParams || {};
|
||||||
|
@ -127,7 +128,7 @@ export function loadSession(opts) {
|
||||||
|
|
||||||
function _loginWithToken(queryParams, defaultDeviceDisplayName) {
|
function _loginWithToken(queryParams, defaultDeviceDisplayName) {
|
||||||
// create a temporary MatrixClient to do the login
|
// create a temporary MatrixClient to do the login
|
||||||
var client = Matrix.createClient({
|
const client = Matrix.createClient({
|
||||||
baseUrl: queryParams.homeserver,
|
baseUrl: queryParams.homeserver,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -159,7 +160,7 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
|
||||||
// Not really sure where the right home for it is.
|
// Not really sure where the right home for it is.
|
||||||
|
|
||||||
// create a temporary MatrixClient to do the login
|
// create a temporary MatrixClient to do the login
|
||||||
var client = Matrix.createClient({
|
const client = Matrix.createClient({
|
||||||
baseUrl: hsUrl,
|
baseUrl: hsUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -188,30 +189,30 @@ function _restoreFromLocalStorage() {
|
||||||
if (!localStorage) {
|
if (!localStorage) {
|
||||||
return q(false);
|
return q(false);
|
||||||
}
|
}
|
||||||
const hs_url = localStorage.getItem("mx_hs_url");
|
const hsUrl = localStorage.getItem("mx_hs_url");
|
||||||
const is_url = localStorage.getItem("mx_is_url") || 'https://matrix.org';
|
const isUrl = localStorage.getItem("mx_is_url") || 'https://matrix.org';
|
||||||
const access_token = localStorage.getItem("mx_access_token");
|
const accessToken = localStorage.getItem("mx_access_token");
|
||||||
const user_id = localStorage.getItem("mx_user_id");
|
const userId = localStorage.getItem("mx_user_id");
|
||||||
const device_id = localStorage.getItem("mx_device_id");
|
const deviceId = localStorage.getItem("mx_device_id");
|
||||||
|
|
||||||
let is_guest;
|
let isGuest;
|
||||||
if (localStorage.getItem("mx_is_guest") !== null) {
|
if (localStorage.getItem("mx_is_guest") !== null) {
|
||||||
is_guest = localStorage.getItem("mx_is_guest") === "true";
|
isGuest = localStorage.getItem("mx_is_guest") === "true";
|
||||||
} else {
|
} else {
|
||||||
// legacy key name
|
// legacy key name
|
||||||
is_guest = localStorage.getItem("matrix-is-guest") === "true";
|
isGuest = localStorage.getItem("matrix-is-guest") === "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (access_token && user_id && hs_url) {
|
if (accessToken && userId && hsUrl) {
|
||||||
console.log("Restoring session for %s", user_id);
|
console.log("Restoring session for %s", userId);
|
||||||
try {
|
try {
|
||||||
setLoggedIn({
|
setLoggedIn({
|
||||||
userId: user_id,
|
userId: userId,
|
||||||
deviceId: device_id,
|
deviceId: deviceId,
|
||||||
accessToken: access_token,
|
accessToken: accessToken,
|
||||||
homeserverUrl: hs_url,
|
homeserverUrl: hsUrl,
|
||||||
identityServerUrl: is_url,
|
identityServerUrl: isUrl,
|
||||||
guest: is_guest,
|
guest: isGuest,
|
||||||
});
|
});
|
||||||
return q(true);
|
return q(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -273,9 +274,13 @@ export function initRtsClient(url) {
|
||||||
*/
|
*/
|
||||||
export function setLoggedIn(credentials) {
|
export function setLoggedIn(credentials) {
|
||||||
credentials.guest = Boolean(credentials.guest);
|
credentials.guest = Boolean(credentials.guest);
|
||||||
console.log("setLoggedIn => %s (guest=%s) hs=%s",
|
|
||||||
credentials.userId, credentials.guest,
|
console.log(
|
||||||
credentials.homeserverUrl);
|
"setLoggedIn: mxid:", credentials.userId,
|
||||||
|
"deviceId:", credentials.deviceId,
|
||||||
|
"guest:", credentials.guest,
|
||||||
|
"hs:", credentials.homeserverUrl,
|
||||||
|
);
|
||||||
// This is dispatched to indicate that the user is still in the process of logging in
|
// This is dispatched to indicate that the user is still in the process of logging in
|
||||||
// because `teamPromise` may take some time to resolve, breaking the assumption that
|
// because `teamPromise` may take some time to resolve, breaking the assumption that
|
||||||
// `setLoggedIn` takes an "instant" to complete, and dispatch `on_logged_in` a few ms
|
// `setLoggedIn` takes an "instant" to complete, and dispatch `on_logged_in` a few ms
|
||||||
|
@ -352,7 +357,7 @@ export function logout() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return MatrixClientPeg.get().logout().then(onLoggedOut,
|
MatrixClientPeg.get().logout().then(onLoggedOut,
|
||||||
(err) => {
|
(err) => {
|
||||||
// Just throwing an error here is going to be very unhelpful
|
// Just throwing an error here is going to be very unhelpful
|
||||||
// if you're trying to log out because your server's down and
|
// if you're trying to log out because your server's down and
|
||||||
|
@ -363,8 +368,8 @@ export function logout() {
|
||||||
// change your password).
|
// change your password).
|
||||||
console.log("Failed to call logout API: token will not be invalidated");
|
console.log("Failed to call logout API: token will not be invalidated");
|
||||||
onLoggedOut();
|
onLoggedOut();
|
||||||
}
|
},
|
||||||
);
|
).done();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -420,7 +425,7 @@ export function stopMatrixClient() {
|
||||||
UserActivity.stop();
|
UserActivity.stop();
|
||||||
Presence.stop();
|
Presence.stop();
|
||||||
if (DMRoomMap.shared()) DMRoomMap.shared().stop();
|
if (DMRoomMap.shared()) DMRoomMap.shared().stop();
|
||||||
var cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
if (cli) {
|
if (cli) {
|
||||||
cli.stopClient();
|
cli.stopClient();
|
||||||
cli.removeAllListeners();
|
cli.removeAllListeners();
|
||||||
|
|
|
@ -15,11 +15,13 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var MatrixClientPeg = require("./MatrixClientPeg");
|
import MatrixClientPeg from './MatrixClientPeg';
|
||||||
var PlatformPeg = require("./PlatformPeg");
|
import PlatformPeg from './PlatformPeg';
|
||||||
var TextForEvent = require('./TextForEvent');
|
import TextForEvent from './TextForEvent';
|
||||||
var Avatar = require('./Avatar');
|
import Avatar from './Avatar';
|
||||||
var dis = require("./dispatcher");
|
import dis from './dispatcher';
|
||||||
|
import sdk from './index';
|
||||||
|
import Modal from './Modal';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Dispatches:
|
* Dispatches:
|
||||||
|
@ -29,7 +31,7 @@ var dis = require("./dispatcher");
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var Notifier = {
|
const Notifier = {
|
||||||
notifsByRoom: {},
|
notifsByRoom: {},
|
||||||
|
|
||||||
notificationMessageForEvent: function(ev) {
|
notificationMessageForEvent: function(ev) {
|
||||||
|
@ -48,16 +50,16 @@ var Notifier = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg = this.notificationMessageForEvent(ev);
|
let msg = this.notificationMessageForEvent(ev);
|
||||||
if (!msg) return;
|
if (!msg) return;
|
||||||
|
|
||||||
var title;
|
let title;
|
||||||
if (!ev.sender || room.name == ev.sender.name) {
|
if (!ev.sender || room.name === ev.sender.name) {
|
||||||
title = room.name;
|
title = room.name;
|
||||||
// notificationMessageForEvent includes sender,
|
// notificationMessageForEvent includes sender,
|
||||||
// but we already have the sender here
|
// but we already have the sender here
|
||||||
if (ev.getContent().body) msg = ev.getContent().body;
|
if (ev.getContent().body) msg = ev.getContent().body;
|
||||||
} else if (ev.getType() == 'm.room.member') {
|
} else if (ev.getType() === 'm.room.member') {
|
||||||
// context is all in the message here, we don't need
|
// context is all in the message here, we don't need
|
||||||
// to display sender info
|
// to display sender info
|
||||||
title = room.name;
|
title = room.name;
|
||||||
|
@ -68,7 +70,7 @@ var Notifier = {
|
||||||
if (ev.getContent().body) msg = ev.getContent().body;
|
if (ev.getContent().body) msg = ev.getContent().body;
|
||||||
}
|
}
|
||||||
|
|
||||||
var avatarUrl = ev.sender ? Avatar.avatarUrlForMember(
|
const avatarUrl = ev.sender ? Avatar.avatarUrlForMember(
|
||||||
ev.sender, 40, 40, 'crop'
|
ev.sender, 40, 40, 'crop'
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
|
@ -83,7 +85,7 @@ var Notifier = {
|
||||||
},
|
},
|
||||||
|
|
||||||
_playAudioNotification: function(ev, room) {
|
_playAudioNotification: function(ev, room) {
|
||||||
var e = document.getElementById("messageAudio");
|
const e = document.getElementById("messageAudio");
|
||||||
if (e) {
|
if (e) {
|
||||||
e.load();
|
e.load();
|
||||||
e.play();
|
e.play();
|
||||||
|
@ -95,7 +97,7 @@ var Notifier = {
|
||||||
this.boundOnSyncStateChange = this.onSyncStateChange.bind(this);
|
this.boundOnSyncStateChange = this.onSyncStateChange.bind(this);
|
||||||
this.boundOnRoomReceipt = this.onRoomReceipt.bind(this);
|
this.boundOnRoomReceipt = this.onRoomReceipt.bind(this);
|
||||||
MatrixClientPeg.get().on('Room.timeline', this.boundOnRoomTimeline);
|
MatrixClientPeg.get().on('Room.timeline', this.boundOnRoomTimeline);
|
||||||
MatrixClientPeg.get().on("Room.receipt", this.boundOnRoomReceipt);
|
MatrixClientPeg.get().on('Room.receipt', this.boundOnRoomReceipt);
|
||||||
MatrixClientPeg.get().on("sync", this.boundOnSyncStateChange);
|
MatrixClientPeg.get().on("sync", this.boundOnSyncStateChange);
|
||||||
this.toolbarHidden = false;
|
this.toolbarHidden = false;
|
||||||
this.isSyncing = false;
|
this.isSyncing = false;
|
||||||
|
@ -104,7 +106,7 @@ var Notifier = {
|
||||||
stop: function() {
|
stop: function() {
|
||||||
if (MatrixClientPeg.get() && this.boundOnRoomTimeline) {
|
if (MatrixClientPeg.get() && this.boundOnRoomTimeline) {
|
||||||
MatrixClientPeg.get().removeListener('Room.timeline', this.boundOnRoomTimeline);
|
MatrixClientPeg.get().removeListener('Room.timeline', this.boundOnRoomTimeline);
|
||||||
MatrixClientPeg.get().removeListener("Room.receipt", this.boundOnRoomReceipt);
|
MatrixClientPeg.get().removeListener('Room.receipt', this.boundOnRoomReceipt);
|
||||||
MatrixClientPeg.get().removeListener('sync', this.boundOnSyncStateChange);
|
MatrixClientPeg.get().removeListener('sync', this.boundOnSyncStateChange);
|
||||||
}
|
}
|
||||||
this.isSyncing = false;
|
this.isSyncing = false;
|
||||||
|
@ -121,7 +123,7 @@ var Notifier = {
|
||||||
// make sure that we persist the current setting audio_enabled setting
|
// make sure that we persist the current setting audio_enabled setting
|
||||||
// before changing anything
|
// before changing anything
|
||||||
if (global.localStorage) {
|
if (global.localStorage) {
|
||||||
if(global.localStorage.getItem('audio_notifications_enabled') == null) {
|
if (global.localStorage.getItem('audio_notifications_enabled') === null) {
|
||||||
this.setAudioEnabled(this.isEnabled());
|
this.setAudioEnabled(this.isEnabled());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,6 +133,16 @@ var Notifier = {
|
||||||
plaf.requestNotificationPermission().done((result) => {
|
plaf.requestNotificationPermission().done((result) => {
|
||||||
if (result !== 'granted') {
|
if (result !== 'granted') {
|
||||||
// The permission request was dismissed or denied
|
// The permission request was dismissed or denied
|
||||||
|
const description = result === 'denied'
|
||||||
|
? 'Riot does not have permission to send you notifications'
|
||||||
|
+ ' - please check your browser settings'
|
||||||
|
: 'Riot was not given permission to send notifications'
|
||||||
|
+ ' - please try again';
|
||||||
|
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
title: 'Unable to enable Notifications',
|
||||||
|
description,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,7 +153,7 @@ var Notifier = {
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "notifier_enabled",
|
action: "notifier_enabled",
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// clear the notifications_hidden flag, so that if notifications are
|
// clear the notifications_hidden flag, so that if notifications are
|
||||||
|
@ -152,7 +164,7 @@ var Notifier = {
|
||||||
global.localStorage.setItem('notifications_enabled', 'false');
|
global.localStorage.setItem('notifications_enabled', 'false');
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "notifier_enabled",
|
action: "notifier_enabled",
|
||||||
value: false
|
value: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -165,7 +177,7 @@ var Notifier = {
|
||||||
|
|
||||||
if (!global.localStorage) return true;
|
if (!global.localStorage) return true;
|
||||||
|
|
||||||
var enabled = global.localStorage.getItem('notifications_enabled');
|
const enabled = global.localStorage.getItem('notifications_enabled');
|
||||||
if (enabled === null) return true;
|
if (enabled === null) return true;
|
||||||
return enabled === 'true';
|
return enabled === 'true';
|
||||||
},
|
},
|
||||||
|
@ -178,7 +190,7 @@ var Notifier = {
|
||||||
|
|
||||||
isAudioEnabled: function(enable) {
|
isAudioEnabled: function(enable) {
|
||||||
if (!global.localStorage) return true;
|
if (!global.localStorage) return true;
|
||||||
var enabled = global.localStorage.getItem(
|
const enabled = global.localStorage.getItem(
|
||||||
'audio_notifications_enabled');
|
'audio_notifications_enabled');
|
||||||
// default to true if the popups are enabled
|
// default to true if the popups are enabled
|
||||||
if (enabled === null) return this.isEnabled();
|
if (enabled === null) return this.isEnabled();
|
||||||
|
@ -192,7 +204,7 @@ var Notifier = {
|
||||||
// this is nothing to do with notifier_enabled
|
// this is nothing to do with notifier_enabled
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "notifier_enabled",
|
action: "notifier_enabled",
|
||||||
value: this.isEnabled()
|
value: this.isEnabled(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// update the info to localStorage for persistent settings
|
// update the info to localStorage for persistent settings
|
||||||
|
@ -215,8 +227,7 @@ var Notifier = {
|
||||||
onSyncStateChange: function(state) {
|
onSyncStateChange: function(state) {
|
||||||
if (state === "SYNCING") {
|
if (state === "SYNCING") {
|
||||||
this.isSyncing = true;
|
this.isSyncing = true;
|
||||||
}
|
} else if (state === "STOPPED" || state === "ERROR") {
|
||||||
else if (state === "STOPPED" || state === "ERROR") {
|
|
||||||
this.isSyncing = false;
|
this.isSyncing = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -225,10 +236,10 @@ var Notifier = {
|
||||||
if (toStartOfTimeline) return;
|
if (toStartOfTimeline) return;
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
if (!this.isSyncing) return; // don't alert for any messages initially
|
if (!this.isSyncing) return; // don't alert for any messages initially
|
||||||
if (ev.sender && ev.sender.userId == MatrixClientPeg.get().credentials.userId) return;
|
if (ev.sender && ev.sender.userId === MatrixClientPeg.get().credentials.userId) return;
|
||||||
if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return;
|
if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return;
|
||||||
|
|
||||||
var actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
|
const actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
|
||||||
if (actions && actions.notify) {
|
if (actions && actions.notify) {
|
||||||
if (this.isEnabled()) {
|
if (this.isEnabled()) {
|
||||||
this._displayPopupNotification(ev, room);
|
this._displayPopupNotification(ev, room);
|
||||||
|
@ -240,7 +251,7 @@ var Notifier = {
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomReceipt: function(ev, room) {
|
onRoomReceipt: function(ev, room) {
|
||||||
if (room.getUnreadNotificationCount() == 0) {
|
if (room.getUnreadNotificationCount() === 0) {
|
||||||
// ideally we would clear each notification when it was read,
|
// ideally we would clear each notification when it was read,
|
||||||
// but we have no way, given a read receipt, to know whether
|
// but we have no way, given a read receipt, to know whether
|
||||||
// the receipt comes before or after an event, so we can't
|
// the receipt comes before or after an event, so we can't
|
||||||
|
@ -255,7 +266,7 @@ var Notifier = {
|
||||||
}
|
}
|
||||||
delete this.notifsByRoom[room.roomId];
|
delete this.notifsByRoom[room.roomId];
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!global.mxNotifier) {
|
if (!global.mxNotifier) {
|
||||||
|
|
|
@ -65,8 +65,8 @@ function textForMemberEvent(ev) {
|
||||||
} else if (!ev.getPrevContent().avatar_url && ev.getContent().avatar_url) {
|
} else if (!ev.getPrevContent().avatar_url && ev.getContent().avatar_url) {
|
||||||
return senderName + " set a profile picture";
|
return senderName + " set a profile picture";
|
||||||
} else {
|
} else {
|
||||||
// hacky hack for https://github.com/vector-im/vector-web/issues/2020
|
// suppress null rejoins
|
||||||
return senderName + " rejoined the room.";
|
return '';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key);
|
if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key);
|
||||||
|
|
|
@ -25,7 +25,9 @@ module.exports = {
|
||||||
eventTriggersUnreadCount: function(ev) {
|
eventTriggersUnreadCount: function(ev) {
|
||||||
if (ev.sender && ev.sender.userId == MatrixClientPeg.get().credentials.userId) {
|
if (ev.sender && ev.sender.userId == MatrixClientPeg.get().credentials.userId) {
|
||||||
return false;
|
return false;
|
||||||
} else if (ev.getType() == "m.room.member") {
|
} else if (ev.getType() == 'm.room.member') {
|
||||||
|
return false;
|
||||||
|
} else if (ev.getType() == 'm.call.answer' || ev.getType() == 'm.call.hangup') {
|
||||||
return false;
|
return false;
|
||||||
} else if (ev.getType == 'm.room.message' && ev.getContent().msgtype == 'm.notify') {
|
} else if (ev.getType == 'm.room.message' && ev.getContent().msgtype == 'm.notify') {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -32,7 +32,7 @@ class UserActivity {
|
||||||
start() {
|
start() {
|
||||||
document.onmousedown = this._onUserActivity.bind(this);
|
document.onmousedown = this._onUserActivity.bind(this);
|
||||||
document.onmousemove = this._onUserActivity.bind(this);
|
document.onmousemove = this._onUserActivity.bind(this);
|
||||||
document.onkeypress = this._onUserActivity.bind(this);
|
document.onkeydown = this._onUserActivity.bind(this);
|
||||||
// can't use document.scroll here because that's only the document
|
// can't use document.scroll here because that's only the document
|
||||||
// itself being scrolled. Need to use addEventListener's useCapture.
|
// itself being scrolled. Need to use addEventListener's useCapture.
|
||||||
// also this needs to be the wheel event, not scroll, as scroll is
|
// also this needs to be the wheel event, not scroll, as scroll is
|
||||||
|
@ -50,7 +50,7 @@ class UserActivity {
|
||||||
stop() {
|
stop() {
|
||||||
document.onmousedown = undefined;
|
document.onmousedown = undefined;
|
||||||
document.onmousemove = undefined;
|
document.onmousemove = undefined;
|
||||||
document.onkeypress = undefined;
|
document.onkeydown = undefined;
|
||||||
window.removeEventListener('wheel', this._onUserActivity.bind(this),
|
window.removeEventListener('wheel', this._onUserActivity.bind(this),
|
||||||
{ passive: true, capture: true });
|
{ passive: true, capture: true });
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,9 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
var q = require("q");
|
import q from 'q';
|
||||||
var MatrixClientPeg = require("./MatrixClientPeg");
|
import MatrixClientPeg from './MatrixClientPeg';
|
||||||
var Notifier = require("./Notifier");
|
import Notifier from './Notifier';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO: Make things use this. This is all WIP - see UserSettings.js for usage.
|
* TODO: Make things use this. This is all WIP - see UserSettings.js for usage.
|
||||||
|
@ -33,7 +33,7 @@ module.exports = {
|
||||||
],
|
],
|
||||||
|
|
||||||
loadProfileInfo: function() {
|
loadProfileInfo: function() {
|
||||||
var cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
return cli.getProfileInfo(cli.credentials.userId);
|
return cli.getProfileInfo(cli.credentials.userId);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ module.exports = {
|
||||||
loadThreePids: function() {
|
loadThreePids: function() {
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
return q({
|
return q({
|
||||||
threepids: []
|
threepids: [],
|
||||||
}); // guests can't poke 3pid endpoint
|
}); // guests can't poke 3pid endpoint
|
||||||
}
|
}
|
||||||
return MatrixClientPeg.get().getThreePids();
|
return MatrixClientPeg.get().getThreePids();
|
||||||
|
@ -73,19 +73,19 @@ module.exports = {
|
||||||
Notifier.setAudioEnabled(enable);
|
Notifier.setAudioEnabled(enable);
|
||||||
},
|
},
|
||||||
|
|
||||||
changePassword: function(old_password, new_password) {
|
changePassword: function(oldPassword, newPassword) {
|
||||||
var cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
|
||||||
var authDict = {
|
const authDict = {
|
||||||
type: 'm.login.password',
|
type: 'm.login.password',
|
||||||
user: cli.credentials.userId,
|
user: cli.credentials.userId,
|
||||||
password: old_password
|
password: oldPassword,
|
||||||
};
|
};
|
||||||
|
|
||||||
return cli.setPassword(authDict, new_password);
|
return cli.setPassword(authDict, newPassword);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Returns the email pusher (pusher of type 'email') for a given
|
* Returns the email pusher (pusher of type 'email') for a given
|
||||||
* email address. Email pushers all have the same app ID, so since
|
* email address. Email pushers all have the same app ID, so since
|
||||||
* pushers are unique over (app ID, pushkey), there will be at most
|
* pushers are unique over (app ID, pushkey), there will be at most
|
||||||
|
@ -95,8 +95,8 @@ module.exports = {
|
||||||
if (pushers === undefined) {
|
if (pushers === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
for (var i = 0; i < pushers.length; ++i) {
|
for (let i = 0; i < pushers.length; ++i) {
|
||||||
if (pushers[i].kind == 'email' && pushers[i].pushkey == address) {
|
if (pushers[i].kind === 'email' && pushers[i].pushkey === address) {
|
||||||
return pushers[i];
|
return pushers[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ module.exports = {
|
||||||
addEmailPusher: function(address, data) {
|
addEmailPusher: function(address, data) {
|
||||||
return MatrixClientPeg.get().setPusher({
|
return MatrixClientPeg.get().setPusher({
|
||||||
kind: 'email',
|
kind: 'email',
|
||||||
app_id: "m.email",
|
app_id: 'm.email',
|
||||||
pushkey: address,
|
pushkey: address,
|
||||||
app_display_name: 'Email Notifications',
|
app_display_name: 'Email Notifications',
|
||||||
device_display_name: address,
|
device_display_name: address,
|
||||||
|
@ -121,46 +121,46 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
getUrlPreviewsDisabled: function() {
|
getUrlPreviewsDisabled: function() {
|
||||||
var event = MatrixClientPeg.get().getAccountData("org.matrix.preview_urls");
|
const event = MatrixClientPeg.get().getAccountData('org.matrix.preview_urls');
|
||||||
return (event && event.getContent().disable);
|
return (event && event.getContent().disable);
|
||||||
},
|
},
|
||||||
|
|
||||||
setUrlPreviewsDisabled: function(disabled) {
|
setUrlPreviewsDisabled: function(disabled) {
|
||||||
// FIXME: handle errors
|
// FIXME: handle errors
|
||||||
return MatrixClientPeg.get().setAccountData("org.matrix.preview_urls", {
|
return MatrixClientPeg.get().setAccountData('org.matrix.preview_urls', {
|
||||||
disable: disabled
|
disable: disabled,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getSyncedSettings: function() {
|
getSyncedSettings: function() {
|
||||||
var event = MatrixClientPeg.get().getAccountData("im.vector.web.settings");
|
const event = MatrixClientPeg.get().getAccountData('im.vector.web.settings');
|
||||||
return event ? event.getContent() : {};
|
return event ? event.getContent() : {};
|
||||||
},
|
},
|
||||||
|
|
||||||
getSyncedSetting: function(type, defaultValue = null) {
|
getSyncedSetting: function(type, defaultValue = null) {
|
||||||
var settings = this.getSyncedSettings();
|
const settings = this.getSyncedSettings();
|
||||||
return settings.hasOwnProperty(type) ? settings[type] : null;
|
return settings.hasOwnProperty(type) ? settings[type] : defaultValue;
|
||||||
},
|
},
|
||||||
|
|
||||||
setSyncedSetting: function(type, value) {
|
setSyncedSetting: function(type, value) {
|
||||||
var settings = this.getSyncedSettings();
|
const settings = this.getSyncedSettings();
|
||||||
settings[type] = value;
|
settings[type] = value;
|
||||||
// FIXME: handle errors
|
// FIXME: handle errors
|
||||||
return MatrixClientPeg.get().setAccountData("im.vector.web.settings", settings);
|
return MatrixClientPeg.get().setAccountData('im.vector.web.settings', settings);
|
||||||
},
|
},
|
||||||
|
|
||||||
getLocalSettings: function() {
|
getLocalSettings: function() {
|
||||||
var localSettingsString = localStorage.getItem('mx_local_settings') || '{}';
|
const localSettingsString = localStorage.getItem('mx_local_settings') || '{}';
|
||||||
return JSON.parse(localSettingsString);
|
return JSON.parse(localSettingsString);
|
||||||
},
|
},
|
||||||
|
|
||||||
getLocalSetting: function(type, defaultValue = null) {
|
getLocalSetting: function(type, defaultValue = null) {
|
||||||
var settings = this.getLocalSettings();
|
const settings = this.getLocalSettings();
|
||||||
return settings.hasOwnProperty(type) ? settings[type] : null;
|
return settings.hasOwnProperty(type) ? settings[type] : defaultValue;
|
||||||
},
|
},
|
||||||
|
|
||||||
setLocalSetting: function(type, value) {
|
setLocalSetting: function(type, value) {
|
||||||
var settings = this.getLocalSettings();
|
const settings = this.getLocalSettings();
|
||||||
settings[type] = value;
|
settings[type] = value;
|
||||||
// FIXME: handle errors
|
// FIXME: handle errors
|
||||||
localStorage.setItem('mx_local_settings', JSON.stringify(settings));
|
localStorage.setItem('mx_local_settings', JSON.stringify(settings));
|
||||||
|
@ -171,8 +171,8 @@ module.exports = {
|
||||||
if (MatrixClientPeg.get().isGuest()) return false;
|
if (MatrixClientPeg.get().isGuest()) return false;
|
||||||
|
|
||||||
if (localStorage.getItem(`mx_labs_feature_${feature}`) === null) {
|
if (localStorage.getItem(`mx_labs_feature_${feature}`) === null) {
|
||||||
for (var i = 0; i < this.LABS_FEATURES.length; i++) {
|
for (let i = 0; i < this.LABS_FEATURES.length; i++) {
|
||||||
var f = this.LABS_FEATURES[i];
|
const f = this.LABS_FEATURES[i];
|
||||||
if (f.id === feature) {
|
if (f.id === feature) {
|
||||||
return f.default;
|
return f.default;
|
||||||
}
|
}
|
||||||
|
@ -183,5 +183,5 @@ module.exports = {
|
||||||
|
|
||||||
setFeatureEnabled: function(feature: string, enabled: boolean) {
|
setFeatureEnabled: function(feature: string, enabled: boolean) {
|
||||||
localStorage.setItem(`mx_labs_feature_${feature}`, enabled);
|
localStorage.setItem(`mx_labs_feature_${feature}`, enabled);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,7 +14,7 @@ let instance = null;
|
||||||
export default class EmojiProvider extends AutocompleteProvider {
|
export default class EmojiProvider extends AutocompleteProvider {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(EMOJI_REGEX);
|
super(EMOJI_REGEX);
|
||||||
this.fuse = new Fuse(EMOJI_SHORTNAMES);
|
this.fuse = new Fuse(EMOJI_SHORTNAMES, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCompletions(query: string, selection: SelectionRange) {
|
async getCompletions(query: string, selection: SelectionRange) {
|
||||||
|
|
|
@ -1,255 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* THIS FILE IS AUTO-GENERATED
|
|
||||||
* You can edit it you like, but your changes will be overwritten,
|
|
||||||
* so you'd just be trying to swim upstream like a salmon.
|
|
||||||
* You are not a salmon.
|
|
||||||
*
|
|
||||||
* To update it, run:
|
|
||||||
* ./reskindex.js -h header
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports.components = {};
|
|
||||||
import structures$ContextualMenu from './components/structures/ContextualMenu';
|
|
||||||
structures$ContextualMenu && (module.exports.components['structures.ContextualMenu'] = structures$ContextualMenu);
|
|
||||||
import structures$CreateRoom from './components/structures/CreateRoom';
|
|
||||||
structures$CreateRoom && (module.exports.components['structures.CreateRoom'] = structures$CreateRoom);
|
|
||||||
import structures$FilePanel from './components/structures/FilePanel';
|
|
||||||
structures$FilePanel && (module.exports.components['structures.FilePanel'] = structures$FilePanel);
|
|
||||||
import structures$InteractiveAuth from './components/structures/InteractiveAuth';
|
|
||||||
structures$InteractiveAuth && (module.exports.components['structures.InteractiveAuth'] = structures$InteractiveAuth);
|
|
||||||
import structures$LoggedInView from './components/structures/LoggedInView';
|
|
||||||
structures$LoggedInView && (module.exports.components['structures.LoggedInView'] = structures$LoggedInView);
|
|
||||||
import structures$MatrixChat from './components/structures/MatrixChat';
|
|
||||||
structures$MatrixChat && (module.exports.components['structures.MatrixChat'] = structures$MatrixChat);
|
|
||||||
import structures$MessagePanel from './components/structures/MessagePanel';
|
|
||||||
structures$MessagePanel && (module.exports.components['structures.MessagePanel'] = structures$MessagePanel);
|
|
||||||
import structures$NotificationPanel from './components/structures/NotificationPanel';
|
|
||||||
structures$NotificationPanel && (module.exports.components['structures.NotificationPanel'] = structures$NotificationPanel);
|
|
||||||
import structures$RoomStatusBar from './components/structures/RoomStatusBar';
|
|
||||||
structures$RoomStatusBar && (module.exports.components['structures.RoomStatusBar'] = structures$RoomStatusBar);
|
|
||||||
import structures$RoomView from './components/structures/RoomView';
|
|
||||||
structures$RoomView && (module.exports.components['structures.RoomView'] = structures$RoomView);
|
|
||||||
import structures$ScrollPanel from './components/structures/ScrollPanel';
|
|
||||||
structures$ScrollPanel && (module.exports.components['structures.ScrollPanel'] = structures$ScrollPanel);
|
|
||||||
import structures$TimelinePanel from './components/structures/TimelinePanel';
|
|
||||||
structures$TimelinePanel && (module.exports.components['structures.TimelinePanel'] = structures$TimelinePanel);
|
|
||||||
import structures$UploadBar from './components/structures/UploadBar';
|
|
||||||
structures$UploadBar && (module.exports.components['structures.UploadBar'] = structures$UploadBar);
|
|
||||||
import structures$UserSettings from './components/structures/UserSettings';
|
|
||||||
structures$UserSettings && (module.exports.components['structures.UserSettings'] = structures$UserSettings);
|
|
||||||
import structures$login$ForgotPassword from './components/structures/login/ForgotPassword';
|
|
||||||
structures$login$ForgotPassword && (module.exports.components['structures.login.ForgotPassword'] = structures$login$ForgotPassword);
|
|
||||||
import structures$login$Login from './components/structures/login/Login';
|
|
||||||
structures$login$Login && (module.exports.components['structures.login.Login'] = structures$login$Login);
|
|
||||||
import structures$login$PostRegistration from './components/structures/login/PostRegistration';
|
|
||||||
structures$login$PostRegistration && (module.exports.components['structures.login.PostRegistration'] = structures$login$PostRegistration);
|
|
||||||
import structures$login$Registration from './components/structures/login/Registration';
|
|
||||||
structures$login$Registration && (module.exports.components['structures.login.Registration'] = structures$login$Registration);
|
|
||||||
import views$avatars$BaseAvatar from './components/views/avatars/BaseAvatar';
|
|
||||||
views$avatars$BaseAvatar && (module.exports.components['views.avatars.BaseAvatar'] = views$avatars$BaseAvatar);
|
|
||||||
import views$avatars$MemberAvatar from './components/views/avatars/MemberAvatar';
|
|
||||||
views$avatars$MemberAvatar && (module.exports.components['views.avatars.MemberAvatar'] = views$avatars$MemberAvatar);
|
|
||||||
import views$avatars$RoomAvatar from './components/views/avatars/RoomAvatar';
|
|
||||||
views$avatars$RoomAvatar && (module.exports.components['views.avatars.RoomAvatar'] = views$avatars$RoomAvatar);
|
|
||||||
import views$create_room$CreateRoomButton from './components/views/create_room/CreateRoomButton';
|
|
||||||
views$create_room$CreateRoomButton && (module.exports.components['views.create_room.CreateRoomButton'] = views$create_room$CreateRoomButton);
|
|
||||||
import views$create_room$Presets from './components/views/create_room/Presets';
|
|
||||||
views$create_room$Presets && (module.exports.components['views.create_room.Presets'] = views$create_room$Presets);
|
|
||||||
import views$create_room$RoomAlias from './components/views/create_room/RoomAlias';
|
|
||||||
views$create_room$RoomAlias && (module.exports.components['views.create_room.RoomAlias'] = views$create_room$RoomAlias);
|
|
||||||
import views$dialogs$BaseDialog from './components/views/dialogs/BaseDialog';
|
|
||||||
views$dialogs$BaseDialog && (module.exports.components['views.dialogs.BaseDialog'] = views$dialogs$BaseDialog);
|
|
||||||
import views$dialogs$ChatCreateOrReuseDialog from './components/views/dialogs/ChatCreateOrReuseDialog';
|
|
||||||
views$dialogs$ChatCreateOrReuseDialog && (module.exports.components['views.dialogs.ChatCreateOrReuseDialog'] = views$dialogs$ChatCreateOrReuseDialog);
|
|
||||||
import views$dialogs$ChatInviteDialog from './components/views/dialogs/ChatInviteDialog';
|
|
||||||
views$dialogs$ChatInviteDialog && (module.exports.components['views.dialogs.ChatInviteDialog'] = views$dialogs$ChatInviteDialog);
|
|
||||||
import views$dialogs$ConfirmRedactDialog from './components/views/dialogs/ConfirmRedactDialog';
|
|
||||||
views$dialogs$ConfirmRedactDialog && (module.exports.components['views.dialogs.ConfirmRedactDialog'] = views$dialogs$ConfirmRedactDialog);
|
|
||||||
import views$dialogs$ConfirmUserActionDialog from './components/views/dialogs/ConfirmUserActionDialog';
|
|
||||||
views$dialogs$ConfirmUserActionDialog && (module.exports.components['views.dialogs.ConfirmUserActionDialog'] = views$dialogs$ConfirmUserActionDialog);
|
|
||||||
import views$dialogs$DeactivateAccountDialog from './components/views/dialogs/DeactivateAccountDialog';
|
|
||||||
views$dialogs$DeactivateAccountDialog && (module.exports.components['views.dialogs.DeactivateAccountDialog'] = views$dialogs$DeactivateAccountDialog);
|
|
||||||
import views$dialogs$ErrorDialog from './components/views/dialogs/ErrorDialog';
|
|
||||||
views$dialogs$ErrorDialog && (module.exports.components['views.dialogs.ErrorDialog'] = views$dialogs$ErrorDialog);
|
|
||||||
import views$dialogs$InteractiveAuthDialog from './components/views/dialogs/InteractiveAuthDialog';
|
|
||||||
views$dialogs$InteractiveAuthDialog && (module.exports.components['views.dialogs.InteractiveAuthDialog'] = views$dialogs$InteractiveAuthDialog);
|
|
||||||
import views$dialogs$NeedToRegisterDialog from './components/views/dialogs/NeedToRegisterDialog';
|
|
||||||
views$dialogs$NeedToRegisterDialog && (module.exports.components['views.dialogs.NeedToRegisterDialog'] = views$dialogs$NeedToRegisterDialog);
|
|
||||||
import views$dialogs$QuestionDialog from './components/views/dialogs/QuestionDialog';
|
|
||||||
views$dialogs$QuestionDialog && (module.exports.components['views.dialogs.QuestionDialog'] = views$dialogs$QuestionDialog);
|
|
||||||
import views$dialogs$SessionRestoreErrorDialog from './components/views/dialogs/SessionRestoreErrorDialog';
|
|
||||||
views$dialogs$SessionRestoreErrorDialog && (module.exports.components['views.dialogs.SessionRestoreErrorDialog'] = views$dialogs$SessionRestoreErrorDialog);
|
|
||||||
import views$dialogs$SetDisplayNameDialog from './components/views/dialogs/SetDisplayNameDialog';
|
|
||||||
views$dialogs$SetDisplayNameDialog && (module.exports.components['views.dialogs.SetDisplayNameDialog'] = views$dialogs$SetDisplayNameDialog);
|
|
||||||
import views$dialogs$TextInputDialog from './components/views/dialogs/TextInputDialog';
|
|
||||||
views$dialogs$TextInputDialog && (module.exports.components['views.dialogs.TextInputDialog'] = views$dialogs$TextInputDialog);
|
|
||||||
import views$dialogs$UnknownDeviceDialog from './components/views/dialogs/UnknownDeviceDialog';
|
|
||||||
views$dialogs$UnknownDeviceDialog && (module.exports.components['views.dialogs.UnknownDeviceDialog'] = views$dialogs$UnknownDeviceDialog);
|
|
||||||
import views$elements$AccessibleButton from './components/views/elements/AccessibleButton';
|
|
||||||
views$elements$AccessibleButton && (module.exports.components['views.elements.AccessibleButton'] = views$elements$AccessibleButton);
|
|
||||||
import views$elements$AddressSelector from './components/views/elements/AddressSelector';
|
|
||||||
views$elements$AddressSelector && (module.exports.components['views.elements.AddressSelector'] = views$elements$AddressSelector);
|
|
||||||
import views$elements$AddressTile from './components/views/elements/AddressTile';
|
|
||||||
views$elements$AddressTile && (module.exports.components['views.elements.AddressTile'] = views$elements$AddressTile);
|
|
||||||
import views$elements$DeviceVerifyButtons from './components/views/elements/DeviceVerifyButtons';
|
|
||||||
views$elements$DeviceVerifyButtons && (module.exports.components['views.elements.DeviceVerifyButtons'] = views$elements$DeviceVerifyButtons);
|
|
||||||
import views$elements$DirectorySearchBox from './components/views/elements/DirectorySearchBox';
|
|
||||||
views$elements$DirectorySearchBox && (module.exports.components['views.elements.DirectorySearchBox'] = views$elements$DirectorySearchBox);
|
|
||||||
import views$elements$Dropdown from './components/views/elements/Dropdown';
|
|
||||||
views$elements$Dropdown && (module.exports.components['views.elements.Dropdown'] = views$elements$Dropdown);
|
|
||||||
import views$elements$EditableText from './components/views/elements/EditableText';
|
|
||||||
views$elements$EditableText && (module.exports.components['views.elements.EditableText'] = views$elements$EditableText);
|
|
||||||
import views$elements$EditableTextContainer from './components/views/elements/EditableTextContainer';
|
|
||||||
views$elements$EditableTextContainer && (module.exports.components['views.elements.EditableTextContainer'] = views$elements$EditableTextContainer);
|
|
||||||
import views$elements$EmojiText from './components/views/elements/EmojiText';
|
|
||||||
views$elements$EmojiText && (module.exports.components['views.elements.EmojiText'] = views$elements$EmojiText);
|
|
||||||
import views$elements$MemberEventListSummary from './components/views/elements/MemberEventListSummary';
|
|
||||||
views$elements$MemberEventListSummary && (module.exports.components['views.elements.MemberEventListSummary'] = views$elements$MemberEventListSummary);
|
|
||||||
import views$elements$PowerSelector from './components/views/elements/PowerSelector';
|
|
||||||
views$elements$PowerSelector && (module.exports.components['views.elements.PowerSelector'] = views$elements$PowerSelector);
|
|
||||||
import views$elements$ProgressBar from './components/views/elements/ProgressBar';
|
|
||||||
views$elements$ProgressBar && (module.exports.components['views.elements.ProgressBar'] = views$elements$ProgressBar);
|
|
||||||
import views$elements$TintableSvg from './components/views/elements/TintableSvg';
|
|
||||||
views$elements$TintableSvg && (module.exports.components['views.elements.TintableSvg'] = views$elements$TintableSvg);
|
|
||||||
import views$elements$TruncatedList from './components/views/elements/TruncatedList';
|
|
||||||
views$elements$TruncatedList && (module.exports.components['views.elements.TruncatedList'] = views$elements$TruncatedList);
|
|
||||||
import views$elements$UserSelector from './components/views/elements/UserSelector';
|
|
||||||
views$elements$UserSelector && (module.exports.components['views.elements.UserSelector'] = views$elements$UserSelector);
|
|
||||||
import views$login$CaptchaForm from './components/views/login/CaptchaForm';
|
|
||||||
views$login$CaptchaForm && (module.exports.components['views.login.CaptchaForm'] = views$login$CaptchaForm);
|
|
||||||
import views$login$CasLogin from './components/views/login/CasLogin';
|
|
||||||
views$login$CasLogin && (module.exports.components['views.login.CasLogin'] = views$login$CasLogin);
|
|
||||||
import views$login$CountryDropdown from './components/views/login/CountryDropdown';
|
|
||||||
views$login$CountryDropdown && (module.exports.components['views.login.CountryDropdown'] = views$login$CountryDropdown);
|
|
||||||
import views$login$CustomServerDialog from './components/views/login/CustomServerDialog';
|
|
||||||
views$login$CustomServerDialog && (module.exports.components['views.login.CustomServerDialog'] = views$login$CustomServerDialog);
|
|
||||||
import views$login$InteractiveAuthEntryComponents from './components/views/login/InteractiveAuthEntryComponents';
|
|
||||||
views$login$InteractiveAuthEntryComponents && (module.exports.components['views.login.InteractiveAuthEntryComponents'] = views$login$InteractiveAuthEntryComponents);
|
|
||||||
import views$login$LoginFooter from './components/views/login/LoginFooter';
|
|
||||||
views$login$LoginFooter && (module.exports.components['views.login.LoginFooter'] = views$login$LoginFooter);
|
|
||||||
import views$login$LoginHeader from './components/views/login/LoginHeader';
|
|
||||||
views$login$LoginHeader && (module.exports.components['views.login.LoginHeader'] = views$login$LoginHeader);
|
|
||||||
import views$login$PasswordLogin from './components/views/login/PasswordLogin';
|
|
||||||
views$login$PasswordLogin && (module.exports.components['views.login.PasswordLogin'] = views$login$PasswordLogin);
|
|
||||||
import views$login$RegistrationForm from './components/views/login/RegistrationForm';
|
|
||||||
views$login$RegistrationForm && (module.exports.components['views.login.RegistrationForm'] = views$login$RegistrationForm);
|
|
||||||
import views$login$ServerConfig from './components/views/login/ServerConfig';
|
|
||||||
views$login$ServerConfig && (module.exports.components['views.login.ServerConfig'] = views$login$ServerConfig);
|
|
||||||
import views$messages$MAudioBody from './components/views/messages/MAudioBody';
|
|
||||||
views$messages$MAudioBody && (module.exports.components['views.messages.MAudioBody'] = views$messages$MAudioBody);
|
|
||||||
import views$messages$MFileBody from './components/views/messages/MFileBody';
|
|
||||||
views$messages$MFileBody && (module.exports.components['views.messages.MFileBody'] = views$messages$MFileBody);
|
|
||||||
import views$messages$MImageBody from './components/views/messages/MImageBody';
|
|
||||||
views$messages$MImageBody && (module.exports.components['views.messages.MImageBody'] = views$messages$MImageBody);
|
|
||||||
import views$messages$MVideoBody from './components/views/messages/MVideoBody';
|
|
||||||
views$messages$MVideoBody && (module.exports.components['views.messages.MVideoBody'] = views$messages$MVideoBody);
|
|
||||||
import views$messages$MessageEvent from './components/views/messages/MessageEvent';
|
|
||||||
views$messages$MessageEvent && (module.exports.components['views.messages.MessageEvent'] = views$messages$MessageEvent);
|
|
||||||
import views$messages$SenderProfile from './components/views/messages/SenderProfile';
|
|
||||||
views$messages$SenderProfile && (module.exports.components['views.messages.SenderProfile'] = views$messages$SenderProfile);
|
|
||||||
import views$messages$TextualBody from './components/views/messages/TextualBody';
|
|
||||||
views$messages$TextualBody && (module.exports.components['views.messages.TextualBody'] = views$messages$TextualBody);
|
|
||||||
import views$messages$TextualEvent from './components/views/messages/TextualEvent';
|
|
||||||
views$messages$TextualEvent && (module.exports.components['views.messages.TextualEvent'] = views$messages$TextualEvent);
|
|
||||||
import views$messages$UnknownBody from './components/views/messages/UnknownBody';
|
|
||||||
views$messages$UnknownBody && (module.exports.components['views.messages.UnknownBody'] = views$messages$UnknownBody);
|
|
||||||
import views$room_settings$AliasSettings from './components/views/room_settings/AliasSettings';
|
|
||||||
views$room_settings$AliasSettings && (module.exports.components['views.room_settings.AliasSettings'] = views$room_settings$AliasSettings);
|
|
||||||
import views$room_settings$ColorSettings from './components/views/room_settings/ColorSettings';
|
|
||||||
views$room_settings$ColorSettings && (module.exports.components['views.room_settings.ColorSettings'] = views$room_settings$ColorSettings);
|
|
||||||
import views$room_settings$UrlPreviewSettings from './components/views/room_settings/UrlPreviewSettings';
|
|
||||||
views$room_settings$UrlPreviewSettings && (module.exports.components['views.room_settings.UrlPreviewSettings'] = views$room_settings$UrlPreviewSettings);
|
|
||||||
import views$rooms$Autocomplete from './components/views/rooms/Autocomplete';
|
|
||||||
views$rooms$Autocomplete && (module.exports.components['views.rooms.Autocomplete'] = views$rooms$Autocomplete);
|
|
||||||
import views$rooms$AuxPanel from './components/views/rooms/AuxPanel';
|
|
||||||
views$rooms$AuxPanel && (module.exports.components['views.rooms.AuxPanel'] = views$rooms$AuxPanel);
|
|
||||||
import views$rooms$EntityTile from './components/views/rooms/EntityTile';
|
|
||||||
views$rooms$EntityTile && (module.exports.components['views.rooms.EntityTile'] = views$rooms$EntityTile);
|
|
||||||
import views$rooms$EventTile from './components/views/rooms/EventTile';
|
|
||||||
views$rooms$EventTile && (module.exports.components['views.rooms.EventTile'] = views$rooms$EventTile);
|
|
||||||
import views$rooms$ForwardMessage from './components/views/rooms/ForwardMessage';
|
|
||||||
views$rooms$ForwardMessage && (module.exports.components['views.rooms.ForwardMessage'] = views$rooms$ForwardMessage);
|
|
||||||
import views$rooms$LinkPreviewWidget from './components/views/rooms/LinkPreviewWidget';
|
|
||||||
views$rooms$LinkPreviewWidget && (module.exports.components['views.rooms.LinkPreviewWidget'] = views$rooms$LinkPreviewWidget);
|
|
||||||
import views$rooms$MemberDeviceInfo from './components/views/rooms/MemberDeviceInfo';
|
|
||||||
views$rooms$MemberDeviceInfo && (module.exports.components['views.rooms.MemberDeviceInfo'] = views$rooms$MemberDeviceInfo);
|
|
||||||
import views$rooms$MemberInfo from './components/views/rooms/MemberInfo';
|
|
||||||
views$rooms$MemberInfo && (module.exports.components['views.rooms.MemberInfo'] = views$rooms$MemberInfo);
|
|
||||||
import views$rooms$MemberList from './components/views/rooms/MemberList';
|
|
||||||
views$rooms$MemberList && (module.exports.components['views.rooms.MemberList'] = views$rooms$MemberList);
|
|
||||||
import views$rooms$MemberTile from './components/views/rooms/MemberTile';
|
|
||||||
views$rooms$MemberTile && (module.exports.components['views.rooms.MemberTile'] = views$rooms$MemberTile);
|
|
||||||
import views$rooms$MessageComposer from './components/views/rooms/MessageComposer';
|
|
||||||
views$rooms$MessageComposer && (module.exports.components['views.rooms.MessageComposer'] = views$rooms$MessageComposer);
|
|
||||||
import views$rooms$MessageComposerInput from './components/views/rooms/MessageComposerInput';
|
|
||||||
views$rooms$MessageComposerInput && (module.exports.components['views.rooms.MessageComposerInput'] = views$rooms$MessageComposerInput);
|
|
||||||
import views$rooms$MessageComposerInputOld from './components/views/rooms/MessageComposerInputOld';
|
|
||||||
views$rooms$MessageComposerInputOld && (module.exports.components['views.rooms.MessageComposerInputOld'] = views$rooms$MessageComposerInputOld);
|
|
||||||
import views$rooms$PresenceLabel from './components/views/rooms/PresenceLabel';
|
|
||||||
views$rooms$PresenceLabel && (module.exports.components['views.rooms.PresenceLabel'] = views$rooms$PresenceLabel);
|
|
||||||
import views$rooms$ReadReceiptMarker from './components/views/rooms/ReadReceiptMarker';
|
|
||||||
views$rooms$ReadReceiptMarker && (module.exports.components['views.rooms.ReadReceiptMarker'] = views$rooms$ReadReceiptMarker);
|
|
||||||
import views$rooms$RoomHeader from './components/views/rooms/RoomHeader';
|
|
||||||
views$rooms$RoomHeader && (module.exports.components['views.rooms.RoomHeader'] = views$rooms$RoomHeader);
|
|
||||||
import views$rooms$RoomList from './components/views/rooms/RoomList';
|
|
||||||
views$rooms$RoomList && (module.exports.components['views.rooms.RoomList'] = views$rooms$RoomList);
|
|
||||||
import views$rooms$RoomNameEditor from './components/views/rooms/RoomNameEditor';
|
|
||||||
views$rooms$RoomNameEditor && (module.exports.components['views.rooms.RoomNameEditor'] = views$rooms$RoomNameEditor);
|
|
||||||
import views$rooms$RoomPreviewBar from './components/views/rooms/RoomPreviewBar';
|
|
||||||
views$rooms$RoomPreviewBar && (module.exports.components['views.rooms.RoomPreviewBar'] = views$rooms$RoomPreviewBar);
|
|
||||||
import views$rooms$RoomSettings from './components/views/rooms/RoomSettings';
|
|
||||||
views$rooms$RoomSettings && (module.exports.components['views.rooms.RoomSettings'] = views$rooms$RoomSettings);
|
|
||||||
import views$rooms$RoomTile from './components/views/rooms/RoomTile';
|
|
||||||
views$rooms$RoomTile && (module.exports.components['views.rooms.RoomTile'] = views$rooms$RoomTile);
|
|
||||||
import views$rooms$RoomTopicEditor from './components/views/rooms/RoomTopicEditor';
|
|
||||||
views$rooms$RoomTopicEditor && (module.exports.components['views.rooms.RoomTopicEditor'] = views$rooms$RoomTopicEditor);
|
|
||||||
import views$rooms$SearchResultTile from './components/views/rooms/SearchResultTile';
|
|
||||||
views$rooms$SearchResultTile && (module.exports.components['views.rooms.SearchResultTile'] = views$rooms$SearchResultTile);
|
|
||||||
import views$rooms$SearchableEntityList from './components/views/rooms/SearchableEntityList';
|
|
||||||
views$rooms$SearchableEntityList && (module.exports.components['views.rooms.SearchableEntityList'] = views$rooms$SearchableEntityList);
|
|
||||||
import views$rooms$SimpleRoomHeader from './components/views/rooms/SimpleRoomHeader';
|
|
||||||
views$rooms$SimpleRoomHeader && (module.exports.components['views.rooms.SimpleRoomHeader'] = views$rooms$SimpleRoomHeader);
|
|
||||||
import views$rooms$TabCompleteBar from './components/views/rooms/TabCompleteBar';
|
|
||||||
views$rooms$TabCompleteBar && (module.exports.components['views.rooms.TabCompleteBar'] = views$rooms$TabCompleteBar);
|
|
||||||
import views$rooms$TopUnreadMessagesBar from './components/views/rooms/TopUnreadMessagesBar';
|
|
||||||
views$rooms$TopUnreadMessagesBar && (module.exports.components['views.rooms.TopUnreadMessagesBar'] = views$rooms$TopUnreadMessagesBar);
|
|
||||||
import views$rooms$UserTile from './components/views/rooms/UserTile';
|
|
||||||
views$rooms$UserTile && (module.exports.components['views.rooms.UserTile'] = views$rooms$UserTile);
|
|
||||||
import views$settings$AddPhoneNumber from './components/views/settings/AddPhoneNumber';
|
|
||||||
views$settings$AddPhoneNumber && (module.exports.components['views.settings.AddPhoneNumber'] = views$settings$AddPhoneNumber);
|
|
||||||
import views$settings$ChangeAvatar from './components/views/settings/ChangeAvatar';
|
|
||||||
views$settings$ChangeAvatar && (module.exports.components['views.settings.ChangeAvatar'] = views$settings$ChangeAvatar);
|
|
||||||
import views$settings$ChangeDisplayName from './components/views/settings/ChangeDisplayName';
|
|
||||||
views$settings$ChangeDisplayName && (module.exports.components['views.settings.ChangeDisplayName'] = views$settings$ChangeDisplayName);
|
|
||||||
import views$settings$ChangePassword from './components/views/settings/ChangePassword';
|
|
||||||
views$settings$ChangePassword && (module.exports.components['views.settings.ChangePassword'] = views$settings$ChangePassword);
|
|
||||||
import views$settings$DevicesPanel from './components/views/settings/DevicesPanel';
|
|
||||||
views$settings$DevicesPanel && (module.exports.components['views.settings.DevicesPanel'] = views$settings$DevicesPanel);
|
|
||||||
import views$settings$DevicesPanelEntry from './components/views/settings/DevicesPanelEntry';
|
|
||||||
views$settings$DevicesPanelEntry && (module.exports.components['views.settings.DevicesPanelEntry'] = views$settings$DevicesPanelEntry);
|
|
||||||
import views$settings$EnableNotificationsButton from './components/views/settings/EnableNotificationsButton';
|
|
||||||
views$settings$EnableNotificationsButton && (module.exports.components['views.settings.EnableNotificationsButton'] = views$settings$EnableNotificationsButton);
|
|
||||||
import views$voip$CallView from './components/views/voip/CallView';
|
|
||||||
views$voip$CallView && (module.exports.components['views.voip.CallView'] = views$voip$CallView);
|
|
||||||
import views$voip$IncomingCallBox from './components/views/voip/IncomingCallBox';
|
|
||||||
views$voip$IncomingCallBox && (module.exports.components['views.voip.IncomingCallBox'] = views$voip$IncomingCallBox);
|
|
||||||
import views$voip$VideoFeed from './components/views/voip/VideoFeed';
|
|
||||||
views$voip$VideoFeed && (module.exports.components['views.voip.VideoFeed'] = views$voip$VideoFeed);
|
|
||||||
import views$voip$VideoView from './components/views/voip/VideoView';
|
|
||||||
views$voip$VideoView && (module.exports.components['views.voip.VideoView'] = views$voip$VideoView);
|
|
|
@ -59,6 +59,8 @@ var FilePanel = React.createClass({
|
||||||
var client = MatrixClientPeg.get();
|
var client = MatrixClientPeg.get();
|
||||||
var room = client.getRoom(roomId);
|
var room = client.getRoom(roomId);
|
||||||
|
|
||||||
|
this.noRoom = !room;
|
||||||
|
|
||||||
if (room) {
|
if (room) {
|
||||||
var filter = new Matrix.Filter(client.credentials.userId);
|
var filter = new Matrix.Filter(client.credentials.userId);
|
||||||
filter.setDefinition(
|
filter.setDefinition(
|
||||||
|
@ -82,13 +84,22 @@ var FilePanel = React.createClass({
|
||||||
console.error("Failed to get or create file panel filter", error);
|
console.error("Failed to get or create file panel filter", error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
console.error("Failed to add filtered timelineSet for FilePanel as no room!");
|
console.error("Failed to add filtered timelineSet for FilePanel as no room!");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
|
return <div className="mx_FilePanel mx_RoomView_messageListWrapper">
|
||||||
|
<div className="mx_RoomView_empty">You must <a href="#/register">register</a> to use this functionality</div>
|
||||||
|
</div>;
|
||||||
|
} else if (this.noRoom) {
|
||||||
|
return <div className="mx_FilePanel mx_RoomView_messageListWrapper">
|
||||||
|
<div className="mx_RoomView_empty">You must join the room to see its files</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
// wrap a TimelinePanel with the jump-to-event bits turned off.
|
// wrap a TimelinePanel with the jump-to-event bits turned off.
|
||||||
var TimelinePanel = sdk.getComponent("structures.TimelinePanel");
|
var TimelinePanel = sdk.getComponent("structures.TimelinePanel");
|
||||||
var Loader = sdk.getComponent("elements.Spinner");
|
var Loader = sdk.getComponent("elements.Spinner");
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -106,18 +107,6 @@ export default React.createClass({
|
||||||
var handled = false;
|
var handled = false;
|
||||||
|
|
||||||
switch (ev.keyCode) {
|
switch (ev.keyCode) {
|
||||||
case KeyCode.ESCAPE:
|
|
||||||
|
|
||||||
// Implemented this way so possible handling for other pages is neater
|
|
||||||
switch (this.props.page_type) {
|
|
||||||
case PageTypes.UserSettings:
|
|
||||||
this.props.onUserSettingsClose();
|
|
||||||
handled = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KeyCode.UP:
|
case KeyCode.UP:
|
||||||
case KeyCode.DOWN:
|
case KeyCode.DOWN:
|
||||||
if (ev.altKey && !ev.shiftKey && !ev.ctrlKey && !ev.metaKey) {
|
if (ev.altKey && !ev.shiftKey && !ev.ctrlKey && !ev.metaKey) {
|
||||||
|
@ -162,19 +151,19 @@ export default React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var LeftPanel = sdk.getComponent('structures.LeftPanel');
|
const LeftPanel = sdk.getComponent('structures.LeftPanel');
|
||||||
var RightPanel = sdk.getComponent('structures.RightPanel');
|
const RightPanel = sdk.getComponent('structures.RightPanel');
|
||||||
var RoomView = sdk.getComponent('structures.RoomView');
|
const RoomView = sdk.getComponent('structures.RoomView');
|
||||||
var UserSettings = sdk.getComponent('structures.UserSettings');
|
const UserSettings = sdk.getComponent('structures.UserSettings');
|
||||||
var CreateRoom = sdk.getComponent('structures.CreateRoom');
|
const CreateRoom = sdk.getComponent('structures.CreateRoom');
|
||||||
var RoomDirectory = sdk.getComponent('structures.RoomDirectory');
|
const RoomDirectory = sdk.getComponent('structures.RoomDirectory');
|
||||||
var HomePage = sdk.getComponent('structures.HomePage');
|
const HomePage = sdk.getComponent('structures.HomePage');
|
||||||
var MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
|
const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
|
||||||
var GuestWarningBar = sdk.getComponent('globals.GuestWarningBar');
|
const GuestWarningBar = sdk.getComponent('globals.GuestWarningBar');
|
||||||
var NewVersionBar = sdk.getComponent('globals.NewVersionBar');
|
const NewVersionBar = sdk.getComponent('globals.NewVersionBar');
|
||||||
|
|
||||||
var page_element;
|
let page_element;
|
||||||
var right_panel = '';
|
let right_panel = '';
|
||||||
|
|
||||||
switch (this.props.page_type) {
|
switch (this.props.page_type) {
|
||||||
case PageTypes.RoomView:
|
case PageTypes.RoomView:
|
||||||
|
@ -220,10 +209,8 @@ export default React.createClass({
|
||||||
case PageTypes.RoomDirectory:
|
case PageTypes.RoomDirectory:
|
||||||
page_element = <RoomDirectory
|
page_element = <RoomDirectory
|
||||||
ref="roomDirectory"
|
ref="roomDirectory"
|
||||||
collapsedRhs={this.props.collapse_rhs}
|
|
||||||
config={this.props.config.roomDirectory}
|
config={this.props.config.roomDirectory}
|
||||||
/>;
|
/>;
|
||||||
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.rightOpacity}/>;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PageTypes.HomePage:
|
case PageTypes.HomePage:
|
||||||
|
|
|
@ -393,9 +393,10 @@ module.exports = React.createClass({
|
||||||
this.notifyNewScreen('forgot_password');
|
this.notifyNewScreen('forgot_password');
|
||||||
break;
|
break;
|
||||||
case 'leave_room':
|
case 'leave_room':
|
||||||
|
const roomToLeave = MatrixClientPeg.get().getRoom(payload.room_id);
|
||||||
Modal.createDialog(QuestionDialog, {
|
Modal.createDialog(QuestionDialog, {
|
||||||
title: "Leave room",
|
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) => {
|
onFinished: (should_leave) => {
|
||||||
if (should_leave) {
|
if (should_leave) {
|
||||||
const d = MatrixClientPeg.get().leave(payload.room_id);
|
const d = MatrixClientPeg.get().leave(payload.room_id);
|
||||||
|
@ -770,8 +771,12 @@ module.exports = React.createClass({
|
||||||
this._teamToken = teamToken;
|
this._teamToken = teamToken;
|
||||||
dis.dispatch({action: 'view_home_page'});
|
dis.dispatch({action: 'view_home_page'});
|
||||||
} else if (this._is_registered) {
|
} else if (this._is_registered) {
|
||||||
|
if (this.props.config.welcomeUserId) {
|
||||||
|
createRoom({dmUserId: this.props.config.welcomeUserId});
|
||||||
|
return;
|
||||||
|
}
|
||||||
// The user has just logged in after registering
|
// The user has just logged in after registering
|
||||||
dis.dispatch({action: 'view_user_settings'});
|
dis.dispatch({action: 'view_room_directory'});
|
||||||
} else {
|
} else {
|
||||||
this._showScreenAfterLogin();
|
this._showScreenAfterLogin();
|
||||||
}
|
}
|
||||||
|
|
|
@ -279,20 +279,19 @@ module.exports = React.createClass({
|
||||||
this.currentGhostEventId = null;
|
this.currentGhostEventId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var isMembershipChange = (e) =>
|
var isMembershipChange = (e) => e.getType() === 'm.room.member';
|
||||||
e.getType() === 'm.room.member'
|
|
||||||
&& (!e.getPrevContent() || e.getContent().membership !== e.getPrevContent().membership);
|
|
||||||
|
|
||||||
for (i = 0; i < this.props.events.length; i++) {
|
for (i = 0; i < this.props.events.length; i++) {
|
||||||
var mxEv = this.props.events[i];
|
let mxEv = this.props.events[i];
|
||||||
var wantTile = true;
|
let wantTile = true;
|
||||||
var eventId = mxEv.getId();
|
let eventId = mxEv.getId();
|
||||||
|
let readMarkerInMels = false;
|
||||||
|
|
||||||
if (!EventTile.haveTileForEvent(mxEv)) {
|
if (!EventTile.haveTileForEvent(mxEv)) {
|
||||||
wantTile = false;
|
wantTile = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var last = (i == lastShownEventIndex);
|
let last = (i == lastShownEventIndex);
|
||||||
|
|
||||||
// Wrap consecutive member events in a ListSummary, ignore if redacted
|
// Wrap consecutive member events in a ListSummary, ignore if redacted
|
||||||
if (isMembershipChange(mxEv) &&
|
if (isMembershipChange(mxEv) &&
|
||||||
|
@ -334,6 +333,9 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
let eventTiles = summarisedEvents.map(
|
let eventTiles = summarisedEvents.map(
|
||||||
(e) => {
|
(e) => {
|
||||||
|
if (e.getId() === this.props.readMarkerEventId) {
|
||||||
|
readMarkerInMels = true;
|
||||||
|
}
|
||||||
// In order to prevent DateSeparators from appearing in the expanded form
|
// In order to prevent DateSeparators from appearing in the expanded form
|
||||||
// of MemberEventListSummary, render each member event as if the previous
|
// of MemberEventListSummary, render each member event as if the previous
|
||||||
// one was itself. This way, the timestamp of the previous event === the
|
// one was itself. This way, the timestamp of the previous event === the
|
||||||
|
@ -352,12 +354,16 @@ module.exports = React.createClass({
|
||||||
<MemberEventListSummary
|
<MemberEventListSummary
|
||||||
key={key}
|
key={key}
|
||||||
events={summarisedEvents}
|
events={summarisedEvents}
|
||||||
data-scroll-token={eventId}
|
|
||||||
onToggle={this._onWidgetLoad} // Update scroll state
|
onToggle={this._onWidgetLoad} // Update scroll state
|
||||||
>
|
>
|
||||||
{eventTiles}
|
{eventTiles}
|
||||||
</MemberEventListSummary>
|
</MemberEventListSummary>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (readMarkerInMels) {
|
||||||
|
ret.push(this._getReadMarkerTile(visible));
|
||||||
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -466,7 +472,7 @@ module.exports = React.createClass({
|
||||||
ret.push(
|
ret.push(
|
||||||
<li key={eventId}
|
<li key={eventId}
|
||||||
ref={this._collectEventNode.bind(this, eventId)}
|
ref={this._collectEventNode.bind(this, eventId)}
|
||||||
data-scroll-token={scrollToken}>
|
data-scroll-tokens={scrollToken}>
|
||||||
<EventTile mxEvent={mxEv} continuation={continuation}
|
<EventTile mxEvent={mxEv} continuation={continuation}
|
||||||
isRedacted={mxEv.isRedacted()}
|
isRedacted={mxEv.isRedacted()}
|
||||||
onWidgetLoad={this._onWidgetLoad}
|
onWidgetLoad={this._onWidgetLoad}
|
||||||
|
|
|
@ -273,6 +273,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
this._updateConfCallNotification();
|
this._updateConfCallNotification();
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', this.onPageUnload);
|
||||||
window.addEventListener('resize', this.onResize);
|
window.addEventListener('resize', this.onResize);
|
||||||
this.onResize();
|
this.onResize();
|
||||||
|
|
||||||
|
@ -355,6 +356,7 @@ module.exports = React.createClass({
|
||||||
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.removeEventListener('beforeunload', this.onPageUnload);
|
||||||
window.removeEventListener('resize', this.onResize);
|
window.removeEventListener('resize', this.onResize);
|
||||||
|
|
||||||
document.removeEventListener("keydown", this.onKeyDown);
|
document.removeEventListener("keydown", this.onKeyDown);
|
||||||
|
@ -367,6 +369,17 @@ module.exports = React.createClass({
|
||||||
// Tinter.tint(); // reset colourscheme
|
// 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) {
|
onKeyDown: function(ev) {
|
||||||
let handled = false;
|
let handled = false;
|
||||||
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
||||||
|
@ -1286,12 +1299,7 @@ module.exports = React.createClass({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var pos = this.refs.messagePanel.getReadMarkerPosition();
|
const showBar = this.refs.messagePanel.canJumpToReadMarker();
|
||||||
|
|
||||||
// we want to show the bar if the read-marker is off the top of the
|
|
||||||
// screen.
|
|
||||||
var showBar = (pos < 0);
|
|
||||||
|
|
||||||
if (this.state.showTopUnreadMessagesBar != showBar) {
|
if (this.state.showTopUnreadMessagesBar != showBar) {
|
||||||
this.setState({showTopUnreadMessagesBar: showBar},
|
this.setState({showTopUnreadMessagesBar: showBar},
|
||||||
this.onChildResize);
|
this.onChildResize);
|
||||||
|
@ -1774,6 +1782,7 @@ module.exports = React.createClass({
|
||||||
oobData={this.props.oobData}
|
oobData={this.props.oobData}
|
||||||
editing={this.state.editingRoomSettings}
|
editing={this.state.editingRoomSettings}
|
||||||
saving={this.state.uploadingRoomSettings}
|
saving={this.state.uploadingRoomSettings}
|
||||||
|
inRoom={myMember && myMember.membership === 'join'}
|
||||||
collapsedRhs={ this.props.collapsedRhs }
|
collapsedRhs={ this.props.collapsedRhs }
|
||||||
onSearchClick={this.onSearchClick}
|
onSearchClick={this.onSearchClick}
|
||||||
onSettingsClick={this.onSettingsClick}
|
onSettingsClick={this.onSettingsClick}
|
||||||
|
|
|
@ -46,9 +46,13 @@ if (DEBUG_SCROLL) {
|
||||||
* It also provides a hook which allows parents to provide more list elements
|
* 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.
|
* 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
|
* Each child element should have a 'data-scroll-tokens'. This string of
|
||||||
* serialise the scroll state, and returned as the 'trackedScrollToken'
|
* comma-separated tokens may contain a single token or many, where many indicates
|
||||||
* attribute by getScrollState().
|
* 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:
|
* Some notes about the implementation:
|
||||||
*
|
*
|
||||||
|
@ -349,8 +353,8 @@ module.exports = React.createClass({
|
||||||
// Subtract height of tile as if it were unpaginated
|
// Subtract height of tile as if it were unpaginated
|
||||||
excessHeight -= tile.clientHeight;
|
excessHeight -= tile.clientHeight;
|
||||||
// The tile may not have a scroll token, so guard it
|
// The tile may not have a scroll token, so guard it
|
||||||
if (tile.dataset.scrollToken) {
|
if (tile.dataset.scrollTokens) {
|
||||||
markerScrollToken = tile.dataset.scrollToken;
|
markerScrollToken = tile.dataset.scrollTokens.split(',')[0];
|
||||||
}
|
}
|
||||||
if (tile.clientHeight > excessHeight) {
|
if (tile.clientHeight > excessHeight) {
|
||||||
break;
|
break;
|
||||||
|
@ -419,7 +423,8 @@ module.exports = React.createClass({
|
||||||
* scroll. false if we are tracking a particular child.
|
* scroll. false if we are tracking a particular child.
|
||||||
*
|
*
|
||||||
* string trackedScrollToken: undefined if stuckAtBottom is true; if it is
|
* 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,
|
* number pixelOffset: undefined if stuckAtBottom is true; if it is false,
|
||||||
* the number of pixels the bottom of the tracked child is above the
|
* 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;
|
var messages = this.refs.itemlist.children;
|
||||||
for (var i = messages.length-1; i >= 0; --i) {
|
for (var i = messages.length-1; i >= 0; --i) {
|
||||||
var m = messages[i];
|
var m = messages[i];
|
||||||
if (!m.dataset.scrollToken) continue;
|
// 'data-scroll-tokens' is a DOMString of comma-separated scroll tokens
|
||||||
if (m.dataset.scrollToken == scrollToken) {
|
// There might only be one scroll token
|
||||||
|
if (m.dataset.scrollTokens &&
|
||||||
|
m.dataset.scrollTokens.split(',').indexOf(scrollToken) !== -1) {
|
||||||
node = m;
|
node = m;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -568,7 +575,7 @@ module.exports = React.createClass({
|
||||||
var boundingRect = node.getBoundingClientRect();
|
var boundingRect = node.getBoundingClientRect();
|
||||||
var scrollDelta = boundingRect.bottom + pixelOffset - wrapperRect.bottom;
|
var scrollDelta = boundingRect.bottom + pixelOffset - wrapperRect.bottom;
|
||||||
|
|
||||||
debuglog("ScrollPanel: scrolling to token '" + node.dataset.scrollToken + "'+" +
|
debuglog("ScrollPanel: scrolling to token '" + scrollToken + "'+" +
|
||||||
pixelOffset + " (delta: "+scrollDelta+")");
|
pixelOffset + " (delta: "+scrollDelta+")");
|
||||||
|
|
||||||
if(scrollDelta != 0) {
|
if(scrollDelta != 0) {
|
||||||
|
@ -591,12 +598,12 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
for (var i = messages.length-1; i >= 0; --i) {
|
for (var i = messages.length-1; i >= 0; --i) {
|
||||||
var node = messages[i];
|
var node = messages[i];
|
||||||
if (!node.dataset.scrollToken) continue;
|
if (!node.dataset.scrollTokens) continue;
|
||||||
|
|
||||||
var boundingRect = node.getBoundingClientRect();
|
var boundingRect = node.getBoundingClientRect();
|
||||||
newScrollState = {
|
newScrollState = {
|
||||||
stuckAtBottom: false,
|
stuckAtBottom: false,
|
||||||
trackedScrollToken: node.dataset.scrollToken,
|
trackedScrollToken: node.dataset.scrollTokens.split(',')[0],
|
||||||
pixelOffset: wrapperRect.bottom - boundingRect.bottom,
|
pixelOffset: wrapperRect.bottom - boundingRect.bottom,
|
||||||
};
|
};
|
||||||
// If the bottom of the panel intersects the ClientRect of node, use this node
|
// If the bottom of the panel intersects the ClientRect of node, use this node
|
||||||
|
@ -608,7 +615,7 @@ module.exports = React.createClass({
|
||||||
break;
|
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) {
|
if (newScrollState) {
|
||||||
this.scrollState = newScrollState;
|
this.scrollState = newScrollState;
|
||||||
debuglog("ScrollPanel: saved scroll state", this.scrollState);
|
debuglog("ScrollPanel: saved scroll state", this.scrollState);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 OpenMarket Ltd
|
Copyright 2016 OpenMarket Ltd
|
||||||
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -167,14 +168,17 @@ var TimelinePanel = React.createClass({
|
||||||
|
|
||||||
backPaginating: false,
|
backPaginating: false,
|
||||||
forwardPaginating: false,
|
forwardPaginating: false,
|
||||||
|
|
||||||
|
// cache of matrixClient.getSyncState() (but from the 'sync' event)
|
||||||
|
clientSyncState: MatrixClientPeg.get().getSyncState(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
debuglog("TimelinePanel: mounting");
|
debuglog("TimelinePanel: mounting");
|
||||||
|
|
||||||
this.last_rr_sent_event_id = undefined;
|
this.lastRRSentEventId = undefined;
|
||||||
this.last_rm_sent_event_id = undefined;
|
this.lastRMSentEventId = undefined;
|
||||||
|
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
|
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
|
||||||
|
@ -183,6 +187,7 @@ var TimelinePanel = React.createClass({
|
||||||
MatrixClientPeg.get().on("Room.receipt", this.onRoomReceipt);
|
MatrixClientPeg.get().on("Room.receipt", this.onRoomReceipt);
|
||||||
MatrixClientPeg.get().on("Room.localEchoUpdated", this.onLocalEchoUpdated);
|
MatrixClientPeg.get().on("Room.localEchoUpdated", this.onLocalEchoUpdated);
|
||||||
MatrixClientPeg.get().on("Room.accountData", this.onAccountData);
|
MatrixClientPeg.get().on("Room.accountData", this.onAccountData);
|
||||||
|
MatrixClientPeg.get().on("sync", this.onSync);
|
||||||
|
|
||||||
this._initTimeline(this.props);
|
this._initTimeline(this.props);
|
||||||
},
|
},
|
||||||
|
@ -251,6 +256,7 @@ var TimelinePanel = React.createClass({
|
||||||
client.removeListener("Room.receipt", this.onRoomReceipt);
|
client.removeListener("Room.receipt", this.onRoomReceipt);
|
||||||
client.removeListener("Room.localEchoUpdated", this.onLocalEchoUpdated);
|
client.removeListener("Room.localEchoUpdated", this.onLocalEchoUpdated);
|
||||||
client.removeListener("Room.accountData", this.onAccountData);
|
client.removeListener("Room.accountData", this.onAccountData);
|
||||||
|
client.removeListener("sync", this.onSync);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -487,17 +493,24 @@ var TimelinePanel = React.createClass({
|
||||||
}, this.props.onReadMarkerUpdated);
|
}, this.props.onReadMarkerUpdated);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onSync: function(state, prevState, data) {
|
||||||
|
this.setState({clientSyncState: state});
|
||||||
|
},
|
||||||
|
|
||||||
sendReadReceipt: function() {
|
sendReadReceipt: function() {
|
||||||
if (!this.refs.messagePanel) return;
|
if (!this.refs.messagePanel) return;
|
||||||
if (!this.props.manageReadReceipts) return;
|
if (!this.props.manageReadReceipts) return;
|
||||||
// This happens on user_activity_end which is delayed, and it's
|
// This happens on user_activity_end which is delayed, and it's
|
||||||
// very possible have logged out within that timeframe, so check
|
// very possible have logged out within that timeframe, so check
|
||||||
// we still have a client.
|
// we still have a client.
|
||||||
if (!MatrixClientPeg.get()) return;
|
const cli = MatrixClientPeg.get();
|
||||||
|
// if no client or client is guest don't send RR or RM
|
||||||
|
if (!cli || cli.isGuest()) return;
|
||||||
|
|
||||||
var currentReadUpToEventId = this._getCurrentReadReceipt(true);
|
let shouldSendRR = true;
|
||||||
var currentReadUpToEventIndex = this._indexForEventId(currentReadUpToEventId);
|
|
||||||
|
|
||||||
|
const currentRREventId = this._getCurrentReadReceipt(true);
|
||||||
|
const currentRREventIndex = this._indexForEventId(currentRREventId);
|
||||||
// We want to avoid sending out read receipts when we are looking at
|
// We want to avoid sending out read receipts when we are looking at
|
||||||
// events in the past which are before the latest RR.
|
// events in the past which are before the latest RR.
|
||||||
//
|
//
|
||||||
|
@ -511,43 +524,60 @@ var TimelinePanel = React.createClass({
|
||||||
// RRs) - but that is a bit of a niche case. It will sort itself out when
|
// RRs) - but that is a bit of a niche case. It will sort itself out when
|
||||||
// the user eventually hits the live timeline.
|
// the user eventually hits the live timeline.
|
||||||
//
|
//
|
||||||
if (currentReadUpToEventId && currentReadUpToEventIndex === null &&
|
if (currentRREventId && currentRREventIndex === null &&
|
||||||
this._timelineWindow.canPaginate(EventTimeline.FORWARDS)) {
|
this._timelineWindow.canPaginate(EventTimeline.FORWARDS)) {
|
||||||
return;
|
shouldSendRR = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastReadEventIndex = this._getLastDisplayedEventIndex({
|
const lastReadEventIndex = this._getLastDisplayedEventIndex({
|
||||||
ignoreOwn: true
|
ignoreOwn: true,
|
||||||
});
|
});
|
||||||
if (lastReadEventIndex === null) return;
|
if (lastReadEventIndex === null) {
|
||||||
|
shouldSendRR = false;
|
||||||
|
}
|
||||||
|
let lastReadEvent = this.state.events[lastReadEventIndex];
|
||||||
|
shouldSendRR = shouldSendRR &&
|
||||||
|
// Only send a RR if the last read event is ahead in the timeline relative to
|
||||||
|
// the current RR event.
|
||||||
|
lastReadEventIndex > currentRREventIndex &&
|
||||||
|
// Only send a RR if the last RR set != the one we would send
|
||||||
|
this.lastRRSentEventId != lastReadEvent.getId();
|
||||||
|
|
||||||
var lastReadEvent = this.state.events[lastReadEventIndex];
|
// Only send a RM if the last RM sent != the one we would send
|
||||||
|
const shouldSendRM =
|
||||||
|
this.lastRMSentEventId != this.state.readMarkerEventId;
|
||||||
|
|
||||||
// we also remember the last read receipt we sent to avoid spamming the
|
// we also remember the last read receipt we sent to avoid spamming the
|
||||||
// same one at the server repeatedly
|
// same one at the server repeatedly
|
||||||
if ((lastReadEventIndex > currentReadUpToEventIndex &&
|
if (shouldSendRR || shouldSendRM) {
|
||||||
this.last_rr_sent_event_id != lastReadEvent.getId()) ||
|
if (shouldSendRR) {
|
||||||
this.last_rm_sent_event_id != this.state.readMarkerEventId) {
|
this.lastRRSentEventId = lastReadEvent.getId();
|
||||||
|
} else {
|
||||||
this.last_rr_sent_event_id = lastReadEvent.getId();
|
lastReadEvent = null;
|
||||||
this.last_rm_sent_event_id = this.state.readMarkerEventId;
|
}
|
||||||
|
this.lastRMSentEventId = this.state.readMarkerEventId;
|
||||||
|
|
||||||
|
debuglog('TimelinePanel: Sending Read Markers for ',
|
||||||
|
this.props.timelineSet.room.roomId,
|
||||||
|
'rm', this.state.readMarkerEventId,
|
||||||
|
lastReadEvent ? 'rr ' + lastReadEvent.getId() : '',
|
||||||
|
);
|
||||||
MatrixClientPeg.get().setRoomReadMarkers(
|
MatrixClientPeg.get().setRoomReadMarkers(
|
||||||
this.props.timelineSet.room.roomId,
|
this.props.timelineSet.room.roomId,
|
||||||
this.state.readMarkerEventId,
|
this.state.readMarkerEventId,
|
||||||
lastReadEvent
|
lastReadEvent, // Could be null, in which case no RR is sent
|
||||||
).catch((e) => {
|
).catch((e) => {
|
||||||
// /read_markers API is not implemented on this HS, fallback to just RR
|
// /read_markers API is not implemented on this HS, fallback to just RR
|
||||||
if (e.errcode === 'M_UNRECOGNIZED') {
|
if (e.errcode === 'M_UNRECOGNIZED' && lastReadEvent) {
|
||||||
return MatrixClientPeg.get().sendReadReceipt(
|
return MatrixClientPeg.get().sendReadReceipt(
|
||||||
lastReadEvent
|
lastReadEvent,
|
||||||
).catch(() => {
|
).catch(() => {
|
||||||
this.last_rr_sent_event_id = undefined;
|
this.lastRRSentEventId = undefined;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// it failed, so allow retries next time the user is active
|
// it failed, so allow retries next time the user is active
|
||||||
this.last_rr_sent_event_id = undefined;
|
this.lastRRSentEventId = undefined;
|
||||||
this.last_rm_sent_event_id = undefined;
|
this.lastRMSentEventId = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
// do a quick-reset of our unreadNotificationCount to avoid having
|
// do a quick-reset of our unreadNotificationCount to avoid having
|
||||||
|
@ -560,7 +590,6 @@ var TimelinePanel = React.createClass({
|
||||||
this.props.timelineSet.room.setUnreadNotificationCount('highlight', 0);
|
this.props.timelineSet.room.setUnreadNotificationCount('highlight', 0);
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'on_room_read',
|
action: 'on_room_read',
|
||||||
room: this.props.timelineSet.room,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -756,6 +785,19 @@ var TimelinePanel = React.createClass({
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
canJumpToReadMarker: function() {
|
||||||
|
// 1. Do not show jump bar if neither the RM nor the RR are set.
|
||||||
|
// 2. Only show jump bar if RR !== RM. If they are the same, there are only fully
|
||||||
|
// read messages and unread messages. We already have a badge count and the bottom
|
||||||
|
// bar to jump to "live" when we have unread messages.
|
||||||
|
// 3. We want to show the bar if the read-marker is off the top of the screen.
|
||||||
|
// 4. Also, if pos === null, the event might not be paginated - show the unread bar
|
||||||
|
const pos = this.getReadMarkerPosition();
|
||||||
|
return this.state.readMarkerEventId !== null && // 1.
|
||||||
|
this.state.readMarkerEventId !== this._getCurrentReadReceipt() && // 2.
|
||||||
|
(pos < 0 || pos === null); // 3., 4.
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* called by the parent component when PageUp/Down/etc is pressed.
|
* called by the parent component when PageUp/Down/etc is pressed.
|
||||||
*
|
*
|
||||||
|
@ -1058,11 +1100,18 @@ var TimelinePanel = React.createClass({
|
||||||
// events when viewing historical messages, we get stuck in a loop
|
// events when viewing historical messages, we get stuck in a loop
|
||||||
// of paginating our way through the entire history of the room.
|
// of paginating our way through the entire history of the room.
|
||||||
var stickyBottom = !this._timelineWindow.canPaginate(EventTimeline.FORWARDS);
|
var stickyBottom = !this._timelineWindow.canPaginate(EventTimeline.FORWARDS);
|
||||||
|
|
||||||
|
// If the state is PREPARED, we're still waiting for the js-sdk to sync with
|
||||||
|
// the HS and fetch the latest events, so we are effectively forward paginating.
|
||||||
|
const forwardPaginating = (
|
||||||
|
this.state.forwardPaginating || this.state.clientSyncState == 'PREPARED'
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MessagePanel ref="messagePanel"
|
<MessagePanel ref="messagePanel"
|
||||||
hidden={ this.props.hidden }
|
hidden={ this.props.hidden }
|
||||||
backPaginating={ this.state.backPaginating }
|
backPaginating={ this.state.backPaginating }
|
||||||
forwardPaginating={ this.state.forwardPaginating }
|
forwardPaginating={ forwardPaginating }
|
||||||
events={ this.state.events }
|
events={ this.state.events }
|
||||||
highlightedEventId={ this.props.highlightedEventId }
|
highlightedEventId={ this.props.highlightedEventId }
|
||||||
readMarkerEventId={ this.state.readMarkerEventId }
|
readMarkerEventId={ this.state.readMarkerEventId }
|
||||||
|
|
|
@ -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
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
var React = require('react');
|
const React = require('react');
|
||||||
var ReactDOM = require('react-dom');
|
const ReactDOM = require('react-dom');
|
||||||
var sdk = require('../../index');
|
const sdk = require('../../index');
|
||||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
const MatrixClientPeg = require("../../MatrixClientPeg");
|
||||||
var PlatformPeg = require("../../PlatformPeg");
|
const PlatformPeg = require("../../PlatformPeg");
|
||||||
var Modal = require('../../Modal');
|
const Modal = require('../../Modal');
|
||||||
var dis = require("../../dispatcher");
|
const dis = require("../../dispatcher");
|
||||||
var q = require('q');
|
const q = require('q');
|
||||||
var package_json = require('../../../package.json');
|
const packageJson = require('../../../package.json');
|
||||||
var UserSettingsStore = require('../../UserSettingsStore');
|
const UserSettingsStore = require('../../UserSettingsStore');
|
||||||
var GeminiScrollbar = require('react-gemini-scrollbar');
|
const GeminiScrollbar = require('react-gemini-scrollbar');
|
||||||
var Email = require('../../email');
|
const Email = require('../../email');
|
||||||
var AddThreepid = require('../../AddThreepid');
|
const AddThreepid = require('../../AddThreepid');
|
||||||
var SdkConfig = require('../../SdkConfig');
|
const SdkConfig = require('../../SdkConfig');
|
||||||
import AccessibleButton from '../views/elements/AccessibleButton';
|
import AccessibleButton from '../views/elements/AccessibleButton';
|
||||||
|
|
||||||
// if this looks like a release, use the 'version' from package.json; else use
|
// 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
|
// 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.
|
// Simple method to help prettify GH Release Tags and Commit Hashes.
|
||||||
const GHVersionUrl = function(repo, token) {
|
const semVerRegex = /^v?(\d+\.\d+\.\d+(?:-rc.+)?)(?:-(?:\d+-g)?([0-9a-fA-F]+))?(?:-dirty)?$/i;
|
||||||
const uriTail = (token.startsWith('v') && token.includes('.')) ? `releases/tag/${token}` : `commit/${token}`;
|
const gHVersionLabel = function(repo, token='') {
|
||||||
return `https://github.com/${repo}/${uriTail}`;
|
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).
|
// Enumerate some simple 'flip a bit' UI settings (if any).
|
||||||
// 'id' gives the key name in the im.vector.web.settings account data event
|
// 'id' gives the key name in the im.vector.web.settings account data event
|
||||||
|
@ -50,7 +59,7 @@ const SETTINGS_LABELS = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'hideReadReceipts',
|
id: 'hideReadReceipts',
|
||||||
label: 'Hide read receipts'
|
label: 'Hide read receipts',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'dontSendTypingNotifications',
|
id: 'dontSendTypingNotifications',
|
||||||
|
@ -106,7 +115,7 @@ const THEMES = [
|
||||||
id: 'theme',
|
id: 'theme',
|
||||||
label: 'Dark theme',
|
label: 'Dark theme',
|
||||||
value: 'dark',
|
value: 'dark',
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
@ -142,10 +151,10 @@ module.exports = React.createClass({
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
avatarUrl: null,
|
avatarUrl: null,
|
||||||
threePids: [],
|
threepids: [],
|
||||||
phase: "UserSettings.LOADING", // LOADING, DISPLAY
|
phase: "UserSettings.LOADING", // LOADING, DISPLAY
|
||||||
email_add_pending: false,
|
email_add_pending: false,
|
||||||
vectorVersion: null,
|
vectorVersion: undefined,
|
||||||
rejectingInvites: false,
|
rejectingInvites: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -180,7 +189,7 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
this._refreshFromServer();
|
this._refreshFromServer();
|
||||||
|
|
||||||
var syncedSettings = UserSettingsStore.getSyncedSettings();
|
const syncedSettings = UserSettingsStore.getSyncedSettings();
|
||||||
if (!syncedSettings.theme) {
|
if (!syncedSettings.theme) {
|
||||||
syncedSettings.theme = 'light';
|
syncedSettings.theme = 'light';
|
||||||
}
|
}
|
||||||
|
@ -202,16 +211,16 @@ module.exports = React.createClass({
|
||||||
middleOpacity: 1.0,
|
middleOpacity: 1.0,
|
||||||
});
|
});
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
let cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
if (cli) {
|
if (cli) {
|
||||||
cli.removeListener("RoomMember.membership", this._onInviteStateChange);
|
cli.removeListener("RoomMember.membership", this._onInviteStateChange);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_refreshFromServer: function() {
|
_refreshFromServer: function() {
|
||||||
var self = this;
|
const self = this;
|
||||||
q.all([
|
q.all([
|
||||||
UserSettingsStore.loadProfileInfo(), UserSettingsStore.loadThreePids()
|
UserSettingsStore.loadProfileInfo(), UserSettingsStore.loadThreePids(),
|
||||||
]).done(function(resps) {
|
]).done(function(resps) {
|
||||||
self.setState({
|
self.setState({
|
||||||
avatarUrl: resps[0].avatar_url,
|
avatarUrl: resps[0].avatar_url,
|
||||||
|
@ -219,7 +228,7 @@ module.exports = React.createClass({
|
||||||
phase: "UserSettings.DISPLAY",
|
phase: "UserSettings.DISPLAY",
|
||||||
});
|
});
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
console.error("Failed to load user settings: " + error);
|
console.error("Failed to load user settings: " + error);
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Can't load user settings",
|
title: "Can't load user settings",
|
||||||
|
@ -236,7 +245,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
onAvatarPickerClick: function(ev) {
|
onAvatarPickerClick: function(ev) {
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
const NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||||
Modal.createDialog(NeedToRegisterDialog, {
|
Modal.createDialog(NeedToRegisterDialog, {
|
||||||
title: "Please Register",
|
title: "Please Register",
|
||||||
description: "Guests can't set avatars. Please register.",
|
description: "Guests can't set avatars. Please register.",
|
||||||
|
@ -250,8 +259,8 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onAvatarSelected: function(ev) {
|
onAvatarSelected: function(ev) {
|
||||||
var self = this;
|
const self = this;
|
||||||
var changeAvatar = this.refs.changeAvatar;
|
const changeAvatar = this.refs.changeAvatar;
|
||||||
if (!changeAvatar) {
|
if (!changeAvatar) {
|
||||||
console.error("No ChangeAvatar found to upload image to!");
|
console.error("No ChangeAvatar found to upload image to!");
|
||||||
return;
|
return;
|
||||||
|
@ -260,9 +269,9 @@ module.exports = React.createClass({
|
||||||
// dunno if the avatar changed, re-check it.
|
// dunno if the avatar changed, re-check it.
|
||||||
self._refreshFromServer();
|
self._refreshFromServer();
|
||||||
}, function(err) {
|
}, 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);
|
console.error("Failed to set avatar: " + err);
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Failed to set avatar",
|
title: "Failed to set avatar",
|
||||||
description: ((err && err.message) ? err.message : "Operation failed"),
|
description: ((err && err.message) ? err.message : "Operation failed"),
|
||||||
|
@ -271,7 +280,7 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onLogoutClicked: function(ev) {
|
onLogoutClicked: function(ev) {
|
||||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
Modal.createDialog(QuestionDialog, {
|
Modal.createDialog(QuestionDialog, {
|
||||||
title: "Sign out?",
|
title: "Sign out?",
|
||||||
description:
|
description:
|
||||||
|
@ -286,7 +295,7 @@ module.exports = React.createClass({
|
||||||
<button key="export" className="mx_Dialog_primary"
|
<button key="export" className="mx_Dialog_primary"
|
||||||
onClick={this._onExportE2eKeysClicked}>
|
onClick={this._onExportE2eKeysClicked}>
|
||||||
Export E2E room keys
|
Export E2E room keys
|
||||||
</button>
|
</button>,
|
||||||
],
|
],
|
||||||
onFinished: (confirmed) => {
|
onFinished: (confirmed) => {
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
|
@ -300,34 +309,33 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onPasswordChangeError: function(err) {
|
onPasswordChangeError: function(err) {
|
||||||
var errMsg = err.error || "";
|
let errMsg = err.error || "";
|
||||||
if (err.httpStatus === 403) {
|
if (err.httpStatus === 403) {
|
||||||
errMsg = "Failed to change password. Is your password correct?";
|
errMsg = "Failed to change password. Is your password correct?";
|
||||||
}
|
} else if (err.httpStatus) {
|
||||||
else if (err.httpStatus) {
|
|
||||||
errMsg += ` (HTTP status ${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);
|
console.error("Failed to change password: " + errMsg);
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Error",
|
title: "Error",
|
||||||
description: errMsg
|
description: errMsg,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onPasswordChanged: function() {
|
onPasswordChanged: function() {
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Success",
|
title: "Success",
|
||||||
description: `Your password was successfully changed. You will not
|
description: `Your password was successfully changed. You will not
|
||||||
receive push notifications on other devices until you
|
receive push notifications on other devices until you
|
||||||
log back in to them.`
|
log back in to them.`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onUpgradeClicked: function() {
|
onUpgradeClicked: function() {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "start_upgrade_registration"
|
action: "start_upgrade_registration",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -341,11 +349,11 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_addEmail: function() {
|
_addEmail: function() {
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
|
|
||||||
var email_address = this.refs.add_email_input.value;
|
const emailAddress = this.refs.add_email_input.value;
|
||||||
if (!Email.looksValid(email_address)) {
|
if (!Email.looksValid(emailAddress)) {
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Invalid Email Address",
|
title: "Invalid Email Address",
|
||||||
description: "This doesn't appear to be a valid 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();
|
this._addThreepid = new AddThreepid();
|
||||||
// we always bind emails when registering, so let's do the
|
// we always bind emails when registering, so let's do the
|
||||||
// same here.
|
// same here.
|
||||||
this._addThreepid.addEmailAddress(email_address, true).done(() => {
|
this._addThreepid.addEmailAddress(emailAddress, true).done(() => {
|
||||||
Modal.createDialog(QuestionDialog, {
|
Modal.createDialog(QuestionDialog, {
|
||||||
title: "Verification Pending",
|
title: "Verification Pending",
|
||||||
description: "Please check your email and click on the link it contains. Once this is done, click continue.",
|
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) => {
|
}, (err) => {
|
||||||
this.setState({email_add_pending: false});
|
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, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Unable to add email address",
|
title: "Unable to add email address",
|
||||||
description: ((err && err.message) ? err.message : "Operation failed"),
|
description: ((err && err.message) ? err.message : "Operation failed"),
|
||||||
|
@ -418,9 +426,9 @@ module.exports = React.createClass({
|
||||||
this.setState({email_add_pending: false});
|
this.setState({email_add_pending: false});
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
this.setState({email_add_pending: false});
|
this.setState({email_add_pending: false});
|
||||||
if (err.errcode == 'M_THREEPID_AUTH_FAILED') {
|
if (err.errcode === 'M_THREEPID_AUTH_FAILED') {
|
||||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
var message = "Unable to verify email address. ";
|
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.";
|
message += "Please check your email and click on the link it contains. Once this is done, click continue.";
|
||||||
Modal.createDialog(QuestionDialog, {
|
Modal.createDialog(QuestionDialog, {
|
||||||
title: "Verification Pending",
|
title: "Verification Pending",
|
||||||
|
@ -429,7 +437,7 @@ module.exports = React.createClass({
|
||||||
onFinished: this.onEmailDialogFinished,
|
onFinished: this.onEmailDialogFinished,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
console.error("Unable to verify email address: " + err);
|
console.error("Unable to verify email address: " + err);
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Unable to verify email address",
|
title: "Unable to verify email address",
|
||||||
|
@ -469,17 +477,17 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
_onRejectAllInvitesClicked: function(rooms, ev) {
|
_onRejectAllInvitesClicked: function(rooms, ev) {
|
||||||
this.setState({
|
this.setState({
|
||||||
rejectingInvites: true
|
rejectingInvites: true,
|
||||||
});
|
});
|
||||||
// reject the invites
|
// reject the invites
|
||||||
let promises = rooms.map((room) => {
|
const promises = rooms.map((room) => {
|
||||||
return MatrixClientPeg.get().leave(room.roomId);
|
return MatrixClientPeg.get().leave(room.roomId);
|
||||||
});
|
});
|
||||||
// purposefully drop errors to the floor: we'll just have a non-zero number on the UI
|
// purposefully drop errors to the floor: we'll just have a non-zero number on the UI
|
||||||
// after trying to reject all the invites.
|
// after trying to reject all the invites.
|
||||||
q.allSettled(promises).then(() => {
|
q.allSettled(promises).then(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
rejectingInvites: false
|
rejectingInvites: false,
|
||||||
});
|
});
|
||||||
}).done();
|
}).done();
|
||||||
},
|
},
|
||||||
|
@ -492,7 +500,7 @@ module.exports = React.createClass({
|
||||||
}, "e2e-export");
|
}, "e2e-export");
|
||||||
}, {
|
}, {
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: MatrixClientPeg.get(),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -504,7 +512,7 @@ module.exports = React.createClass({
|
||||||
}, "e2e-export");
|
}, "e2e-export");
|
||||||
}, {
|
}, {
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: MatrixClientPeg.get(),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -530,8 +538,6 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderUserInterfaceSettings: function() {
|
_renderUserInterfaceSettings: function() {
|
||||||
var client = MatrixClientPeg.get();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h3>User Interface</h3>
|
<h3>User Interface</h3>
|
||||||
|
@ -549,7 +555,7 @@ module.exports = React.createClass({
|
||||||
<input id="urlPreviewsDisabled"
|
<input id="urlPreviewsDisabled"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
defaultChecked={ UserSettingsStore.getUrlPreviewsDisabled() }
|
defaultChecked={ UserSettingsStore.getUrlPreviewsDisabled() }
|
||||||
onChange={ e => UserSettingsStore.setUrlPreviewsDisabled(e.target.checked) }
|
onChange={ (e) => UserSettingsStore.setUrlPreviewsDisabled(e.target.checked) }
|
||||||
/>
|
/>
|
||||||
<label htmlFor="urlPreviewsDisabled">
|
<label htmlFor="urlPreviewsDisabled">
|
||||||
Disable inline URL previews by default
|
Disable inline URL previews by default
|
||||||
|
@ -562,7 +568,7 @@ module.exports = React.createClass({
|
||||||
<input id={ setting.id }
|
<input id={ setting.id }
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
defaultChecked={ this._syncedSettings[setting.id] }
|
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 }>
|
<label htmlFor={ setting.id }>
|
||||||
{ setting.label }
|
{ setting.label }
|
||||||
|
@ -577,7 +583,7 @@ module.exports = React.createClass({
|
||||||
name={ setting.id }
|
name={ setting.id }
|
||||||
value={ setting.value }
|
value={ setting.value }
|
||||||
defaultChecked={ this._syncedSettings[setting.id] === setting.value }
|
defaultChecked={ this._syncedSettings[setting.id] === setting.value }
|
||||||
onChange={ e => {
|
onChange={ (e) => {
|
||||||
if (e.target.checked) {
|
if (e.target.checked) {
|
||||||
UserSettingsStore.setSyncedSetting(setting.id, setting.value);
|
UserSettingsStore.setSyncedSetting(setting.id, setting.value);
|
||||||
}
|
}
|
||||||
|
@ -639,8 +645,8 @@ module.exports = React.createClass({
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
defaultChecked={ this._localSettings[setting.id] }
|
defaultChecked={ this._localSettings[setting.id] }
|
||||||
onChange={
|
onChange={
|
||||||
e => {
|
(e) => {
|
||||||
UserSettingsStore.setLocalSetting(setting.id, e.target.checked)
|
UserSettingsStore.setLocalSetting(setting.id, e.target.checked);
|
||||||
if (setting.id === 'blacklistUnverifiedDevices') { // XXX: this is a bit ugly
|
if (setting.id === 'blacklistUnverifiedDevices') { // XXX: this is a bit ugly
|
||||||
client.setGlobalBlacklistUnverifiedDevices(e.target.checked);
|
client.setGlobalBlacklistUnverifiedDevices(e.target.checked);
|
||||||
}
|
}
|
||||||
|
@ -654,7 +660,7 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderDevicesPanel: function() {
|
_renderDevicesPanel: function() {
|
||||||
var DevicesPanel = sdk.getComponent('settings.DevicesPanel');
|
const DevicesPanel = sdk.getComponent('settings.DevicesPanel');
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h3>Devices</h3>
|
<h3>Devices</h3>
|
||||||
|
@ -665,7 +671,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
_renderBugReport: function() {
|
_renderBugReport: function() {
|
||||||
if (!SdkConfig.get().bug_report_endpoint_url) {
|
if (!SdkConfig.get().bug_report_endpoint_url) {
|
||||||
return <div />
|
return <div />;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -684,17 +690,17 @@ module.exports = React.createClass({
|
||||||
// default to enabled if undefined
|
// default to enabled if undefined
|
||||||
if (this.props.enableLabs === false) return null;
|
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">
|
<div key={feature.id} className="mx_UserSettings_toggle">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id={feature.id}
|
id={feature.id}
|
||||||
name={feature.id}
|
name={feature.id}
|
||||||
defaultChecked={ UserSettingsStore.isFeatureEnabled(feature.id) }
|
defaultChecked={ UserSettingsStore.isFeatureEnabled(feature.id) }
|
||||||
onChange={e => {
|
onChange={(e) => {
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
e.target.checked = false;
|
e.target.checked = false;
|
||||||
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
const NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||||
Modal.createDialog(NeedToRegisterDialog, {
|
Modal.createDialog(NeedToRegisterDialog, {
|
||||||
title: "Please Register",
|
title: "Please Register",
|
||||||
description: "Guests can't use labs features. Please register.",
|
description: "Guests can't use labs features. Please register.",
|
||||||
|
@ -746,14 +752,14 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderBulkOptions: function() {
|
_renderBulkOptions: function() {
|
||||||
let invitedRooms = MatrixClientPeg.get().getRooms().filter((r) => {
|
const invitedRooms = MatrixClientPeg.get().getRooms().filter((r) => {
|
||||||
return r.hasMembershipState(this._me, "invite");
|
return r.hasMembershipState(this._me, "invite");
|
||||||
});
|
});
|
||||||
if (invitedRooms.length === 0) {
|
if (invitedRooms.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Spinner = sdk.getComponent("elements.Spinner");
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
|
|
||||||
let reject = <Spinner />;
|
let reject = <Spinner />;
|
||||||
if (!this.state.rejectingInvites) {
|
if (!this.state.rejectingInvites) {
|
||||||
|
@ -777,9 +783,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
_showSpoiler: function(event) {
|
_showSpoiler: function(event) {
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
const hidden = target.getAttribute('data-spoiler');
|
target.innerHTML = target.getAttribute('data-spoiler');
|
||||||
|
|
||||||
target.innerHTML = hidden;
|
|
||||||
|
|
||||||
const range = document.createRange();
|
const range = document.createRange();
|
||||||
range.selectNodeContents(target);
|
range.selectNodeContents(target);
|
||||||
|
@ -790,12 +794,12 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
nameForMedium: function(medium) {
|
nameForMedium: function(medium) {
|
||||||
if (medium == 'msisdn') return 'Phone';
|
if (medium === 'msisdn') return 'Phone';
|
||||||
return medium[0].toUpperCase() + medium.slice(1);
|
return medium[0].toUpperCase() + medium.slice(1);
|
||||||
},
|
},
|
||||||
|
|
||||||
presentableTextForThreepid: function(threepid) {
|
presentableTextForThreepid: function(threepid) {
|
||||||
if (threepid.medium == 'msisdn') {
|
if (threepid.medium === 'msisdn') {
|
||||||
return '+' + threepid.address;
|
return '+' + threepid.address;
|
||||||
} else {
|
} else {
|
||||||
return threepid.address;
|
return threepid.address;
|
||||||
|
@ -803,7 +807,7 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var Loader = sdk.getComponent("elements.Spinner");
|
const Loader = sdk.getComponent("elements.Spinner");
|
||||||
switch (this.state.phase) {
|
switch (this.state.phase) {
|
||||||
case "UserSettings.LOADING":
|
case "UserSettings.LOADING":
|
||||||
return (
|
return (
|
||||||
|
@ -815,18 +819,18 @@ module.exports = React.createClass({
|
||||||
throw new Error("Unknown state.phase => " + this.state.phase);
|
throw new Error("Unknown state.phase => " + this.state.phase);
|
||||||
}
|
}
|
||||||
// can only get here if phase is UserSettings.DISPLAY
|
// can only get here if phase is UserSettings.DISPLAY
|
||||||
var SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
|
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
|
||||||
var ChangeDisplayName = sdk.getComponent("views.settings.ChangeDisplayName");
|
const ChangeDisplayName = sdk.getComponent("views.settings.ChangeDisplayName");
|
||||||
var ChangePassword = sdk.getComponent("views.settings.ChangePassword");
|
const ChangePassword = sdk.getComponent("views.settings.ChangePassword");
|
||||||
var ChangeAvatar = sdk.getComponent('settings.ChangeAvatar');
|
const ChangeAvatar = sdk.getComponent('settings.ChangeAvatar');
|
||||||
var Notifications = sdk.getComponent("settings.Notifications");
|
const Notifications = sdk.getComponent("settings.Notifications");
|
||||||
var EditableText = sdk.getComponent('elements.EditableText');
|
const EditableText = sdk.getComponent('elements.EditableText');
|
||||||
|
|
||||||
var avatarUrl = (
|
const avatarUrl = (
|
||||||
this.state.avatarUrl ? MatrixClientPeg.get().mxcUrlToHttp(this.state.avatarUrl) : null
|
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;
|
const id = "3pid-" + val.address;
|
||||||
return (
|
return (
|
||||||
<div className="mx_UserSettings_profileTableRow" key={pidIndex}>
|
<div className="mx_UserSettings_profileTableRow" key={pidIndex}>
|
||||||
|
@ -851,6 +855,7 @@ module.exports = React.createClass({
|
||||||
addEmailSection = (
|
addEmailSection = (
|
||||||
<div className="mx_UserSettings_profileTableRow" key="_newEmail">
|
<div className="mx_UserSettings_profileTableRow" key="_newEmail">
|
||||||
<div className="mx_UserSettings_profileLabelCell">
|
<div className="mx_UserSettings_profileLabelCell">
|
||||||
|
<label>Email</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_UserSettings_profileInputCell">
|
<div className="mx_UserSettings_profileInputCell">
|
||||||
<EditableText
|
<EditableText
|
||||||
|
@ -874,7 +879,7 @@ module.exports = React.createClass({
|
||||||
threepidsSection.push(addEmailSection);
|
threepidsSection.push(addEmailSection);
|
||||||
threepidsSection.push(addMsisdnSection);
|
threepidsSection.push(addMsisdnSection);
|
||||||
|
|
||||||
var accountJsx;
|
let accountJsx;
|
||||||
|
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
accountJsx = (
|
accountJsx = (
|
||||||
|
@ -882,8 +887,7 @@ module.exports = React.createClass({
|
||||||
Create an account
|
Create an account
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
accountJsx = (
|
accountJsx = (
|
||||||
<ChangePassword
|
<ChangePassword
|
||||||
className="mx_UserSettings_accountTable"
|
className="mx_UserSettings_accountTable"
|
||||||
|
@ -895,9 +899,9 @@ module.exports = React.createClass({
|
||||||
onFinished={this.onPasswordChanged} />
|
onFinished={this.onPasswordChanged} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
var notification_area;
|
let notificationArea;
|
||||||
if (!MatrixClientPeg.get().isGuest() && this.state.threepids !== undefined) {
|
if (!MatrixClientPeg.get().isGuest() && this.state.threepids !== undefined) {
|
||||||
notification_area = (<div>
|
notificationArea = (<div>
|
||||||
<h3>Notifications</h3>
|
<h3>Notifications</h3>
|
||||||
|
|
||||||
<div className="mx_UserSettings_section">
|
<div className="mx_UserSettings_section">
|
||||||
|
@ -911,7 +915,7 @@ module.exports = React.createClass({
|
||||||
// we are using a version old version of olm. We assume the former.
|
// we are using a version old version of olm. We assume the former.
|
||||||
let olmVersionString = "<not-enabled>";
|
let olmVersionString = "<not-enabled>";
|
||||||
if (olmVersion !== undefined) {
|
if (olmVersion !== undefined) {
|
||||||
olmVersionString = `v${olmVersion[0]}.${olmVersion[1]}.${olmVersion[2]}`;
|
olmVersionString = `${olmVersion[0]}.${olmVersion[1]}.${olmVersion[2]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -969,7 +973,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
{this._renderReferral()}
|
{this._renderReferral()}
|
||||||
|
|
||||||
{notification_area}
|
{notificationArea}
|
||||||
|
|
||||||
{this._renderUserInterfaceSettings()}
|
{this._renderUserInterfaceSettings()}
|
||||||
{this._renderLabs()}
|
{this._renderLabs()}
|
||||||
|
@ -985,7 +989,10 @@ module.exports = React.createClass({
|
||||||
Logged in as {this._me}
|
Logged in as {this._me}
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_UserSettings_advanced">
|
<div className="mx_UserSettings_advanced">
|
||||||
Access Token: <span className="mx_UserSettings_advanced_spoiler" onClick={this._showSpoiler} data-spoiler={ MatrixClientPeg.get().getAccessToken() }><click to reveal></span>
|
Access Token: <span className="mx_UserSettings_advanced_spoiler"
|
||||||
|
onClick={this._showSpoiler}
|
||||||
|
data-spoiler={ MatrixClientPeg.get().getAccessToken() }
|
||||||
|
><click to reveal></span>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_UserSettings_advanced">
|
<div className="mx_UserSettings_advanced">
|
||||||
Homeserver is { MatrixClientPeg.get().getHomeserverUrl() }
|
Homeserver is { MatrixClientPeg.get().getHomeserverUrl() }
|
||||||
|
@ -995,11 +1002,11 @@ module.exports = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_UserSettings_advanced">
|
<div className="mx_UserSettings_advanced">
|
||||||
matrix-react-sdk version: {(REACT_SDK_VERSION !== '<local>')
|
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
|
: REACT_SDK_VERSION
|
||||||
}<br/>
|
}<br/>
|
||||||
riot-web version: {(this.state.vectorVersion !== null)
|
riot-web version: {(this.state.vectorVersion !== undefined)
|
||||||
? <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'
|
: 'unknown'
|
||||||
}<br/>
|
}<br/>
|
||||||
olm version: {olmVersionString}<br/>
|
olm version: {olmVersionString}<br/>
|
||||||
|
@ -1013,5 +1020,5 @@ module.exports = React.createClass({
|
||||||
</GeminiScrollbar>
|
</GeminiScrollbar>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,6 +23,9 @@ import url from 'url';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import Login from '../../../Login';
|
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
|
* A wire component which glues together login UI components and Login logic
|
||||||
*/
|
*/
|
||||||
|
@ -125,7 +128,16 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onPhoneNumberChanged: function(phoneNumber) {
|
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) {
|
onServerConfigChange: function(config) {
|
||||||
|
|
|
@ -123,18 +123,17 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onHsUrlChanged: function(newHsUrl) {
|
onServerConfigChange: function(config) {
|
||||||
this.setState({
|
let newState = {};
|
||||||
hsUrl: newHsUrl,
|
if (config.hsUrl !== undefined) {
|
||||||
});
|
newState.hsUrl = config.hsUrl;
|
||||||
|
}
|
||||||
|
if (config.isUrl !== undefined) {
|
||||||
|
newState.isUrl = config.isUrl;
|
||||||
|
}
|
||||||
|
this.setState(newState, function() {
|
||||||
this._replaceClient();
|
this._replaceClient();
|
||||||
},
|
|
||||||
|
|
||||||
onIsUrlChanged: function(newIsUrl) {
|
|
||||||
this.setState({
|
|
||||||
isUrl: newIsUrl,
|
|
||||||
});
|
});
|
||||||
this._replaceClient();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_replaceClient: function() {
|
_replaceClient: function() {
|
||||||
|
@ -390,8 +389,7 @@ module.exports = React.createClass({
|
||||||
customIsUrl={this.props.customIsUrl}
|
customIsUrl={this.props.customIsUrl}
|
||||||
defaultHsUrl={this.props.defaultHsUrl}
|
defaultHsUrl={this.props.defaultHsUrl}
|
||||||
defaultIsUrl={this.props.defaultIsUrl}
|
defaultIsUrl={this.props.defaultIsUrl}
|
||||||
onHsUrlChanged={this.onHsUrlChanged}
|
onServerConfigChange={this.onServerConfigChange}
|
||||||
onIsUrlChanged={this.onIsUrlChanged}
|
|
||||||
delayTimeMs={1000}
|
delayTimeMs={1000}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -59,7 +59,9 @@ module.exports = React.createClass({
|
||||||
ContentRepo.getHttpUriForMxc(
|
ContentRepo.getHttpUriForMxc(
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
MatrixClientPeg.get().getHomeserverUrl(),
|
||||||
props.oobData.avatarUrl,
|
props.oobData.avatarUrl,
|
||||||
props.width, props.height, props.resizeMethod
|
Math.floor(props.width * window.devicePixelRatio),
|
||||||
|
Math.floor(props.height * window.devicePixelRatio),
|
||||||
|
props.resizeMethod
|
||||||
), // highest priority
|
), // highest priority
|
||||||
this.getRoomAvatarUrl(props),
|
this.getRoomAvatarUrl(props),
|
||||||
this.getOneToOneAvatar(props),
|
this.getOneToOneAvatar(props),
|
||||||
|
@ -74,7 +76,9 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
return props.room.getAvatarUrl(
|
return props.room.getAvatarUrl(
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
MatrixClientPeg.get().getHomeserverUrl(),
|
||||||
props.width, props.height, props.resizeMethod,
|
Math.floor(props.width * window.devicePixelRatio),
|
||||||
|
Math.floor(props.height * window.devicePixelRatio),
|
||||||
|
props.resizeMethod,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -103,13 +107,17 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
return theOtherGuy.getAvatarUrl(
|
return theOtherGuy.getAvatarUrl(
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
MatrixClientPeg.get().getHomeserverUrl(),
|
||||||
props.width, props.height, props.resizeMethod,
|
Math.floor(props.width * window.devicePixelRatio),
|
||||||
|
Math.floor(props.height * window.devicePixelRatio),
|
||||||
|
props.resizeMethod,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
} else if (userIds.length == 1) {
|
} else if (userIds.length == 1) {
|
||||||
return mlist[userIds[0]].getAvatarUrl(
|
return mlist[userIds[0]].getAvatarUrl(
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
MatrixClientPeg.get().getHomeserverUrl(),
|
||||||
props.width, props.height, props.resizeMethod,
|
Math.floor(props.width * window.devicePixelRatio),
|
||||||
|
Math.floor(props.height * window.devicePixelRatio),
|
||||||
|
props.resizeMethod,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -47,16 +47,6 @@ export default React.createClass({
|
||||||
children: React.PropTypes.node,
|
children: React.PropTypes.node,
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
|
||||||
this.priorActiveElement = document.activeElement;
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
|
||||||
if (this.priorActiveElement !== null) {
|
|
||||||
this.priorActiveElement.focus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_onKeyDown: function(e) {
|
_onKeyDown: function(e) {
|
||||||
if (e.keyCode === KeyCode.ESCAPE) {
|
if (e.keyCode === KeyCode.ESCAPE) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
|
@ -47,12 +47,6 @@ export default React.createClass({
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
|
||||||
if (this.props.focus) {
|
|
||||||
this.refs.button.focus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
const cancelButton = this.props.hasCancelButton ? (
|
const cancelButton = this.props.hasCancelButton ? (
|
||||||
|
@ -69,7 +63,7 @@ export default React.createClass({
|
||||||
{this.props.description}
|
{this.props.description}
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<button ref="button" className="mx_Dialog_primary" onClick={this.onOk}>
|
<button className="mx_Dialog_primary" onClick={this.onOk} autoFocus={this.props.focus}>
|
||||||
{this.props.button}
|
{this.props.button}
|
||||||
</button>
|
</button>
|
||||||
{this.props.extraButtons}
|
{this.props.extraButtons}
|
||||||
|
|
|
@ -149,7 +149,7 @@ export default React.createClass({
|
||||||
>
|
>
|
||||||
<GeminiScrollbar autoshow={false} className="mx_Dialog_content">
|
<GeminiScrollbar autoshow={false} className="mx_Dialog_content">
|
||||||
<h4>
|
<h4>
|
||||||
This room contains devices that you haven't seen before.
|
"{this.props.room.name}" contains devices that you haven't seen before.
|
||||||
</h4>
|
</h4>
|
||||||
{ warning }
|
{ warning }
|
||||||
Unknown devices:
|
Unknown devices:
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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)}
|
onClick={this.onClick.bind(this, i)}
|
||||||
onMouseEnter={this.onMouseEnter.bind(this, i)}
|
onMouseEnter={this.onMouseEnter.bind(this, i)}
|
||||||
onMouseLeave={this.onMouseLeave}
|
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; }}
|
ref={(ref) => { this.addressListElement = ref; }}
|
||||||
>
|
>
|
||||||
<AddressTile address={this.props.addressList[i]} justified={true} networkName="vector" networkUrl="img/search-icon-vector.svg" />
|
<AddressTile address={this.props.addressList[i]} justified={true} networkName="vector" networkUrl="img/search-icon-vector.svg" />
|
||||||
|
|
|
@ -114,8 +114,11 @@ export default class Dropdown extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (!nextProps.children || nextProps.children.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this._reindexChildren(nextProps.children);
|
this._reindexChildren(nextProps.children);
|
||||||
const firstChild = React.Children.toArray(nextProps.children)[0];
|
const firstChild = nextProps.children[0];
|
||||||
this.setState({
|
this.setState({
|
||||||
highlightedOption: firstChild ? firstChild.key : null,
|
highlightedOption: firstChild ? firstChild.key : null,
|
||||||
});
|
});
|
||||||
|
@ -149,11 +152,13 @@ export default class Dropdown extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onInputClick(ev) {
|
_onInputClick(ev) {
|
||||||
|
if (!this.state.expanded) {
|
||||||
this.setState({
|
this.setState({
|
||||||
expanded: !this.state.expanded,
|
expanded: true,
|
||||||
});
|
});
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_onMenuOptionClick(dropdownKey) {
|
_onMenuOptionClick(dropdownKey) {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -249,7 +254,7 @@ export default class Dropdown extends React.Component {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
if (options.length === 0) {
|
if (options.length === 0) {
|
||||||
return [<div className="mx_Dropdown_option">
|
return [<div key="0" className="mx_Dropdown_option">
|
||||||
No results
|
No results
|
||||||
</div>];
|
</div>];
|
||||||
}
|
}
|
||||||
|
|
|
@ -221,6 +221,8 @@ module.exports = React.createClass({
|
||||||
"banned": beConjugated + " banned",
|
"banned": beConjugated + " banned",
|
||||||
"unbanned": beConjugated + " unbanned",
|
"unbanned": beConjugated + " unbanned",
|
||||||
"kicked": beConjugated + " kicked",
|
"kicked": beConjugated + " kicked",
|
||||||
|
"changed_name": "changed name",
|
||||||
|
"changed_avatar": "changed avatar",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (Object.keys(map).includes(t)) {
|
if (Object.keys(map).includes(t)) {
|
||||||
|
@ -267,7 +269,7 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<span className="mx_MemberEventListSummary_avatars">
|
<span className="mx_MemberEventListSummary_avatars" onClick={ this._toggleSummary }>
|
||||||
{avatars}
|
{avatars}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
@ -289,7 +291,24 @@ module.exports = React.createClass({
|
||||||
switch (e.mxEvent.getContent().membership) {
|
switch (e.mxEvent.getContent().membership) {
|
||||||
case 'invite': return 'invited';
|
case 'invite': return 'invited';
|
||||||
case 'ban': return 'banned';
|
case 'ban': return 'banned';
|
||||||
case 'join': return 'joined';
|
case 'join':
|
||||||
|
if (e.mxEvent.getPrevContent().membership === 'join') {
|
||||||
|
if (e.mxEvent.getContent().displayname !==
|
||||||
|
e.mxEvent.getPrevContent().displayname)
|
||||||
|
{
|
||||||
|
return 'changed_name';
|
||||||
|
}
|
||||||
|
else if (e.mxEvent.getContent().avatar_url !==
|
||||||
|
e.mxEvent.getPrevContent().avatar_url)
|
||||||
|
{
|
||||||
|
return 'changed_avatar';
|
||||||
|
}
|
||||||
|
// console.log("MELS ignoring duplicate membership join event");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 'joined';
|
||||||
|
}
|
||||||
case 'leave':
|
case 'leave':
|
||||||
if (e.mxEvent.getSender() === e.mxEvent.getStateKey()) {
|
if (e.mxEvent.getSender() === e.mxEvent.getStateKey()) {
|
||||||
switch (e.mxEvent.getPrevContent().membership) {
|
switch (e.mxEvent.getPrevContent().membership) {
|
||||||
|
@ -350,6 +369,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const eventsToRender = this.props.events;
|
const eventsToRender = this.props.events;
|
||||||
|
const eventIds = eventsToRender.map(e => e.getId()).join(',');
|
||||||
const fewEvents = eventsToRender.length < this.props.threshold;
|
const fewEvents = eventsToRender.length < this.props.threshold;
|
||||||
const expanded = this.state.expanded || fewEvents;
|
const expanded = this.state.expanded || fewEvents;
|
||||||
|
|
||||||
|
@ -360,7 +380,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
if (fewEvents) {
|
if (fewEvents) {
|
||||||
return (
|
return (
|
||||||
<div className="mx_MemberEventListSummary">
|
<div className="mx_MemberEventListSummary" data-scroll-tokens={eventIds}>
|
||||||
{expandedEvents}
|
{expandedEvents}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -418,7 +438,7 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_MemberEventListSummary">
|
<div className="mx_MemberEventListSummary" data-scroll-tokens={eventIds}>
|
||||||
{toggleButton}
|
{toggleButton}
|
||||||
{summaryContainer}
|
{summaryContainer}
|
||||||
{expanded ? <div className="mx_MemberEventListSummary_line"> </div> : null}
|
{expanded ? <div className="mx_MemberEventListSummary_line"> </div> : null}
|
||||||
|
|
|
@ -19,7 +19,6 @@ import React from 'react';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
|
|
||||||
import { COUNTRIES } from '../../../phonenumber';
|
import { COUNTRIES } from '../../../phonenumber';
|
||||||
import { charactersToImageNode } from '../../../HtmlUtils';
|
|
||||||
|
|
||||||
const COUNTRIES_BY_ISO2 = new Object(null);
|
const COUNTRIES_BY_ISO2 = new Object(null);
|
||||||
for (const c of COUNTRIES) {
|
for (const c of COUNTRIES) {
|
||||||
|
@ -27,9 +26,14 @@ for (const c of COUNTRIES) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function countryMatchesSearchQuery(query, country) {
|
function countryMatchesSearchQuery(query, country) {
|
||||||
|
// Remove '+' if present (when searching for a prefix)
|
||||||
|
if (query[0] === '+') {
|
||||||
|
query = query.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
if (country.name.toUpperCase().indexOf(query.toUpperCase()) == 0) return true;
|
if (country.name.toUpperCase().indexOf(query.toUpperCase()) == 0) return true;
|
||||||
if (country.iso2 == query.toUpperCase()) return true;
|
if (country.iso2 == query.toUpperCase()) return true;
|
||||||
if (country.prefix == query) return true;
|
if (country.prefix.indexOf(query) !== -1) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,10 +41,12 @@ export default class CountryDropdown extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this._onSearchChange = this._onSearchChange.bind(this);
|
this._onSearchChange = this._onSearchChange.bind(this);
|
||||||
|
this._onOptionChange = this._onOptionChange.bind(this);
|
||||||
|
this._getShortOption = this._getShortOption.bind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
|
@ -48,7 +54,7 @@ export default class CountryDropdown extends React.Component {
|
||||||
// If no value is given, we start with the first
|
// If no value is given, we start with the first
|
||||||
// country selected, but our parent component
|
// country selected, but our parent component
|
||||||
// doesn't know this, therefore we do this.
|
// doesn't know this, therefore we do this.
|
||||||
this.props.onOptionChange(COUNTRIES[0].iso2);
|
this.props.onOptionChange(COUNTRIES[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,14 +64,26 @@ export default class CountryDropdown extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onOptionChange(iso2) {
|
||||||
|
this.props.onOptionChange(COUNTRIES_BY_ISO2[iso2]);
|
||||||
|
}
|
||||||
|
|
||||||
_flagImgForIso2(iso2) {
|
_flagImgForIso2(iso2) {
|
||||||
// Unicode Regional Indicator Symbol letter 'A'
|
return <img src={`flags/${iso2}.png`}/>;
|
||||||
const RIS_A = 0x1F1E6;
|
}
|
||||||
const ASCII_A = 65;
|
|
||||||
return charactersToImageNode(iso2, true,
|
_getShortOption(iso2) {
|
||||||
RIS_A + (iso2.charCodeAt(0) - ASCII_A),
|
if (!this.props.isSmall) {
|
||||||
RIS_A + (iso2.charCodeAt(1) - ASCII_A),
|
return undefined;
|
||||||
);
|
}
|
||||||
|
let countryPrefix;
|
||||||
|
if (this.props.showPrefix) {
|
||||||
|
countryPrefix = '+' + COUNTRIES_BY_ISO2[iso2].prefix;
|
||||||
|
}
|
||||||
|
return <span>
|
||||||
|
{ this._flagImgForIso2(iso2) }
|
||||||
|
{ countryPrefix }
|
||||||
|
</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -94,7 +112,7 @@ export default class CountryDropdown extends React.Component {
|
||||||
const options = displayedCountries.map((country) => {
|
const options = displayedCountries.map((country) => {
|
||||||
return <div key={country.iso2}>
|
return <div key={country.iso2}>
|
||||||
{this._flagImgForIso2(country.iso2)}
|
{this._flagImgForIso2(country.iso2)}
|
||||||
{country.name}
|
{country.name} <span>(+{country.prefix})</span>
|
||||||
</div>;
|
</div>;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -102,18 +120,21 @@ export default class CountryDropdown extends React.Component {
|
||||||
// values between mounting and the initial value propgating
|
// values between mounting and the initial value propgating
|
||||||
const value = this.props.value || COUNTRIES[0].iso2;
|
const value = this.props.value || COUNTRIES[0].iso2;
|
||||||
|
|
||||||
return <Dropdown className={this.props.className}
|
return <Dropdown className={this.props.className + " left_aligned"}
|
||||||
onOptionChange={this.props.onOptionChange} onSearchChange={this._onSearchChange}
|
onOptionChange={this._onOptionChange} onSearchChange={this._onSearchChange}
|
||||||
menuWidth={298} getShortOption={this._flagImgForIso2}
|
menuWidth={298} getShortOption={this._getShortOption}
|
||||||
value={value} searchEnabled={true}
|
value={value} searchEnabled={true}
|
||||||
>
|
>
|
||||||
{options}
|
{options}
|
||||||
</Dropdown>
|
</Dropdown>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CountryDropdown.propTypes = {
|
CountryDropdown.propTypes = {
|
||||||
className: React.PropTypes.string,
|
className: React.PropTypes.string,
|
||||||
|
isSmall: React.PropTypes.bool,
|
||||||
|
// if isSmall, show +44 in the selected value
|
||||||
|
showPrefix: React.PropTypes.bool,
|
||||||
onOptionChange: React.PropTypes.func.isRequired,
|
onOptionChange: React.PropTypes.func.isRequired,
|
||||||
value: React.PropTypes.string,
|
value: React.PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
|
@ -90,8 +90,11 @@ class PasswordLogin extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
onPhoneCountryChanged(country) {
|
onPhoneCountryChanged(country) {
|
||||||
this.setState({phoneCountry: country});
|
this.setState({
|
||||||
this.props.onPhoneCountryChanged(country);
|
phoneCountry: country.iso2,
|
||||||
|
phonePrefix: country.prefix,
|
||||||
|
});
|
||||||
|
this.props.onPhoneCountryChanged(country.iso2);
|
||||||
}
|
}
|
||||||
|
|
||||||
onPhoneNumberChanged(ev) {
|
onPhoneNumberChanged(ev) {
|
||||||
|
@ -121,16 +124,17 @@ class PasswordLogin extends React.Component {
|
||||||
const mxidInputClasses = classNames({
|
const mxidInputClasses = classNames({
|
||||||
"mx_Login_field": true,
|
"mx_Login_field": true,
|
||||||
"mx_Login_username": true,
|
"mx_Login_username": true,
|
||||||
|
"mx_Login_field_has_prefix": true,
|
||||||
"mx_Login_field_has_suffix": Boolean(this.props.hsDomain),
|
"mx_Login_field_has_suffix": Boolean(this.props.hsDomain),
|
||||||
});
|
});
|
||||||
let suffix = null;
|
let suffix = null;
|
||||||
if (this.props.hsDomain) {
|
if (this.props.hsDomain) {
|
||||||
suffix = <div className="mx_Login_username_suffix">
|
suffix = <div className="mx_Login_field_suffix">
|
||||||
:{this.props.hsDomain}
|
:{this.props.hsDomain}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
return <div className="mx_Login_username_group">
|
return <div className="mx_Login_field_group">
|
||||||
<div className="mx_Login_username_prefix">@</div>
|
<div className="mx_Login_field_prefix">@</div>
|
||||||
<input
|
<input
|
||||||
className={mxidInputClasses}
|
className={mxidInputClasses}
|
||||||
key="username_input"
|
key="username_input"
|
||||||
|
@ -147,13 +151,15 @@ class PasswordLogin extends React.Component {
|
||||||
const CountryDropdown = sdk.getComponent('views.login.CountryDropdown');
|
const CountryDropdown = sdk.getComponent('views.login.CountryDropdown');
|
||||||
return <div className="mx_Login_phoneSection">
|
return <div className="mx_Login_phoneSection">
|
||||||
<CountryDropdown
|
<CountryDropdown
|
||||||
className="mx_Login_phoneCountry"
|
className="mx_Login_phoneCountry mx_Login_field_prefix"
|
||||||
ref="phone_country"
|
ref="phone_country"
|
||||||
onOptionChange={this.onPhoneCountryChanged}
|
onOptionChange={this.onPhoneCountryChanged}
|
||||||
value={this.state.phoneCountry}
|
value={this.state.phoneCountry}
|
||||||
|
isSmall={true}
|
||||||
|
showPrefix={true}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
className="mx_Login_phoneNumberField mx_Login_field"
|
className="mx_Login_phoneNumberField mx_Login_field mx_Login_field_has_prefix"
|
||||||
ref="phoneNumber"
|
ref="phoneNumber"
|
||||||
key="phone_input"
|
key="phone_input"
|
||||||
type="text"
|
type="text"
|
||||||
|
|
|
@ -270,7 +270,8 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
_onPhoneCountryChange(newVal) {
|
_onPhoneCountryChange(newVal) {
|
||||||
this.setState({
|
this.setState({
|
||||||
phoneCountry: newVal,
|
phoneCountry: newVal.iso2,
|
||||||
|
phonePrefix: newVal.prefix,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -313,14 +314,19 @@ module.exports = React.createClass({
|
||||||
const phoneSection = (
|
const phoneSection = (
|
||||||
<div className="mx_Login_phoneSection">
|
<div className="mx_Login_phoneSection">
|
||||||
<CountryDropdown ref="phone_country" onOptionChange={this._onPhoneCountryChange}
|
<CountryDropdown ref="phone_country" onOptionChange={this._onPhoneCountryChange}
|
||||||
className="mx_Login_phoneCountry"
|
className="mx_Login_phoneCountry mx_Login_field_prefix"
|
||||||
value={this.state.phoneCountry}
|
value={this.state.phoneCountry}
|
||||||
|
isSmall={true}
|
||||||
|
showPrefix={true}
|
||||||
/>
|
/>
|
||||||
<input type="text" ref="phoneNumber"
|
<input type="text" ref="phoneNumber"
|
||||||
placeholder="Mobile phone number (optional)"
|
placeholder="Mobile phone number (optional)"
|
||||||
defaultValue={this.props.defaultPhoneNumber}
|
defaultValue={this.props.defaultPhoneNumber}
|
||||||
className={this._classForField(
|
className={this._classForField(
|
||||||
FIELD_PHONE_NUMBER, 'mx_Login_phoneNumberField', 'mx_Login_field'
|
FIELD_PHONE_NUMBER,
|
||||||
|
'mx_Login_phoneNumberField',
|
||||||
|
'mx_Login_field',
|
||||||
|
'mx_Login_field_has_prefix'
|
||||||
)}
|
)}
|
||||||
onBlur={function() {self.validateField(FIELD_PHONE_NUMBER);}}
|
onBlur={function() {self.validateField(FIELD_PHONE_NUMBER);}}
|
||||||
value={self.state.phoneNumber}
|
value={self.state.phoneNumber}
|
||||||
|
|
|
@ -295,16 +295,6 @@ module.exports = WithMatrixClient(React.createClass({
|
||||||
const receiptOffset = 15;
|
const receiptOffset = 15;
|
||||||
let left = 0;
|
let left = 0;
|
||||||
|
|
||||||
// It's possible that the receipt was sent several days AFTER the event.
|
|
||||||
// If it is, we want to display the complete date along with the HH:MM:SS,
|
|
||||||
// rather than just HH:MM:SS.
|
|
||||||
let dayAfterEvent = new Date(this.props.mxEvent.getTs());
|
|
||||||
dayAfterEvent.setDate(dayAfterEvent.getDate() + 1);
|
|
||||||
dayAfterEvent.setHours(0);
|
|
||||||
dayAfterEvent.setMinutes(0);
|
|
||||||
dayAfterEvent.setSeconds(0);
|
|
||||||
let dayAfterEventTime = dayAfterEvent.getTime();
|
|
||||||
|
|
||||||
var receipts = this.props.readReceipts || [];
|
var receipts = this.props.readReceipts || [];
|
||||||
for (var i = 0; i < receipts.length; ++i) {
|
for (var i = 0; i < receipts.length; ++i) {
|
||||||
var receipt = receipts[i];
|
var receipt = receipts[i];
|
||||||
|
@ -340,7 +330,6 @@ module.exports = WithMatrixClient(React.createClass({
|
||||||
suppressAnimation={this._suppressReadReceiptAnimation}
|
suppressAnimation={this._suppressReadReceiptAnimation}
|
||||||
onClick={this.toggleAllReadAvatars}
|
onClick={this.toggleAllReadAvatars}
|
||||||
timestamp={receipt.ts}
|
timestamp={receipt.ts}
|
||||||
showFullTimestamp={receipt.ts >= dayAfterEventTime}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -401,8 +390,7 @@ module.exports = WithMatrixClient(React.createClass({
|
||||||
var msgtype = content.msgtype;
|
var msgtype = content.msgtype;
|
||||||
var eventType = this.props.mxEvent.getType();
|
var eventType = this.props.mxEvent.getType();
|
||||||
|
|
||||||
// Info messages are basically information about commands processed on a
|
// Info messages are basically information about commands processed on a room
|
||||||
// room, or emote messages
|
|
||||||
var isInfoMessage = (eventType !== 'm.room.message');
|
var isInfoMessage = (eventType !== 'm.room.message');
|
||||||
|
|
||||||
var EventTileType = sdk.getComponent(eventTileTypes[eventType]);
|
var EventTileType = sdk.getComponent(eventTileTypes[eventType]);
|
||||||
|
@ -430,7 +418,8 @@ module.exports = WithMatrixClient(React.createClass({
|
||||||
menu: this.state.menu,
|
menu: this.state.menu,
|
||||||
mx_EventTile_verified: this.state.verified == true,
|
mx_EventTile_verified: this.state.verified == true,
|
||||||
mx_EventTile_unverified: this.state.verified == false,
|
mx_EventTile_unverified: this.state.verified == false,
|
||||||
mx_EventTile_bad: this.props.mxEvent.getContent().msgtype === 'm.bad.encrypted',
|
mx_EventTile_bad: msgtype === 'm.bad.encrypted',
|
||||||
|
mx_EventTile_emote: msgtype === 'm.emote',
|
||||||
mx_EventTile_redacted: isRedacted,
|
mx_EventTile_redacted: isRedacted,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -492,22 +481,22 @@ module.exports = WithMatrixClient(React.createClass({
|
||||||
var e2e;
|
var e2e;
|
||||||
// cosmetic padlocks:
|
// cosmetic padlocks:
|
||||||
if ((e2eEnabled && this.props.eventSendStatus) || this.props.mxEvent.getType() === 'm.room.encryption') {
|
if ((e2eEnabled && this.props.eventSendStatus) || this.props.mxEvent.getType() === 'm.room.encryption') {
|
||||||
e2e = <img style={{ cursor: 'initial', marginLeft: '-1px' }} className="mx_EventTile_e2eIcon" src="img/e2e-verified.svg" width="10" height="12" />;
|
e2e = <img style={{ cursor: 'initial', marginLeft: '-1px' }} className="mx_EventTile_e2eIcon" alt="Encrypted by verified device" src="img/e2e-verified.svg" width="10" height="12" />;
|
||||||
}
|
}
|
||||||
// real padlocks
|
// real padlocks
|
||||||
else if (this.props.mxEvent.isEncrypted() || (e2eEnabled && this.props.eventSendStatus)) {
|
else if (this.props.mxEvent.isEncrypted() || (e2eEnabled && this.props.eventSendStatus)) {
|
||||||
if (this.props.mxEvent.getContent().msgtype === 'm.bad.encrypted') {
|
if (this.props.mxEvent.getContent().msgtype === 'm.bad.encrypted') {
|
||||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" src="img/e2e-blocked.svg" width="12" height="12" style={{ marginLeft: "-1px" }} />;
|
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt="Undecryptable" src="img/e2e-blocked.svg" width="12" height="12" style={{ marginLeft: "-1px" }} />;
|
||||||
}
|
}
|
||||||
else if (this.state.verified == true || (e2eEnabled && this.props.eventSendStatus)) {
|
else if (this.state.verified == true || (e2eEnabled && this.props.eventSendStatus)) {
|
||||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" src="img/e2e-verified.svg" width="10" height="12"/>;
|
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt="Encrypted by verified device" src="img/e2e-verified.svg" width="10" height="12"/>;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" src="img/e2e-warning.svg" width="15" height="12" style={{ marginLeft: "-2px" }}/>;
|
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt="Encrypted by unverified device" src="img/e2e-warning.svg" width="15" height="12" style={{ marginLeft: "-2px" }}/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (e2eEnabled) {
|
else if (e2eEnabled) {
|
||||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" src="img/e2e-unencrypted.svg" width="12" height="12"/>;
|
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt="Unencrypted message" src="img/e2e-unencrypted.svg" width="12" height="12"/>;
|
||||||
}
|
}
|
||||||
const timestamp = this.props.mxEvent.getTs() ?
|
const timestamp = this.props.mxEvent.getTs() ?
|
||||||
<MessageTimestamp ts={this.props.mxEvent.getTs()} /> : null;
|
<MessageTimestamp ts={this.props.mxEvent.getTs()} /> : null;
|
||||||
|
|
|
@ -100,7 +100,9 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var p = this.state.preview;
|
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?
|
// FIXME: do we want to factor out all image displaying between this and MImageBody - especially for lightboxing?
|
||||||
var image = p["og:image"];
|
var image = p["og:image"];
|
||||||
|
|
|
@ -717,8 +717,16 @@ module.exports = WithMatrixClient(React.createClass({
|
||||||
|
|
||||||
const memberName = this.props.member.name;
|
const memberName = this.props.member.name;
|
||||||
|
|
||||||
|
if (this.props.member.user) {
|
||||||
|
var presenceState = this.props.member.user.presence;
|
||||||
|
var presenceLastActiveAgo = this.props.member.user.lastActiveAgo;
|
||||||
|
var presenceLastTs = this.props.member.user.lastPresenceTs;
|
||||||
|
var presenceCurrentlyActive = this.props.member.user.currentlyActive;
|
||||||
|
}
|
||||||
|
|
||||||
var MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
var MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
||||||
var PowerSelector = sdk.getComponent('elements.PowerSelector');
|
var PowerSelector = sdk.getComponent('elements.PowerSelector');
|
||||||
|
var PresenceLabel = sdk.getComponent('rooms.PresenceLabel');
|
||||||
const EmojiText = sdk.getComponent('elements.EmojiText');
|
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||||
return (
|
return (
|
||||||
<div className="mx_MemberInfo">
|
<div className="mx_MemberInfo">
|
||||||
|
@ -736,6 +744,11 @@ module.exports = WithMatrixClient(React.createClass({
|
||||||
<div className="mx_MemberInfo_profileField">
|
<div className="mx_MemberInfo_profileField">
|
||||||
Level: <b><PowerSelector controlled={true} value={ parseInt(this.props.member.powerLevel) } disabled={ !this.state.can.modifyLevel } onChange={ this.onPowerChange }/></b>
|
Level: <b><PowerSelector controlled={true} value={ parseInt(this.props.member.powerLevel) } disabled={ !this.state.can.modifyLevel } onChange={ this.onPowerChange }/></b>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mx_MemberInfo_profileField">
|
||||||
|
<PresenceLabel activeAgo={ presenceLastActiveAgo }
|
||||||
|
currentlyActive={ presenceCurrentlyActive }
|
||||||
|
presenceState={ presenceState } />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ adminTools }
|
{ adminTools }
|
||||||
|
|
|
@ -33,6 +33,7 @@ export default class MessageComposer extends React.Component {
|
||||||
this.onHangupClick = this.onHangupClick.bind(this);
|
this.onHangupClick = this.onHangupClick.bind(this);
|
||||||
this.onUploadClick = this.onUploadClick.bind(this);
|
this.onUploadClick = this.onUploadClick.bind(this);
|
||||||
this.onUploadFileSelected = this.onUploadFileSelected.bind(this);
|
this.onUploadFileSelected = this.onUploadFileSelected.bind(this);
|
||||||
|
this.uploadFiles = this.uploadFiles.bind(this);
|
||||||
this.onVoiceCallClick = this.onVoiceCallClick.bind(this);
|
this.onVoiceCallClick = this.onVoiceCallClick.bind(this);
|
||||||
this.onInputContentChanged = this.onInputContentChanged.bind(this);
|
this.onInputContentChanged = this.onInputContentChanged.bind(this);
|
||||||
this.onUpArrow = this.onUpArrow.bind(this);
|
this.onUpArrow = this.onUpArrow.bind(this);
|
||||||
|
@ -43,6 +44,7 @@ export default class MessageComposer extends React.Component {
|
||||||
this.onToggleMarkdownClicked = this.onToggleMarkdownClicked.bind(this);
|
this.onToggleMarkdownClicked = this.onToggleMarkdownClicked.bind(this);
|
||||||
this.onInputStateChanged = this.onInputStateChanged.bind(this);
|
this.onInputStateChanged = this.onInputStateChanged.bind(this);
|
||||||
this.onEvent = this.onEvent.bind(this);
|
this.onEvent = this.onEvent.bind(this);
|
||||||
|
this.onPageUnload = this.onPageUnload.bind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
autocompleteQuery: '',
|
autocompleteQuery: '',
|
||||||
|
@ -50,7 +52,7 @@ export default class MessageComposer extends React.Component {
|
||||||
inputState: {
|
inputState: {
|
||||||
style: [],
|
style: [],
|
||||||
blockType: null,
|
blockType: null,
|
||||||
isRichtextEnabled: UserSettingsStore.getSyncedSetting('MessageComposerInput.isRichTextEnabled', true),
|
isRichtextEnabled: UserSettingsStore.getSyncedSetting('MessageComposerInput.isRichTextEnabled', false),
|
||||||
wordCount: 0,
|
wordCount: 0,
|
||||||
},
|
},
|
||||||
showFormatting: UserSettingsStore.getSyncedSetting('MessageComposer.showFormatting', false),
|
showFormatting: UserSettingsStore.getSyncedSetting('MessageComposer.showFormatting', false),
|
||||||
|
@ -64,12 +66,21 @@ export default class MessageComposer extends React.Component {
|
||||||
// marked as encrypted.
|
// marked as encrypted.
|
||||||
// XXX: fragile as all hell - fixme somehow, perhaps with a dedicated Room.encryption event or something.
|
// XXX: fragile as all hell - fixme somehow, perhaps with a dedicated Room.encryption event or something.
|
||||||
MatrixClientPeg.get().on("event", this.onEvent);
|
MatrixClientPeg.get().on("event", this.onEvent);
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', this.onPageUnload);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
if (MatrixClientPeg.get()) {
|
if (MatrixClientPeg.get()) {
|
||||||
MatrixClientPeg.get().removeListener("event", this.onEvent);
|
MatrixClientPeg.get().removeListener("event", this.onEvent);
|
||||||
}
|
}
|
||||||
|
window.removeEventListener('beforeunload', this.onPageUnload);
|
||||||
|
}
|
||||||
|
|
||||||
|
onPageUnload(event) {
|
||||||
|
if (this.messageComposerInput) {
|
||||||
|
this.messageComposerInput.sentHistory.saveLastTextEntry();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onEvent(event) {
|
onEvent(event) {
|
||||||
|
@ -91,10 +102,11 @@ export default class MessageComposer extends React.Component {
|
||||||
this.refs.uploadInput.click();
|
this.refs.uploadInput.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
onUploadFileSelected(files, isPasted) {
|
onUploadFileSelected(files) {
|
||||||
if (!isPasted)
|
this.uploadFiles(files.target.files);
|
||||||
files = files.target.files;
|
}
|
||||||
|
|
||||||
|
uploadFiles(files) {
|
||||||
let QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
let QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
let TintableSvg = sdk.getComponent("elements.TintableSvg");
|
let TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
|
|
||||||
|
@ -300,7 +312,7 @@ export default class MessageComposer extends React.Component {
|
||||||
tryComplete={this._tryComplete}
|
tryComplete={this._tryComplete}
|
||||||
onUpArrow={this.onUpArrow}
|
onUpArrow={this.onUpArrow}
|
||||||
onDownArrow={this.onDownArrow}
|
onDownArrow={this.onDownArrow}
|
||||||
onUploadFileSelected={this.onUploadFileSelected}
|
onFilesPasted={this.uploadFiles}
|
||||||
tabComplete={this.props.tabComplete} // used for old messagecomposerinput/tabcomplete
|
tabComplete={this.props.tabComplete} // used for old messagecomposerinput/tabcomplete
|
||||||
onContentChanged={this.onInputContentChanged}
|
onContentChanged={this.onInputContentChanged}
|
||||||
onInputStateChanged={this.onInputStateChanged} />,
|
onInputStateChanged={this.onInputStateChanged} />,
|
||||||
|
|
|
@ -84,7 +84,6 @@ export default class MessageComposerInput extends React.Component {
|
||||||
this.onAction = this.onAction.bind(this);
|
this.onAction = this.onAction.bind(this);
|
||||||
this.handleReturn = this.handleReturn.bind(this);
|
this.handleReturn = this.handleReturn.bind(this);
|
||||||
this.handleKeyCommand = this.handleKeyCommand.bind(this);
|
this.handleKeyCommand = this.handleKeyCommand.bind(this);
|
||||||
this.handlePastedFiles = this.handlePastedFiles.bind(this);
|
|
||||||
this.onEditorContentChanged = this.onEditorContentChanged.bind(this);
|
this.onEditorContentChanged = this.onEditorContentChanged.bind(this);
|
||||||
this.setEditorState = this.setEditorState.bind(this);
|
this.setEditorState = this.setEditorState.bind(this);
|
||||||
this.onUpArrow = this.onUpArrow.bind(this);
|
this.onUpArrow = this.onUpArrow.bind(this);
|
||||||
|
@ -94,7 +93,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
this.setDisplayedCompletion = this.setDisplayedCompletion.bind(this);
|
this.setDisplayedCompletion = this.setDisplayedCompletion.bind(this);
|
||||||
this.onMarkdownToggleClicked = this.onMarkdownToggleClicked.bind(this);
|
this.onMarkdownToggleClicked = this.onMarkdownToggleClicked.bind(this);
|
||||||
|
|
||||||
const isRichtextEnabled = UserSettingsStore.getSyncedSetting('MessageComposerInput.isRichTextEnabled', true);
|
const isRichtextEnabled = UserSettingsStore.getSyncedSetting('MessageComposerInput.isRichTextEnabled', false);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
// whether we're in rich text or markdown mode
|
// whether we're in rich text or markdown mode
|
||||||
|
@ -477,10 +476,6 @@ export default class MessageComposerInput extends React.Component {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePastedFiles(files) {
|
|
||||||
this.props.onUploadFileSelected(files, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleReturn(ev) {
|
handleReturn(ev) {
|
||||||
if (ev.shiftKey) {
|
if (ev.shiftKey) {
|
||||||
this.onEditorContentChanged(RichUtils.insertSoftNewline(this.state.editorState));
|
this.onEditorContentChanged(RichUtils.insertSoftNewline(this.state.editorState));
|
||||||
|
@ -542,9 +537,9 @@ export default class MessageComposerInput extends React.Component {
|
||||||
let sendTextFn = this.client.sendTextMessage;
|
let sendTextFn = this.client.sendTextMessage;
|
||||||
|
|
||||||
if (contentText.startsWith('/me')) {
|
if (contentText.startsWith('/me')) {
|
||||||
contentText = contentText.replace('/me ', '');
|
contentText = contentText.substring(4);
|
||||||
// bit of a hack, but the alternative would be quite complicated
|
// bit of a hack, but the alternative would be quite complicated
|
||||||
if (contentHTML) contentHTML = contentHTML.replace('/me ', '');
|
if (contentHTML) contentHTML = contentHTML.replace(/\/me ?/, '');
|
||||||
sendHtmlFn = this.client.sendHtmlEmote;
|
sendHtmlFn = this.client.sendHtmlEmote;
|
||||||
sendTextFn = this.client.sendEmoteMessage;
|
sendTextFn = this.client.sendEmoteMessage;
|
||||||
}
|
}
|
||||||
|
@ -734,7 +729,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
keyBindingFn={MessageComposerInput.getKeyBinding}
|
keyBindingFn={MessageComposerInput.getKeyBinding}
|
||||||
handleKeyCommand={this.handleKeyCommand}
|
handleKeyCommand={this.handleKeyCommand}
|
||||||
handleReturn={this.handleReturn}
|
handleReturn={this.handleReturn}
|
||||||
handlePastedFiles={this.handlePastedFiles}
|
handlePastedFiles={this.props.onFilesPasted}
|
||||||
stripPastedStyles={!this.state.isRichtextEnabled}
|
stripPastedStyles={!this.state.isRichtextEnabled}
|
||||||
onTab={this.onTab}
|
onTab={this.onTab}
|
||||||
onUpArrow={this.onUpArrow}
|
onUpArrow={this.onUpArrow}
|
||||||
|
@ -764,7 +759,7 @@ MessageComposerInput.propTypes = {
|
||||||
|
|
||||||
onDownArrow: React.PropTypes.func,
|
onDownArrow: React.PropTypes.func,
|
||||||
|
|
||||||
onUploadFileSelected: React.PropTypes.func,
|
onFilesPasted: React.PropTypes.func,
|
||||||
|
|
||||||
// attempts to confirm currently selected completion, returns whether actually confirmed
|
// attempts to confirm currently selected completion, returns whether actually confirmed
|
||||||
tryComplete: React.PropTypes.func,
|
tryComplete: React.PropTypes.func,
|
||||||
|
|
|
@ -69,6 +69,9 @@ export default React.createClass({
|
||||||
|
|
||||||
// The text to use a placeholder in the input box
|
// The text to use a placeholder in the input box
|
||||||
placeholder: React.PropTypes.string.isRequired,
|
placeholder: React.PropTypes.string.isRequired,
|
||||||
|
|
||||||
|
// callback to handle files pasted into the composer
|
||||||
|
onFilesPasted: React.PropTypes.func,
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
|
@ -439,10 +442,27 @@ export default React.createClass({
|
||||||
this.refs.textarea.focus();
|
this.refs.textarea.focus();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_onPaste: function(ev) {
|
||||||
|
const items = ev.clipboardData.items;
|
||||||
|
const files = [];
|
||||||
|
for (const item of items) {
|
||||||
|
if (item.kind === 'file') {
|
||||||
|
files.push(item.getAsFile());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (files.length && this.props.onFilesPasted) {
|
||||||
|
this.props.onFilesPasted(files);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<div className="mx_MessageComposer_input" onClick={ this.onInputClick }>
|
<div className="mx_MessageComposer_input" onClick={ this.onInputClick }>
|
||||||
<textarea autoFocus ref="textarea" rows="1" onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} placeholder={this.props.placeholder} />
|
<textarea autoFocus ref="textarea" rows="1" onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} placeholder={this.props.placeholder}
|
||||||
|
onPaste={this._onPaste}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@ var sdk = require('../../../index');
|
||||||
var Velociraptor = require('../../../Velociraptor');
|
var Velociraptor = require('../../../Velociraptor');
|
||||||
require('../../../VelocityBounce');
|
require('../../../VelocityBounce');
|
||||||
|
|
||||||
|
import DateUtils from '../../../DateUtils';
|
||||||
|
|
||||||
var bounce = false;
|
var bounce = false;
|
||||||
try {
|
try {
|
||||||
if (global.localStorage) {
|
if (global.localStorage) {
|
||||||
|
@ -63,9 +65,6 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// Timestamp when the receipt was read
|
// Timestamp when the receipt was read
|
||||||
timestamp: React.PropTypes.number,
|
timestamp: React.PropTypes.number,
|
||||||
|
|
||||||
// True to show the full date/time rather than just the time
|
|
||||||
showFullTimestamp: React.PropTypes.bool,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -170,16 +169,8 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
let title;
|
let title;
|
||||||
if (this.props.timestamp) {
|
if (this.props.timestamp) {
|
||||||
const prefix = "Seen by " + this.props.member.userId + " at ";
|
title = "Seen by " + this.props.member.userId + " at " +
|
||||||
let ts = new Date(this.props.timestamp);
|
DateUtils.formatDate(new Date(this.props.timestamp));
|
||||||
if (this.props.showFullTimestamp) {
|
|
||||||
// "15/12/2016, 7:05:45 PM (@alice:matrix.org)"
|
|
||||||
title = prefix + ts.toLocaleString();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// "7:05:45 PM (@alice:matrix.org)"
|
|
||||||
title = prefix + ts.toLocaleTimeString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
|
var classNames = require('classnames');
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||||
var Modal = require("../../../Modal");
|
var Modal = require("../../../Modal");
|
||||||
|
@ -39,6 +40,7 @@ module.exports = React.createClass({
|
||||||
oobData: React.PropTypes.object,
|
oobData: React.PropTypes.object,
|
||||||
editing: React.PropTypes.bool,
|
editing: React.PropTypes.bool,
|
||||||
saving: React.PropTypes.bool,
|
saving: React.PropTypes.bool,
|
||||||
|
inRoom: React.PropTypes.bool,
|
||||||
collapsedRhs: React.PropTypes.bool,
|
collapsedRhs: React.PropTypes.bool,
|
||||||
onSettingsClick: React.PropTypes.func,
|
onSettingsClick: React.PropTypes.func,
|
||||||
onSaveClick: React.PropTypes.func,
|
onSaveClick: React.PropTypes.func,
|
||||||
|
@ -49,7 +51,7 @@ module.exports = React.createClass({
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
editing: false,
|
editing: false,
|
||||||
onSettingsClick: function() {},
|
inRoom: false,
|
||||||
onSaveClick: function() {},
|
onSaveClick: function() {},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -228,10 +230,10 @@ module.exports = React.createClass({
|
||||||
roomName = this.props.room.name;
|
roomName = this.props.room.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const emojiTextClasses = classNames('mx_RoomHeader_nametext', { mx_RoomHeader_settingsHint: settingsHint });
|
||||||
name =
|
name =
|
||||||
<div className="mx_RoomHeader_name" onClick={this.props.onSettingsClick}>
|
<div className="mx_RoomHeader_name" onClick={this.props.onSettingsClick}>
|
||||||
<EmojiText element="div" className={ "mx_RoomHeader_nametext " + (settingsHint ? "mx_RoomHeader_settingsHint" : "") } title={ roomName }>{roomName}</EmojiText>
|
<EmojiText element="div" className={emojiTextClasses} title={roomName}>{ roomName }</EmojiText>
|
||||||
{ searchStatus }
|
{ searchStatus }
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
@ -302,6 +304,14 @@ module.exports = React.createClass({
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let search_button;
|
||||||
|
if (this.props.onSearchClick && this.props.inRoom) {
|
||||||
|
search_button =
|
||||||
|
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onSearchClick} title="Search">
|
||||||
|
<TintableSvg src="img/icons-search.svg" width="35" height="35"/>
|
||||||
|
</AccessibleButton>;
|
||||||
|
}
|
||||||
|
|
||||||
var rightPanel_buttons;
|
var rightPanel_buttons;
|
||||||
if (this.props.collapsedRhs) {
|
if (this.props.collapsedRhs) {
|
||||||
rightPanel_buttons =
|
rightPanel_buttons =
|
||||||
|
@ -316,9 +326,7 @@ module.exports = React.createClass({
|
||||||
<div className="mx_RoomHeader_rightRow">
|
<div className="mx_RoomHeader_rightRow">
|
||||||
{ settings_button }
|
{ settings_button }
|
||||||
{ forget_button }
|
{ forget_button }
|
||||||
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onSearchClick} title="Search">
|
{ search_button }
|
||||||
<TintableSvg src="img/icons-search.svg" width="35" height="35"/>
|
|
||||||
</AccessibleButton>
|
|
||||||
{ rightPanel_buttons }
|
{ rightPanel_buttons }
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,13 +21,13 @@ var GeminiScrollbar = require('react-gemini-scrollbar');
|
||||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||||
var CallHandler = require('../../../CallHandler');
|
var CallHandler = require('../../../CallHandler');
|
||||||
var RoomListSorter = require("../../../RoomListSorter");
|
var RoomListSorter = require("../../../RoomListSorter");
|
||||||
|
var Unread = require('../../../Unread');
|
||||||
var dis = require("../../../dispatcher");
|
var dis = require("../../../dispatcher");
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
var rate_limited_func = require('../../../ratelimitedfunc');
|
var rate_limited_func = require('../../../ratelimitedfunc');
|
||||||
var Rooms = require('../../../Rooms');
|
var Rooms = require('../../../Rooms');
|
||||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||||
var Receipt = require('../../../utils/Receipt');
|
var Receipt = require('../../../utils/Receipt');
|
||||||
var constantTimeDispatcher = require('../../../ConstantTimeDispatcher');
|
|
||||||
|
|
||||||
var HIDE_CONFERENCE_CHANS = true;
|
var HIDE_CONFERENCE_CHANS = true;
|
||||||
|
|
||||||
|
@ -37,19 +37,10 @@ module.exports = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
ConferenceHandler: React.PropTypes.any,
|
ConferenceHandler: React.PropTypes.any,
|
||||||
collapsed: React.PropTypes.bool.isRequired,
|
collapsed: React.PropTypes.bool.isRequired,
|
||||||
selectedRoom: React.PropTypes.string,
|
currentRoom: React.PropTypes.string,
|
||||||
searchFilter: React.PropTypes.string,
|
searchFilter: React.PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
shouldComponentUpdate: function(nextProps, nextState) {
|
|
||||||
if (nextProps.collapsed !== this.props.collapsed) return true;
|
|
||||||
if (nextProps.searchFilter !== this.props.searchFilter) return true;
|
|
||||||
if (nextState.lists !== this.state.lists ||
|
|
||||||
nextState.isLoadingLeftRooms !== this.state.isLoadingLeftRooms ||
|
|
||||||
nextState.incomingCall !== this.state.incomingCall) return true;
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
isLoadingLeftRooms: false,
|
isLoadingLeftRooms: false,
|
||||||
|
@ -59,6 +50,8 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
|
this.mounted = false;
|
||||||
|
|
||||||
var cli = MatrixClientPeg.get();
|
var cli = MatrixClientPeg.get();
|
||||||
cli.on("Room", this.onRoom);
|
cli.on("Room", this.onRoom);
|
||||||
cli.on("deleteRoom", this.onDeleteRoom);
|
cli.on("deleteRoom", this.onDeleteRoom);
|
||||||
|
@ -66,46 +59,23 @@ module.exports = React.createClass({
|
||||||
cli.on("Room.name", this.onRoomName);
|
cli.on("Room.name", this.onRoomName);
|
||||||
cli.on("Room.tags", this.onRoomTags);
|
cli.on("Room.tags", this.onRoomTags);
|
||||||
cli.on("Room.receipt", this.onRoomReceipt);
|
cli.on("Room.receipt", this.onRoomReceipt);
|
||||||
cli.on("RoomState.members", this.onRoomStateMember);
|
cli.on("RoomState.events", this.onRoomStateEvents);
|
||||||
cli.on("RoomMember.name", this.onRoomMemberName);
|
cli.on("RoomMember.name", this.onRoomMemberName);
|
||||||
cli.on("accountData", this.onAccountData);
|
cli.on("accountData", this.onAccountData);
|
||||||
|
|
||||||
// lookup for which lists a given roomId is currently in.
|
|
||||||
this.listsForRoomId = {};
|
|
||||||
|
|
||||||
var s = this.getRoomLists();
|
var s = this.getRoomLists();
|
||||||
this.setState(s);
|
this.setState(s);
|
||||||
|
|
||||||
// order of the sublists
|
|
||||||
//this.listOrder = [];
|
|
||||||
|
|
||||||
// loop count to stop a stack overflow if the user keeps waggling the
|
|
||||||
// mouse for >30s in a row, or if running under mocha
|
|
||||||
this._delayedRefreshRoomListLoopCount = 0
|
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
// Initialise the stickyHeaders when the component is created
|
// Initialise the stickyHeaders when the component is created
|
||||||
this._updateStickyHeaders(true);
|
this._updateStickyHeaders(true);
|
||||||
|
|
||||||
|
this.mounted = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillReceiveProps: function(nextProps) {
|
componentDidUpdate: function() {
|
||||||
// short-circuit react when the room changes
|
|
||||||
// to avoid rerendering all the sublists everywhere
|
|
||||||
if (nextProps.selectedRoom !== this.props.selectedRoom) {
|
|
||||||
if (this.props.selectedRoom) {
|
|
||||||
constantTimeDispatcher.dispatch(
|
|
||||||
"RoomTile.select", this.props.selectedRoom, {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
constantTimeDispatcher.dispatch(
|
|
||||||
"RoomTile.select", nextProps.selectedRoom, { selected: true }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidUpdate: function(prevProps, prevState) {
|
|
||||||
// Reinitialise the stickyHeaders when the component is updated
|
// Reinitialise the stickyHeaders when the component is updated
|
||||||
this._updateStickyHeaders(true);
|
this._updateStickyHeaders(true);
|
||||||
this._repositionIncomingCallBox(undefined, false);
|
this._repositionIncomingCallBox(undefined, false);
|
||||||
|
@ -131,29 +101,17 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'on_room_read':
|
case 'on_room_read':
|
||||||
// poke the right RoomTile to refresh, using the constantTimeDispatcher
|
// Force an update because the notif count state is too deep to cause
|
||||||
// to avoid each and every RoomTile registering to the 'on_room_read' event
|
// an update. This forces the local echo of reading notifs to be
|
||||||
// XXX: if we like the constantTimeDispatcher we might want to dispatch
|
// reflected by the RoomTiles.
|
||||||
// directly from TimelinePanel rather than needlessly bouncing via here.
|
this.forceUpdate();
|
||||||
constantTimeDispatcher.dispatch(
|
|
||||||
"RoomTile.refresh", payload.room.roomId, {}
|
|
||||||
);
|
|
||||||
|
|
||||||
// also have to poke the right list(s)
|
|
||||||
var lists = this.listsForRoomId[payload.room.roomId];
|
|
||||||
if (lists) {
|
|
||||||
lists.forEach(list=>{
|
|
||||||
constantTimeDispatcher.dispatch(
|
|
||||||
"RoomSubList.refreshHeader", list, { room: payload.room }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
|
this.mounted = false;
|
||||||
|
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
if (MatrixClientPeg.get()) {
|
if (MatrixClientPeg.get()) {
|
||||||
MatrixClientPeg.get().removeListener("Room", this.onRoom);
|
MatrixClientPeg.get().removeListener("Room", this.onRoom);
|
||||||
|
@ -162,7 +120,7 @@ module.exports = React.createClass({
|
||||||
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
|
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
|
||||||
MatrixClientPeg.get().removeListener("Room.tags", this.onRoomTags);
|
MatrixClientPeg.get().removeListener("Room.tags", this.onRoomTags);
|
||||||
MatrixClientPeg.get().removeListener("Room.receipt", this.onRoomReceipt);
|
MatrixClientPeg.get().removeListener("Room.receipt", this.onRoomReceipt);
|
||||||
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
|
MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents);
|
||||||
MatrixClientPeg.get().removeListener("RoomMember.name", this.onRoomMemberName);
|
MatrixClientPeg.get().removeListener("RoomMember.name", this.onRoomMemberName);
|
||||||
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
||||||
}
|
}
|
||||||
|
@ -171,14 +129,10 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoom: function(room) {
|
onRoom: function(room) {
|
||||||
// XXX: this happens rarely; ideally we should only update the correct
|
|
||||||
// sublists when it does (e.g. via a constantTimeDispatch to the right sublist)
|
|
||||||
this._delayedRefreshRoomList();
|
this._delayedRefreshRoomList();
|
||||||
},
|
},
|
||||||
|
|
||||||
onDeleteRoom: function(roomId) {
|
onDeleteRoom: function(roomId) {
|
||||||
// XXX: this happens rarely; ideally we should only update the correct
|
|
||||||
// sublists when it does (e.g. via a constantTimeDispatch to the right sublist)
|
|
||||||
this._delayedRefreshRoomList();
|
this._delayedRefreshRoomList();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -201,10 +155,6 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_onMouseOver: function(ev) {
|
|
||||||
this._lastMouseOverTs = Date.now();
|
|
||||||
},
|
|
||||||
|
|
||||||
onSubListHeaderClick: function(isHidden, scrollToPosition) {
|
onSubListHeaderClick: function(isHidden, scrollToPosition) {
|
||||||
// The scroll area has expanded or contracted, so re-calculate sticky headers positions
|
// The scroll area has expanded or contracted, so re-calculate sticky headers positions
|
||||||
this._updateStickyHeaders(true, scrollToPosition);
|
this._updateStickyHeaders(true, scrollToPosition);
|
||||||
|
@ -214,98 +164,41 @@ module.exports = React.createClass({
|
||||||
if (toStartOfTimeline) return;
|
if (toStartOfTimeline) return;
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return;
|
if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return;
|
||||||
|
this._delayedRefreshRoomList();
|
||||||
// rather than regenerate our full roomlists, which is very heavy, we poke the
|
|
||||||
// correct sublists to just re-sort themselves. This isn't enormously reacty,
|
|
||||||
// but is much faster than the default react reconciler, or having to do voodoo
|
|
||||||
// with shouldComponentUpdate and a pleaseRefresh property or similar.
|
|
||||||
var lists = this.listsForRoomId[room.roomId];
|
|
||||||
if (lists) {
|
|
||||||
lists.forEach(list=>{
|
|
||||||
constantTimeDispatcher.dispatch("RoomSubList.sort", list, { room: room });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// we have to explicitly hit the roomtile which just changed
|
|
||||||
constantTimeDispatcher.dispatch(
|
|
||||||
"RoomTile.refresh", room.roomId, {}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomReceipt: function(receiptEvent, room) {
|
onRoomReceipt: function(receiptEvent, room) {
|
||||||
// because if we read a notification, it will affect notification count
|
// because if we read a notification, it will affect notification count
|
||||||
// only bother updating if there's a receipt from us
|
// only bother updating if there's a receipt from us
|
||||||
if (Receipt.findReadReceiptFromUserId(receiptEvent, MatrixClientPeg.get().credentials.userId)) {
|
if (Receipt.findReadReceiptFromUserId(receiptEvent, MatrixClientPeg.get().credentials.userId)) {
|
||||||
var lists = this.listsForRoomId[room.roomId];
|
this._delayedRefreshRoomList();
|
||||||
if (lists) {
|
|
||||||
lists.forEach(list=>{
|
|
||||||
constantTimeDispatcher.dispatch(
|
|
||||||
"RoomSubList.refreshHeader", list, { room: room }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// we have to explicitly hit the roomtile which just changed
|
|
||||||
constantTimeDispatcher.dispatch(
|
|
||||||
"RoomTile.refresh", room.roomId, {}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomName: function(room) {
|
onRoomName: function(room) {
|
||||||
constantTimeDispatcher.dispatch(
|
this._delayedRefreshRoomList();
|
||||||
"RoomTile.refresh", room.roomId, {}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomTags: function(event, room) {
|
onRoomTags: function(event, room) {
|
||||||
// XXX: this happens rarely; ideally we should only update the correct
|
|
||||||
// sublists when it does (e.g. via a constantTimeDispatch to the right sublist)
|
|
||||||
this._delayedRefreshRoomList();
|
this._delayedRefreshRoomList();
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomStateMember: function(ev, state, member) {
|
onRoomStateEvents: function(ev, state) {
|
||||||
if (ev.getStateKey() === MatrixClientPeg.get().credentials.userId &&
|
|
||||||
ev.getPrevContent() && ev.getPrevContent().membership === "invite")
|
|
||||||
{
|
|
||||||
this._delayedRefreshRoomList();
|
this._delayedRefreshRoomList();
|
||||||
}
|
|
||||||
else {
|
|
||||||
constantTimeDispatcher.dispatch(
|
|
||||||
"RoomTile.refresh", member.roomId, {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomMemberName: function(ev, member) {
|
onRoomMemberName: function(ev, member) {
|
||||||
constantTimeDispatcher.dispatch(
|
this._delayedRefreshRoomList();
|
||||||
"RoomTile.refresh", member.roomId, {}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onAccountData: function(ev) {
|
onAccountData: function(ev) {
|
||||||
if (ev.getType() == 'm.direct') {
|
if (ev.getType() == 'm.direct') {
|
||||||
// XXX: this happens rarely; ideally we should only update the correct
|
|
||||||
// sublists when it does (e.g. via a constantTimeDispatch to the right sublist)
|
|
||||||
this._delayedRefreshRoomList();
|
|
||||||
}
|
|
||||||
else if (ev.getType() == 'm.push_rules') {
|
|
||||||
this._delayedRefreshRoomList();
|
this._delayedRefreshRoomList();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_delayedRefreshRoomList: new rate_limited_func(function() {
|
_delayedRefreshRoomList: new rate_limited_func(function() {
|
||||||
// if the mouse has been moving over the RoomList in the last 500ms
|
|
||||||
// then delay the refresh further to avoid bouncing around under the
|
|
||||||
// cursor
|
|
||||||
if (Date.now() - this._lastMouseOverTs > 500 || this._delayedRefreshRoomListLoopCount > 60) {
|
|
||||||
this.refreshRoomList();
|
this.refreshRoomList();
|
||||||
this._delayedRefreshRoomListLoopCount = 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this._delayedRefreshRoomListLoopCount++;
|
|
||||||
this._delayedRefreshRoomList();
|
|
||||||
}
|
|
||||||
}, 500),
|
}, 500),
|
||||||
|
|
||||||
refreshRoomList: function() {
|
refreshRoomList: function() {
|
||||||
|
@ -313,10 +206,12 @@ module.exports = React.createClass({
|
||||||
// (!this._lastRefreshRoomListTs ? "-" : (Date.now() - this._lastRefreshRoomListTs))
|
// (!this._lastRefreshRoomListTs ? "-" : (Date.now() - this._lastRefreshRoomListTs))
|
||||||
// );
|
// );
|
||||||
|
|
||||||
// TODO: ideally we'd calculate this once at start, and then maintain
|
// TODO: rather than bluntly regenerating and re-sorting everything
|
||||||
// any changes to it incrementally, updating the appropriate sublists
|
// every time we see any kind of room change from the JS SDK
|
||||||
// as needed.
|
// we could do incremental updates on our copy of the state
|
||||||
// Alternatively we'd do something magical with Immutable.js or similar.
|
// based on the room which has actually changed. This would stop
|
||||||
|
// us re-rendering all the sublists every time anything changes anywhere
|
||||||
|
// in the state of the client.
|
||||||
this.setState(this.getRoomLists());
|
this.setState(this.getRoomLists());
|
||||||
|
|
||||||
// this._lastRefreshRoomListTs = Date.now();
|
// this._lastRefreshRoomListTs = Date.now();
|
||||||
|
@ -333,9 +228,6 @@ module.exports = React.createClass({
|
||||||
s.lists["m.lowpriority"] = [];
|
s.lists["m.lowpriority"] = [];
|
||||||
s.lists["im.vector.fake.archived"] = [];
|
s.lists["im.vector.fake.archived"] = [];
|
||||||
|
|
||||||
this.listsForRoomId = {};
|
|
||||||
var otherTagNames = {};
|
|
||||||
|
|
||||||
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
|
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
|
||||||
|
|
||||||
MatrixClientPeg.get().getRooms().forEach(function(room) {
|
MatrixClientPeg.get().getRooms().forEach(function(room) {
|
||||||
|
@ -347,12 +239,7 @@ module.exports = React.createClass({
|
||||||
// ", target = " + me.events.member.getStateKey() +
|
// ", target = " + me.events.member.getStateKey() +
|
||||||
// ", prevMembership = " + me.events.member.getPrevContent().membership);
|
// ", prevMembership = " + me.events.member.getPrevContent().membership);
|
||||||
|
|
||||||
if (!self.listsForRoomId[room.roomId]) {
|
|
||||||
self.listsForRoomId[room.roomId] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (me.membership == "invite") {
|
if (me.membership == "invite") {
|
||||||
self.listsForRoomId[room.roomId].push("im.vector.fake.invite");
|
|
||||||
s.lists["im.vector.fake.invite"].push(room);
|
s.lists["im.vector.fake.invite"].push(room);
|
||||||
}
|
}
|
||||||
else if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(room, me, self.props.ConferenceHandler)) {
|
else if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(room, me, self.props.ConferenceHandler)) {
|
||||||
|
@ -363,27 +250,23 @@ module.exports = React.createClass({
|
||||||
{
|
{
|
||||||
// Used to split rooms via tags
|
// Used to split rooms via tags
|
||||||
var tagNames = Object.keys(room.tags);
|
var tagNames = Object.keys(room.tags);
|
||||||
|
|
||||||
if (tagNames.length) {
|
if (tagNames.length) {
|
||||||
for (var i = 0; i < tagNames.length; i++) {
|
for (var i = 0; i < tagNames.length; i++) {
|
||||||
var tagName = tagNames[i];
|
var tagName = tagNames[i];
|
||||||
s.lists[tagName] = s.lists[tagName] || [];
|
s.lists[tagName] = s.lists[tagName] || [];
|
||||||
s.lists[tagName].push(room);
|
s.lists[tagNames[i]].push(room);
|
||||||
self.listsForRoomId[room.roomId].push(tagName);
|
|
||||||
otherTagNames[tagName] = 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (dmRoomMap.getUserIdForRoomId(room.roomId)) {
|
else if (dmRoomMap.getUserIdForRoomId(room.roomId)) {
|
||||||
// "Direct Message" rooms (that we're still in and that aren't otherwise tagged)
|
// "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);
|
s.lists["im.vector.fake.direct"].push(room);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
self.listsForRoomId[room.roomId].push("im.vector.fake.recent");
|
|
||||||
s.lists["im.vector.fake.recent"].push(room);
|
s.lists["im.vector.fake.recent"].push(room);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (me.membership === "leave") {
|
else if (me.membership === "leave") {
|
||||||
self.listsForRoomId[room.roomId].push("im.vector.fake.archived");
|
|
||||||
s.lists["im.vector.fake.archived"].push(room);
|
s.lists["im.vector.fake.archived"].push(room);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -404,10 +287,8 @@ module.exports = React.createClass({
|
||||||
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
|
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||||
|
|
||||||
if (me && Rooms.looksLikeDirectMessageRoom(room, me)) {
|
if (me && Rooms.looksLikeDirectMessageRoom(room, me)) {
|
||||||
self.listsForRoomId[room.roomId].push("im.vector.fake.direct");
|
|
||||||
s.lists["im.vector.fake.direct"].push(room);
|
s.lists["im.vector.fake.direct"].push(room);
|
||||||
} else {
|
} else {
|
||||||
self.listsForRoomId[room.roomId].push("im.vector.fake.recent");
|
|
||||||
s.lists["im.vector.fake.recent"].push(room);
|
s.lists["im.vector.fake.recent"].push(room);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -424,8 +305,6 @@ module.exports = React.createClass({
|
||||||
newMDirectEvent[otherPerson.userId] = roomList;
|
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
|
// 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();
|
MatrixClientPeg.get().setAccountData('m.direct', newMDirectEvent).done();
|
||||||
}
|
}
|
||||||
|
@ -434,32 +313,19 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// we actually apply the sorting to this when receiving the prop in RoomSubLists.
|
// 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",
|
|
||||||
"im.vector.fake.recent",
|
|
||||||
"im.vector.fake.direct",
|
|
||||||
Object.keys(otherTagNames).filter(tagName=>{
|
|
||||||
return (!tagName.match(/^m\.(favourite|lowpriority)$/));
|
|
||||||
}).sort(),
|
|
||||||
"m.lowpriority",
|
|
||||||
"im.vector.fake.archived"
|
|
||||||
];
|
|
||||||
*/
|
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
},
|
},
|
||||||
|
|
||||||
_getScrollNode: function() {
|
_getScrollNode: function() {
|
||||||
|
if (!this.mounted) return null;
|
||||||
var panel = ReactDOM.findDOMNode(this);
|
var panel = ReactDOM.findDOMNode(this);
|
||||||
if (!panel) return null;
|
if (!panel) return null;
|
||||||
|
|
||||||
// empirically, if we have gm-prevented for some reason, the scroll node
|
if (panel.classList.contains('gm-prevented')) {
|
||||||
// is still the 3rd child (i.e. the view child). This looks to be due
|
return panel;
|
||||||
// to vdh's improved resize updater logic...?
|
} else {
|
||||||
return panel.children[2]; // XXX: Fragile!
|
return panel.children[2]; // XXX: Fragile!
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_whenScrolling: function(e) {
|
_whenScrolling: function(e) {
|
||||||
|
@ -479,6 +345,7 @@ module.exports = React.createClass({
|
||||||
var incomingCallBox = document.getElementById("incomingCallBox");
|
var incomingCallBox = document.getElementById("incomingCallBox");
|
||||||
if (incomingCallBox && incomingCallBox.parentElement) {
|
if (incomingCallBox && incomingCallBox.parentElement) {
|
||||||
var scrollArea = this._getScrollNode();
|
var scrollArea = this._getScrollNode();
|
||||||
|
if (!scrollArea) return;
|
||||||
// Use the offset of the top of the scroll area from the window
|
// 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
|
// as this is used to calculate the CSS fixed top position for the stickies
|
||||||
var scrollAreaOffset = scrollArea.getBoundingClientRect().top + window.pageYOffset;
|
var scrollAreaOffset = scrollArea.getBoundingClientRect().top + window.pageYOffset;
|
||||||
|
@ -502,10 +369,11 @@ module.exports = React.createClass({
|
||||||
// properly through React
|
// properly through React
|
||||||
_initAndPositionStickyHeaders: function(initialise, scrollToPosition) {
|
_initAndPositionStickyHeaders: function(initialise, scrollToPosition) {
|
||||||
var scrollArea = this._getScrollNode();
|
var scrollArea = this._getScrollNode();
|
||||||
|
if (!scrollArea) return;
|
||||||
// Use the offset of the top of the scroll area from the window
|
// 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
|
// as this is used to calculate the CSS fixed top position for the stickies
|
||||||
var scrollAreaOffset = scrollArea.getBoundingClientRect().top + window.pageYOffset;
|
var scrollAreaOffset = scrollArea.getBoundingClientRect().top + window.pageYOffset;
|
||||||
// Use the offset of the top of the component from the window
|
// Use the offset of the top of the componet from the window
|
||||||
// as this is used to calculate the CSS fixed top position for the stickies
|
// as this is used to calculate the CSS fixed top position for the stickies
|
||||||
var scrollAreaHeight = ReactDOM.findDOMNode(this).getBoundingClientRect().height;
|
var scrollAreaHeight = ReactDOM.findDOMNode(this).getBoundingClientRect().height;
|
||||||
|
|
||||||
|
@ -605,16 +473,15 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GeminiScrollbar className="mx_RoomList_scrollbar"
|
<GeminiScrollbar className="mx_RoomList_scrollbar"
|
||||||
autoshow={true} onScroll={ self._whenScrolling } onResize={ self._whenScrolling } ref="gemscroll">
|
autoshow={true} onScroll={ self._whenScrolling } ref="gemscroll">
|
||||||
<div className="mx_RoomList" onMouseOver={ this._onMouseOver }>
|
<div className="mx_RoomList">
|
||||||
<RoomSubList list={ self.state.lists['im.vector.fake.invite'] }
|
<RoomSubList list={ self.state.lists['im.vector.fake.invite'] }
|
||||||
label="Invites"
|
label="Invites"
|
||||||
tagName="im.vector.fake.invite"
|
|
||||||
editable={ false }
|
editable={ false }
|
||||||
order="recent"
|
order="recent"
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
incomingCall={ self.state.incomingCall }
|
incomingCall={ self.state.incomingCall }
|
||||||
collapsed={ self.props.collapsed }
|
collapsed={ self.props.collapsed }
|
||||||
selectedRoom={ self.props.selectedRoom }
|
|
||||||
searchFilter={ self.props.searchFilter }
|
searchFilter={ self.props.searchFilter }
|
||||||
onHeaderClick={ self.onSubListHeaderClick }
|
onHeaderClick={ self.onSubListHeaderClick }
|
||||||
onShowMoreRooms={ self.onShowMoreRooms } />
|
onShowMoreRooms={ self.onShowMoreRooms } />
|
||||||
|
@ -625,9 +492,9 @@ module.exports = React.createClass({
|
||||||
verb="favourite"
|
verb="favourite"
|
||||||
editable={ true }
|
editable={ true }
|
||||||
order="manual"
|
order="manual"
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
incomingCall={ self.state.incomingCall }
|
incomingCall={ self.state.incomingCall }
|
||||||
collapsed={ self.props.collapsed }
|
collapsed={ self.props.collapsed }
|
||||||
selectedRoom={ self.props.selectedRoom }
|
|
||||||
searchFilter={ self.props.searchFilter }
|
searchFilter={ self.props.searchFilter }
|
||||||
onHeaderClick={ self.onSubListHeaderClick }
|
onHeaderClick={ self.onSubListHeaderClick }
|
||||||
onShowMoreRooms={ self.onShowMoreRooms } />
|
onShowMoreRooms={ self.onShowMoreRooms } />
|
||||||
|
@ -638,9 +505,9 @@ module.exports = React.createClass({
|
||||||
verb="tag direct chat"
|
verb="tag direct chat"
|
||||||
editable={ true }
|
editable={ true }
|
||||||
order="recent"
|
order="recent"
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
incomingCall={ self.state.incomingCall }
|
incomingCall={ self.state.incomingCall }
|
||||||
collapsed={ self.props.collapsed }
|
collapsed={ self.props.collapsed }
|
||||||
selectedRoom={ self.props.selectedRoom }
|
|
||||||
alwaysShowHeader={ true }
|
alwaysShowHeader={ true }
|
||||||
searchFilter={ self.props.searchFilter }
|
searchFilter={ self.props.searchFilter }
|
||||||
onHeaderClick={ self.onSubListHeaderClick }
|
onHeaderClick={ self.onSubListHeaderClick }
|
||||||
|
@ -648,18 +515,17 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
<RoomSubList list={ self.state.lists['im.vector.fake.recent'] }
|
<RoomSubList list={ self.state.lists['im.vector.fake.recent'] }
|
||||||
label="Rooms"
|
label="Rooms"
|
||||||
tagName="im.vector.fake.recent"
|
|
||||||
editable={ true }
|
editable={ true }
|
||||||
verb="restore"
|
verb="restore"
|
||||||
order="recent"
|
order="recent"
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
incomingCall={ self.state.incomingCall }
|
incomingCall={ self.state.incomingCall }
|
||||||
collapsed={ self.props.collapsed }
|
collapsed={ self.props.collapsed }
|
||||||
selectedRoom={ self.props.selectedRoom }
|
|
||||||
searchFilter={ self.props.searchFilter }
|
searchFilter={ self.props.searchFilter }
|
||||||
onHeaderClick={ self.onSubListHeaderClick }
|
onHeaderClick={ self.onSubListHeaderClick }
|
||||||
onShowMoreRooms={ self.onShowMoreRooms } />
|
onShowMoreRooms={ self.onShowMoreRooms } />
|
||||||
|
|
||||||
{ Object.keys(self.state.lists).sort().map(function(tagName) {
|
{ Object.keys(self.state.lists).map(function(tagName) {
|
||||||
if (!tagName.match(/^(m\.(favourite|lowpriority)|im\.vector\.fake\.(invite|recent|direct|archived))$/)) {
|
if (!tagName.match(/^(m\.(favourite|lowpriority)|im\.vector\.fake\.(invite|recent|direct|archived))$/)) {
|
||||||
return <RoomSubList list={ self.state.lists[tagName] }
|
return <RoomSubList list={ self.state.lists[tagName] }
|
||||||
key={ tagName }
|
key={ tagName }
|
||||||
|
@ -668,9 +534,9 @@ module.exports = React.createClass({
|
||||||
verb={ "tag as " + tagName }
|
verb={ "tag as " + tagName }
|
||||||
editable={ true }
|
editable={ true }
|
||||||
order="manual"
|
order="manual"
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
incomingCall={ self.state.incomingCall }
|
incomingCall={ self.state.incomingCall }
|
||||||
collapsed={ self.props.collapsed }
|
collapsed={ self.props.collapsed }
|
||||||
selectedRoom={ self.props.selectedRoom }
|
|
||||||
searchFilter={ self.props.searchFilter }
|
searchFilter={ self.props.searchFilter }
|
||||||
onHeaderClick={ self.onSubListHeaderClick }
|
onHeaderClick={ self.onSubListHeaderClick }
|
||||||
onShowMoreRooms={ self.onShowMoreRooms } />;
|
onShowMoreRooms={ self.onShowMoreRooms } />;
|
||||||
|
@ -684,20 +550,19 @@ module.exports = React.createClass({
|
||||||
verb="demote"
|
verb="demote"
|
||||||
editable={ true }
|
editable={ true }
|
||||||
order="recent"
|
order="recent"
|
||||||
|
selectedRoom={ self.props.selectedRoom }
|
||||||
incomingCall={ self.state.incomingCall }
|
incomingCall={ self.state.incomingCall }
|
||||||
collapsed={ self.props.collapsed }
|
collapsed={ self.props.collapsed }
|
||||||
selectedRoom={ self.props.selectedRoom }
|
|
||||||
searchFilter={ self.props.searchFilter }
|
searchFilter={ self.props.searchFilter }
|
||||||
onHeaderClick={ self.onSubListHeaderClick }
|
onHeaderClick={ self.onSubListHeaderClick }
|
||||||
onShowMoreRooms={ self.onShowMoreRooms } />
|
onShowMoreRooms={ self.onShowMoreRooms } />
|
||||||
|
|
||||||
<RoomSubList list={ self.state.lists['im.vector.fake.archived'] }
|
<RoomSubList list={ self.state.lists['im.vector.fake.archived'] }
|
||||||
label="Historical"
|
label="Historical"
|
||||||
tagName="im.vector.fake.archived"
|
|
||||||
editable={ false }
|
editable={ false }
|
||||||
order="recent"
|
order="recent"
|
||||||
collapsed={ self.props.collapsed }
|
|
||||||
selectedRoom={ self.props.selectedRoom }
|
selectedRoom={ self.props.selectedRoom }
|
||||||
|
collapsed={ self.props.collapsed }
|
||||||
alwaysShowHeader={ true }
|
alwaysShowHeader={ true }
|
||||||
startAsHidden={ true }
|
startAsHidden={ true }
|
||||||
showSpinner={ self.state.isLoadingLeftRooms }
|
showSpinner={ self.state.isLoadingLeftRooms }
|
||||||
|
|
|
@ -47,7 +47,7 @@ module.exports = React.createClass({
|
||||||
// The alias that was used to access this room, if appropriate
|
// The alias that was used to access this room, if appropriate
|
||||||
// If given, this will be how the room is referred to (eg.
|
// If given, this will be how the room is referred to (eg.
|
||||||
// in error messages).
|
// in error messages).
|
||||||
roomAlias: React.PropTypes.object,
|
roomAlias: React.PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
|
|
@ -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}/>
|
<PowerSelector ref="ban" value={ban_level} controlled={false} disabled={!can_change_levels || current_user_level < ban_level} onChange={this.onPowerLevelsChanged}/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomSettings_powerLevel">
|
<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}/>
|
<PowerSelector ref="redact" value={redact_level} controlled={false} disabled={!can_change_levels || current_user_level < redact_level} onChange={this.onPowerLevelsChanged}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,6 @@ var RoomNotifs = require('../../../RoomNotifs');
|
||||||
var FormattingUtils = require('../../../utils/FormattingUtils');
|
var FormattingUtils = require('../../../utils/FormattingUtils');
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
var UserSettingsStore = require('../../../UserSettingsStore');
|
var UserSettingsStore = require('../../../UserSettingsStore');
|
||||||
var constantTimeDispatcher = require('../../../ConstantTimeDispatcher');
|
|
||||||
var Unread = require('../../../Unread');
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RoomTile',
|
displayName: 'RoomTile',
|
||||||
|
@ -38,10 +36,12 @@ module.exports = React.createClass({
|
||||||
connectDropTarget: React.PropTypes.func,
|
connectDropTarget: React.PropTypes.func,
|
||||||
onClick: React.PropTypes.func,
|
onClick: React.PropTypes.func,
|
||||||
isDragging: React.PropTypes.bool,
|
isDragging: React.PropTypes.bool,
|
||||||
selectedRoom: React.PropTypes.string,
|
|
||||||
|
|
||||||
room: React.PropTypes.object.isRequired,
|
room: React.PropTypes.object.isRequired,
|
||||||
collapsed: React.PropTypes.bool.isRequired,
|
collapsed: React.PropTypes.bool.isRequired,
|
||||||
|
selected: React.PropTypes.bool.isRequired,
|
||||||
|
unread: React.PropTypes.bool.isRequired,
|
||||||
|
highlight: React.PropTypes.bool.isRequired,
|
||||||
isInvite: React.PropTypes.bool.isRequired,
|
isInvite: React.PropTypes.bool.isRequired,
|
||||||
incomingCall: React.PropTypes.object,
|
incomingCall: React.PropTypes.object,
|
||||||
},
|
},
|
||||||
|
@ -54,11 +54,10 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return({
|
return({
|
||||||
hover: false,
|
hover : false,
|
||||||
badgeHover: false,
|
badgeHover : false,
|
||||||
menuDisplayed: false,
|
menuDisplayed: false,
|
||||||
notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
|
notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
|
||||||
selected: this.props.room ? (this.props.selectedRoom === this.props.room.roomId) : false,
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -80,32 +79,23 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onAccountData: function(accountDataEvent) {
|
||||||
|
if (accountDataEvent.getType() == 'm.push_rules') {
|
||||||
|
this.setState({
|
||||||
|
notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
constantTimeDispatcher.register("RoomTile.refresh", this.props.room.roomId, this.onRefresh);
|
MatrixClientPeg.get().on("accountData", this.onAccountData);
|
||||||
constantTimeDispatcher.register("RoomTile.select", this.props.room.roomId, this.onSelect);
|
|
||||||
this.onRefresh();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
constantTimeDispatcher.unregister("RoomTile.refresh", this.props.room.roomId, this.onRefresh);
|
var cli = MatrixClientPeg.get();
|
||||||
constantTimeDispatcher.unregister("RoomTile.select", this.props.room.roomId, this.onSelect);
|
if (cli) {
|
||||||
},
|
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
||||||
|
}
|
||||||
componentWillReceiveProps: function(nextProps) {
|
|
||||||
this.onRefresh();
|
|
||||||
},
|
|
||||||
|
|
||||||
onRefresh: function(params) {
|
|
||||||
this.setState({
|
|
||||||
unread: Unread.doesRoomHaveUnreadMessages(this.props.room),
|
|
||||||
highlight: this.props.room.getUnreadNotificationCount('highlight') > 0 || this.props.isInvite,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onSelect: function(params) {
|
|
||||||
this.setState({
|
|
||||||
selected: params.selected,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onClick: function(ev) {
|
onClick: function(ev) {
|
||||||
|
@ -179,13 +169,13 @@ module.exports = React.createClass({
|
||||||
// var highlightCount = this.props.room.getUnreadNotificationCount("highlight");
|
// var highlightCount = this.props.room.getUnreadNotificationCount("highlight");
|
||||||
|
|
||||||
const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge();
|
const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge();
|
||||||
const mentionBadges = this.state.highlight && this._shouldShowMentionBadge();
|
const mentionBadges = this.props.highlight && this._shouldShowMentionBadge();
|
||||||
const badges = notifBadges || mentionBadges;
|
const badges = notifBadges || mentionBadges;
|
||||||
|
|
||||||
var classes = classNames({
|
var classes = classNames({
|
||||||
'mx_RoomTile': true,
|
'mx_RoomTile': true,
|
||||||
'mx_RoomTile_selected': this.state.selected,
|
'mx_RoomTile_selected': this.props.selected,
|
||||||
'mx_RoomTile_unread': this.state.unread,
|
'mx_RoomTile_unread': this.props.unread,
|
||||||
'mx_RoomTile_unreadNotify': notifBadges,
|
'mx_RoomTile_unreadNotify': notifBadges,
|
||||||
'mx_RoomTile_highlight': mentionBadges,
|
'mx_RoomTile_highlight': mentionBadges,
|
||||||
'mx_RoomTile_invited': (me && me.membership == 'invite'),
|
'mx_RoomTile_invited': (me && me.membership == 'invite'),
|
||||||
|
@ -231,7 +221,7 @@ module.exports = React.createClass({
|
||||||
'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.menuDisplayed,
|
'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.menuDisplayed,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.state.selected) {
|
if (this.props.selected) {
|
||||||
let nameSelected = <EmojiText>{name}</EmojiText>;
|
let nameSelected = <EmojiText>{name}</EmojiText>;
|
||||||
|
|
||||||
label = <div title={ name } className={ nameClasses }>{ nameSelected }</div>;
|
label = <div title={ name } className={ nameClasses }>{ nameSelected }</div>;
|
||||||
|
@ -265,8 +255,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
let ret = (
|
let ret = (
|
||||||
<div> { /* Only native elements can be wrapped in a DnD object. */}
|
<div> { /* Only native elements can be wrapped in a DnD object. */}
|
||||||
<AccessibleButton className={classes} tabIndex="0" onClick={this.onClick}
|
<AccessibleButton className={classes} tabIndex="0" onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||||
onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
|
||||||
<div className={avatarClasses}>
|
<div className={avatarClasses}>
|
||||||
<div className="mx_RoomTile_avatar_container">
|
<div className="mx_RoomTile_avatar_container">
|
||||||
<RoomAvatar room={this.props.room} width={24} height={24} />
|
<RoomAvatar room={this.props.room} width={24} height={24} />
|
||||||
|
|
|
@ -60,7 +60,7 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<li data-scroll-token={eventId+"+"+j}>
|
<li data-scroll-tokens={eventId+"+"+j}>
|
||||||
{ret}
|
{ret}
|
||||||
</li>);
|
</li>);
|
||||||
},
|
},
|
||||||
|
|
|
@ -19,6 +19,7 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
import sdk from '../../../index';
|
||||||
|
|
||||||
// cancel button which is shared between room header and simple room header
|
// cancel button which is shared between room header and simple room header
|
||||||
export function CancelButton(props) {
|
export function CancelButton(props) {
|
||||||
|
@ -45,6 +46,9 @@ export default React.createClass({
|
||||||
|
|
||||||
// is the RightPanel collapsed?
|
// is the RightPanel collapsed?
|
||||||
collapsedRhs: React.PropTypes.bool,
|
collapsedRhs: React.PropTypes.bool,
|
||||||
|
|
||||||
|
// `src` to a TintableSvg. Optional.
|
||||||
|
icon: React.PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
onShowRhsClick: function(ev) {
|
onShowRhsClick: function(ev) {
|
||||||
|
@ -53,9 +57,17 @@ export default React.createClass({
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
let cancelButton;
|
let cancelButton;
|
||||||
|
let icon;
|
||||||
if (this.props.onCancelClick) {
|
if (this.props.onCancelClick) {
|
||||||
cancelButton = <CancelButton onClick={this.props.onCancelClick} />;
|
cancelButton = <CancelButton onClick={this.props.onCancelClick} />;
|
||||||
}
|
}
|
||||||
|
if (this.props.icon) {
|
||||||
|
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
||||||
|
icon = <TintableSvg
|
||||||
|
className="mx_RoomHeader_icon" src={this.props.icon}
|
||||||
|
width="25" height="25"
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
let showRhsButton;
|
let showRhsButton;
|
||||||
/* // don't bother cluttering things up with this for now.
|
/* // don't bother cluttering things up with this for now.
|
||||||
|
@ -73,6 +85,7 @@ export default React.createClass({
|
||||||
<div className="mx_RoomHeader" >
|
<div className="mx_RoomHeader" >
|
||||||
<div className="mx_RoomHeader_wrapper">
|
<div className="mx_RoomHeader_wrapper">
|
||||||
<div className="mx_RoomHeader_simpleHeader">
|
<div className="mx_RoomHeader_simpleHeader">
|
||||||
|
{ icon }
|
||||||
{ this.props.title }
|
{ this.props.title }
|
||||||
{ showRhsButton }
|
{ showRhsButton }
|
||||||
{ cancelButton }
|
{ cancelButton }
|
||||||
|
|
|
@ -38,7 +38,7 @@ module.exports = React.createClass({
|
||||||
title="Scroll to unread messages"/>
|
title="Scroll to unread messages"/>
|
||||||
Jump to first unread message.
|
Jump to first unread message.
|
||||||
</div>
|
</div>
|
||||||
<img className="mx_TopUnreadMessagesBar_close"
|
<img className="mx_TopUnreadMessagesBar_close mx_filterFlipColor"
|
||||||
src="img/cancel.svg" width="18" height="18"
|
src="img/cancel.svg" width="18" height="18"
|
||||||
alt="Close" title="Close"
|
alt="Close" title="Close"
|
||||||
onClick={this.props.onCloseClick} />
|
onClick={this.props.onCloseClick} />
|
||||||
|
|
|
@ -50,7 +50,7 @@ export default WithMatrixClient(React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_onPhoneCountryChange: function(phoneCountry) {
|
_onPhoneCountryChange: function(phoneCountry) {
|
||||||
this.setState({ phoneCountry: phoneCountry });
|
this.setState({ phoneCountry: phoneCountry.iso2 });
|
||||||
},
|
},
|
||||||
|
|
||||||
_onPhoneNumberChange: function(ev) {
|
_onPhoneNumberChange: function(ev) {
|
||||||
|
@ -147,12 +147,14 @@ export default WithMatrixClient(React.createClass({
|
||||||
return (
|
return (
|
||||||
<form className="mx_UserSettings_profileTableRow" onSubmit={this._onAddMsisdnSubmit}>
|
<form className="mx_UserSettings_profileTableRow" onSubmit={this._onAddMsisdnSubmit}>
|
||||||
<div className="mx_UserSettings_profileLabelCell">
|
<div className="mx_UserSettings_profileLabelCell">
|
||||||
|
<label>Phone</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_UserSettings_profileInputCell">
|
<div className="mx_UserSettings_profileInputCell">
|
||||||
<div className="mx_Login_phoneSection">
|
<div className="mx_UserSettings_phoneSection">
|
||||||
<CountryDropdown onOptionChange={this._onPhoneCountryChange}
|
<CountryDropdown onOptionChange={this._onPhoneCountryChange}
|
||||||
className="mx_Login_phoneCountry"
|
className="mx_UserSettings_phoneCountry"
|
||||||
value={this.state.phoneCountry}
|
value={this.state.phoneCountry}
|
||||||
|
isSmall={true}
|
||||||
/>
|
/>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
ref={this._collectAddMsisdnInput}
|
ref={this._collectAddMsisdnInput}
|
||||||
|
|
|
@ -115,7 +115,7 @@ var Tester = React.createClass({
|
||||||
//
|
//
|
||||||
// there is an extra 50 pixels of margin at the bottom.
|
// there is an extra 50 pixels of margin at the bottom.
|
||||||
return (
|
return (
|
||||||
<li key={key} data-scroll-token={key}>
|
<li key={key} data-scroll-tokens={key}>
|
||||||
<div style={{height: '98px', margin: '50px', border: '1px solid black',
|
<div style={{height: '98px', margin: '50px', border: '1px solid black',
|
||||||
backgroundColor: '#fff8dc' }}>
|
backgroundColor: '#fff8dc' }}>
|
||||||
{key}
|
{key}
|
||||||
|
|
Loading…
Reference in a new issue