mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-18 06:35:35 +08:00
Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into webrtc_settings
This commit is contained in:
commit
a4b2bacc7e
3
.gitignore
vendored
3
.gitignore
vendored
@ -9,3 +9,6 @@ npm-debug.log
|
||||
|
||||
# test reports created by karma
|
||||
/karma-reports
|
||||
|
||||
# ignore auto-generated component index
|
||||
/src/component-index.js
|
||||
|
@ -9,11 +9,16 @@ set -ev
|
||||
RIOT_WEB_DIR=riot-web
|
||||
REACT_SDK_DIR=`pwd`
|
||||
|
||||
git clone --depth=1 --branch develop https://github.com/vector-im/riot-web.git \
|
||||
curbranch="${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH}"
|
||||
echo "Determined branch to be $curbranch"
|
||||
|
||||
git clone https://github.com/vector-im/riot-web.git \
|
||||
"$RIOT_WEB_DIR"
|
||||
|
||||
cd "$RIOT_WEB_DIR"
|
||||
|
||||
git checkout "$curbranch" || git checkout develop
|
||||
|
||||
mkdir node_modules
|
||||
npm install
|
||||
|
||||
|
172
CHANGELOG.md
172
CHANGELOG.md
@ -1,3 +1,175 @@
|
||||
Changes in [0.8.9](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.8.9) (2017-05-22)
|
||||
===================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.8.9-rc.1...v0.8.9)
|
||||
|
||||
* No changes
|
||||
|
||||
|
||||
Changes in [0.8.9-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.8.9-rc.1) (2017-05-19)
|
||||
=============================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.8.8...v0.8.9-rc.1)
|
||||
|
||||
* Prevent an exception getting scroll node
|
||||
[\#902](https://github.com/matrix-org/matrix-react-sdk/pull/902)
|
||||
* Fix a few remaining snags with country dd
|
||||
[\#901](https://github.com/matrix-org/matrix-react-sdk/pull/901)
|
||||
* Add left_aligned class to CountryDropdown
|
||||
[\#900](https://github.com/matrix-org/matrix-react-sdk/pull/900)
|
||||
* Swap to new flag files (which are stored as GB.png)
|
||||
[\#899](https://github.com/matrix-org/matrix-react-sdk/pull/899)
|
||||
* Improve phone number country dropdown for registration and login (Act. 2,
|
||||
Return of the Prefix)
|
||||
[\#897](https://github.com/matrix-org/matrix-react-sdk/pull/897)
|
||||
* Support for pasting files into normal composer
|
||||
[\#892](https://github.com/matrix-org/matrix-react-sdk/pull/892)
|
||||
* tell guests they can't use filepanel until they register
|
||||
[\#887](https://github.com/matrix-org/matrix-react-sdk/pull/887)
|
||||
* Prevent reskindex -w from running when file names have not changed
|
||||
[\#888](https://github.com/matrix-org/matrix-react-sdk/pull/888)
|
||||
* I broke UserSettings for webpack-dev-server
|
||||
[\#884](https://github.com/matrix-org/matrix-react-sdk/pull/884)
|
||||
* various fixes to RoomHeader
|
||||
[\#880](https://github.com/matrix-org/matrix-react-sdk/pull/880)
|
||||
* remove /me whether or not it has a space after it
|
||||
[\#885](https://github.com/matrix-org/matrix-react-sdk/pull/885)
|
||||
* show error if we can't set a filter because no room
|
||||
[\#883](https://github.com/matrix-org/matrix-react-sdk/pull/883)
|
||||
* Fix RM not updating if RR event unpaginated
|
||||
[\#874](https://github.com/matrix-org/matrix-react-sdk/pull/874)
|
||||
* change roomsettings wording
|
||||
[\#878](https://github.com/matrix-org/matrix-react-sdk/pull/878)
|
||||
* make reskindex windows friendly
|
||||
[\#875](https://github.com/matrix-org/matrix-react-sdk/pull/875)
|
||||
* Fixes 2 issues with Dialog closing
|
||||
[\#867](https://github.com/matrix-org/matrix-react-sdk/pull/867)
|
||||
* Automatic Reskindex
|
||||
[\#871](https://github.com/matrix-org/matrix-react-sdk/pull/871)
|
||||
* Put room name in 'leave room' confirmation dialog
|
||||
[\#873](https://github.com/matrix-org/matrix-react-sdk/pull/873)
|
||||
* Fix this/self fail in LeftPanel
|
||||
[\#872](https://github.com/matrix-org/matrix-react-sdk/pull/872)
|
||||
* Don't show null URL previews
|
||||
[\#870](https://github.com/matrix-org/matrix-react-sdk/pull/870)
|
||||
* Fix keys for AddressSelector
|
||||
[\#869](https://github.com/matrix-org/matrix-react-sdk/pull/869)
|
||||
* Make left panel better for new users (mk II)
|
||||
[\#859](https://github.com/matrix-org/matrix-react-sdk/pull/859)
|
||||
* Explicitly save composer content onUnload
|
||||
[\#866](https://github.com/matrix-org/matrix-react-sdk/pull/866)
|
||||
* Warn on unload
|
||||
[\#851](https://github.com/matrix-org/matrix-react-sdk/pull/851)
|
||||
* Log deviceid at login
|
||||
[\#862](https://github.com/matrix-org/matrix-react-sdk/pull/862)
|
||||
* Guests can't send RR so no point trying
|
||||
[\#860](https://github.com/matrix-org/matrix-react-sdk/pull/860)
|
||||
* Remove babelcheck
|
||||
[\#861](https://github.com/matrix-org/matrix-react-sdk/pull/861)
|
||||
* T3chguy/settings versions improvements
|
||||
[\#857](https://github.com/matrix-org/matrix-react-sdk/pull/857)
|
||||
* Change max-len 90->120
|
||||
[\#852](https://github.com/matrix-org/matrix-react-sdk/pull/852)
|
||||
* Remove DM-guessing code
|
||||
[\#829](https://github.com/matrix-org/matrix-react-sdk/pull/829)
|
||||
* Fix jumping to an unread event when in MELS
|
||||
[\#855](https://github.com/matrix-org/matrix-react-sdk/pull/855)
|
||||
* Validate phone number on login
|
||||
[\#856](https://github.com/matrix-org/matrix-react-sdk/pull/856)
|
||||
* Failed to enable HTML5 Notifications Error Dialogs
|
||||
[\#827](https://github.com/matrix-org/matrix-react-sdk/pull/827)
|
||||
* Pin filesize ver to fix break upstream
|
||||
[\#854](https://github.com/matrix-org/matrix-react-sdk/pull/854)
|
||||
* Improve RoomDirectory Look & Feel
|
||||
[\#848](https://github.com/matrix-org/matrix-react-sdk/pull/848)
|
||||
* Only show jumpToReadMarker bar when RM !== RR
|
||||
[\#845](https://github.com/matrix-org/matrix-react-sdk/pull/845)
|
||||
* Allow MELS to have its own RM
|
||||
[\#846](https://github.com/matrix-org/matrix-react-sdk/pull/846)
|
||||
* Use document.onkeydown instead of onkeypress
|
||||
[\#844](https://github.com/matrix-org/matrix-react-sdk/pull/844)
|
||||
* (Room)?Avatar: Request 96x96 avatars on high DPI screens
|
||||
[\#808](https://github.com/matrix-org/matrix-react-sdk/pull/808)
|
||||
* Add mx_EventTile_emote class
|
||||
[\#842](https://github.com/matrix-org/matrix-react-sdk/pull/842)
|
||||
* Fix dialog reappearing after hitting Enter
|
||||
[\#841](https://github.com/matrix-org/matrix-react-sdk/pull/841)
|
||||
* Fix spinner that shows until the first sync
|
||||
[\#840](https://github.com/matrix-org/matrix-react-sdk/pull/840)
|
||||
* Show spinner until first sync has completed
|
||||
[\#839](https://github.com/matrix-org/matrix-react-sdk/pull/839)
|
||||
* Style fixes for LoggedInView
|
||||
[\#838](https://github.com/matrix-org/matrix-react-sdk/pull/838)
|
||||
* Fix specifying custom server for registration
|
||||
[\#834](https://github.com/matrix-org/matrix-react-sdk/pull/834)
|
||||
* Improve country dropdown UX and expose +prefix
|
||||
[\#833](https://github.com/matrix-org/matrix-react-sdk/pull/833)
|
||||
* Fix user settings store
|
||||
[\#836](https://github.com/matrix-org/matrix-react-sdk/pull/836)
|
||||
* show the room name in the UDE Dialog
|
||||
[\#832](https://github.com/matrix-org/matrix-react-sdk/pull/832)
|
||||
* summarise profile changes in MELS
|
||||
[\#826](https://github.com/matrix-org/matrix-react-sdk/pull/826)
|
||||
* Transform h1 and h2 tags to h3 tags
|
||||
[\#820](https://github.com/matrix-org/matrix-react-sdk/pull/820)
|
||||
* limit our keyboard shortcut modifiers correctly
|
||||
[\#825](https://github.com/matrix-org/matrix-react-sdk/pull/825)
|
||||
* Specify cross platform regexes and add olm to noParse
|
||||
[\#823](https://github.com/matrix-org/matrix-react-sdk/pull/823)
|
||||
* Remember element that was in focus before rendering dialog
|
||||
[\#822](https://github.com/matrix-org/matrix-react-sdk/pull/822)
|
||||
* move user settings outward and use built in read receipts disabling
|
||||
[\#824](https://github.com/matrix-org/matrix-react-sdk/pull/824)
|
||||
* File Download Consistency
|
||||
[\#802](https://github.com/matrix-org/matrix-react-sdk/pull/802)
|
||||
* Show Access Token under Advanced in Settings
|
||||
[\#806](https://github.com/matrix-org/matrix-react-sdk/pull/806)
|
||||
* Link tags/commit hashes in the UserSettings version section
|
||||
[\#810](https://github.com/matrix-org/matrix-react-sdk/pull/810)
|
||||
* On return to RoomView from auxPanel, send focus back to Composer
|
||||
[\#813](https://github.com/matrix-org/matrix-react-sdk/pull/813)
|
||||
* Change presence status labels to 'for' instead of 'ago'
|
||||
[\#817](https://github.com/matrix-org/matrix-react-sdk/pull/817)
|
||||
* Disable Scalar Integrations if urls passed to it are falsey
|
||||
[\#816](https://github.com/matrix-org/matrix-react-sdk/pull/816)
|
||||
* Add option to hide other people's read receipts.
|
||||
[\#818](https://github.com/matrix-org/matrix-react-sdk/pull/818)
|
||||
* Add option to not send typing notifications
|
||||
[\#819](https://github.com/matrix-org/matrix-react-sdk/pull/819)
|
||||
* Sync RM across instances of Riot
|
||||
[\#805](https://github.com/matrix-org/matrix-react-sdk/pull/805)
|
||||
* First iteration on improving login UI
|
||||
[\#811](https://github.com/matrix-org/matrix-react-sdk/pull/811)
|
||||
* focus on composer after jumping to bottom
|
||||
[\#809](https://github.com/matrix-org/matrix-react-sdk/pull/809)
|
||||
* Improve RoomList performance via side-stepping React
|
||||
[\#807](https://github.com/matrix-org/matrix-react-sdk/pull/807)
|
||||
* Don't show link preview when link is inside of a quote
|
||||
[\#762](https://github.com/matrix-org/matrix-react-sdk/pull/762)
|
||||
* Escape closes UserSettings
|
||||
[\#765](https://github.com/matrix-org/matrix-react-sdk/pull/765)
|
||||
* Implement user power-level changes in timeline
|
||||
[\#794](https://github.com/matrix-org/matrix-react-sdk/pull/794)
|
||||
|
||||
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)
|
||||
===================================================================================================
|
||||
[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
|
||||
}
|
||||
```
|
||||
- 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
|
||||
are simple and closely related. If you put the next declaration on a new line,
|
||||
treat yourself to another `var`:
|
||||
|
||||
```javascript
|
||||
var key = "foo",
|
||||
const key = "foo",
|
||||
comparator = function(x, y) {
|
||||
return x - y;
|
||||
}; // Bad
|
||||
|
||||
var key = "foo";
|
||||
var comparator = function(x, y) {
|
||||
const key = "foo";
|
||||
const comparator = function(x, y) {
|
||||
return x - y;
|
||||
}; // Good
|
||||
|
||||
var x = 0, y = 0; // Fine
|
||||
let x = 0, y = 0; // Fine
|
||||
|
||||
var x = 0;
|
||||
var y = 0; // Also fine
|
||||
let x = 0;
|
||||
let y = 0; // Also fine
|
||||
```
|
||||
- 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 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.
|
||||
|
16
package.json
16
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "matrix-react-sdk",
|
||||
"version": "0.8.7",
|
||||
"version": "0.8.9",
|
||||
"description": "SDK for matrix.org using React",
|
||||
"author": "matrix.org",
|
||||
"repository": {
|
||||
@ -31,9 +31,11 @@
|
||||
"reskindex": "scripts/reskindex.js"
|
||||
},
|
||||
"scripts": {
|
||||
"reskindex": "scripts/reskindex.js -h header",
|
||||
"build": "babel src -d lib --source-maps",
|
||||
"start": "babel src -w -d lib --source-maps",
|
||||
"reskindex": "node scripts/reskindex.js -h header",
|
||||
"reskindex:watch": "node scripts/reskindex.js -h header -w",
|
||||
"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/",
|
||||
"lintall": "eslint src/ test/",
|
||||
"clean": "rimraf lib",
|
||||
@ -61,13 +63,13 @@
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"linkifyjs": "^2.1.3",
|
||||
"lodash": "^4.13.1",
|
||||
"matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
|
||||
"matrix-js-sdk": "0.7.8",
|
||||
"optimist": "^0.6.1",
|
||||
"q": "^1.4.1",
|
||||
"react": "^15.4.0",
|
||||
"react-addons-css-transition-group": "15.3.2",
|
||||
"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",
|
||||
"text-encoding-utf-8": "^1.0.1",
|
||||
"velocity-vector": "vector-im/velocity#059e3b2",
|
||||
@ -88,6 +90,7 @@
|
||||
"babel-preset-es2016": "^6.11.3",
|
||||
"babel-preset-es2017": "^6.14.0",
|
||||
"babel-preset-react": "^6.11.1",
|
||||
"chokidar": "^1.6.1",
|
||||
"eslint": "^3.13.1",
|
||||
"eslint-config-google": "^0.7.1",
|
||||
"eslint-plugin-babel": "^4.0.1",
|
||||
@ -104,6 +107,7 @@
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-webpack": "^1.7.0",
|
||||
"mocha": "^2.4.5",
|
||||
"parallelshell": "^1.2.0",
|
||||
"phantomjs-prebuilt": "^2.1.7",
|
||||
"react-addons-test-utils": "^15.4.0",
|
||||
"require-json": "0.0.1",
|
||||
|
@ -1,53 +1,99 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var glob = require('glob');
|
||||
|
||||
var args = require('optimist').argv;
|
||||
|
||||
var header = args.h || args.header;
|
||||
|
||||
var componentsDir = path.join('src', 'components');
|
||||
var chokidar = require('chokidar');
|
||||
|
||||
var componentIndex = path.join('src', 'component-index.js');
|
||||
var componentIndexTmp = componentIndex+".tmp";
|
||||
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) {
|
||||
strm.write(fs.readFileSync(header));
|
||||
strm.write('\n');
|
||||
var strm = fs.createWriteStream(componentIndexTmp);
|
||||
|
||||
if (header) {
|
||||
strm.write(fs.readFileSync(header));
|
||||
strm.write('\n');
|
||||
}
|
||||
|
||||
strm.write("/*\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(" * so you'd just be trying to swim upstream like a salmon.\n");
|
||||
strm.write(" * You are not a salmon.\n");
|
||||
strm.write(" */\n\n");
|
||||
|
||||
if (packageJson['matrix-react-parent']) {
|
||||
const parentIndex = packageJson['matrix-react-parent'] +
|
||||
'/lib/component-index';
|
||||
strm.write(
|
||||
`let components = require('${parentIndex}').components;
|
||||
if (!components) {
|
||||
throw new Error("'${parentIndex}' didn't export components");
|
||||
}
|
||||
`);
|
||||
} else {
|
||||
strm.write("let components = {};\n");
|
||||
}
|
||||
|
||||
for (var i = 0; i < files.length; ++i) {
|
||||
var file = files[i].replace('.js', '');
|
||||
|
||||
var moduleName = (file.replace(/\//g, '.'));
|
||||
var importName = moduleName.replace(/\./g, "$");
|
||||
|
||||
strm.write("import " + importName + " from './components/" + file + "';\n");
|
||||
strm.write(importName + " && (components['"+moduleName+"'] = " + importName + ");");
|
||||
strm.write('\n');
|
||||
strm.uncork();
|
||||
}
|
||||
|
||||
strm.write("export {components};\n");
|
||||
strm.end();
|
||||
fs.rename(componentIndexTmp, componentIndex, function(err) {
|
||||
if(err) {
|
||||
console.error("Error moving new index into place: " + err);
|
||||
} else {
|
||||
console.log('Reskindex: completed');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
strm.write("/*\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(" * so you'd just be trying to swim upstream like a salmon.\n");
|
||||
strm.write(" * You are not a salmon.\n");
|
||||
strm.write(" *\n");
|
||||
strm.write(" * To update it, run:\n");
|
||||
strm.write(" * ./reskindex.js -h header\n");
|
||||
strm.write(" */\n\n");
|
||||
|
||||
if (packageJson['matrix-react-parent']) {
|
||||
strm.write("module.exports.components = require('"+packageJson['matrix-react-parent']+"/lib/component-index').components;\n\n");
|
||||
} else {
|
||||
strm.write("module.exports.components = {};\n");
|
||||
// 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;
|
||||
}
|
||||
|
||||
var files = glob.sync('**/*.js', {cwd: componentsDir}).sort();
|
||||
for (var i = 0; i < files.length; ++i) {
|
||||
var file = files[i].replace('.js', '');
|
||||
|
||||
var moduleName = (file.replace(/\//g, '.'));
|
||||
var importName = moduleName.replace(/\./g, "$");
|
||||
|
||||
strm.write("import " + importName + " from './components/" + file + "';\n");
|
||||
strm.write(importName + " && (module.exports.components['"+moduleName+"'] = " + importName + ");");
|
||||
strm.write('\n');
|
||||
strm.uncork();
|
||||
// -w indicates watch mode where any FS events will trigger reskindex
|
||||
if (!args.w) {
|
||||
reskindex();
|
||||
return;
|
||||
}
|
||||
|
||||
strm.end();
|
||||
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);
|
||||
});
|
||||
|
@ -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 months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||
|
||||
function pad(n) {
|
||||
return (n < 10 ? '0' : '') + n;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
formatDate: function(date) {
|
||||
// date.toLocaleTimeString is completely system dependent.
|
||||
// just go 24h for now
|
||||
function pad(n) {
|
||||
return (n < 10 ? '0' : '') + n;
|
||||
}
|
||||
|
||||
var now = new Date();
|
||||
if (date.toDateString() === now.toDateString()) {
|
||||
@ -34,19 +35,20 @@ module.exports = {
|
||||
else if (now.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) {
|
||||
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());
|
||||
}
|
||||
/*
|
||||
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) {
|
||||
//return pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||
return ('00' + date.getHours()).slice(-2) + ':' + ('00' + date.getMinutes()).slice(-2);
|
||||
return pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -148,17 +148,18 @@ var sanitizeHtmlParams = {
|
||||
attribs.href = m[1];
|
||||
delete attribs.target;
|
||||
}
|
||||
|
||||
m = attribs.href.match(linkifyMatrix.MATRIXTO_URL_PATTERN);
|
||||
if (m) {
|
||||
var entity = m[1];
|
||||
if (entity[0] === '@') {
|
||||
attribs.href = '#/user/' + entity;
|
||||
else {
|
||||
m = attribs.href.match(linkifyMatrix.MATRIXTO_URL_PATTERN);
|
||||
if (m) {
|
||||
var entity = m[1];
|
||||
if (entity[0] === '@') {
|
||||
attribs.href = '#/user/' + entity;
|
||||
}
|
||||
else if (entity[0] === '#' || entity[0] === '!') {
|
||||
attribs.href = '#/room/' + entity;
|
||||
}
|
||||
delete attribs.target;
|
||||
}
|
||||
else if (entity[0] === '#' || entity[0] === '!') {
|
||||
attribs.href = '#/room/' + entity;
|
||||
}
|
||||
delete attribs.target;
|
||||
}
|
||||
}
|
||||
attribs.rel = 'noopener'; // https://mathiasbynens.github.io/rel-noopener/
|
||||
|
@ -32,5 +32,4 @@ module.exports = {
|
||||
DELETE: 46,
|
||||
KEY_D: 68,
|
||||
KEY_E: 69,
|
||||
KEY_K: 75,
|
||||
};
|
||||
|
@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
var MatrixClientPeg = require("./MatrixClientPeg");
|
||||
var dis = require("./dispatcher");
|
||||
var Tinter = require("./Tinter");
|
||||
import MatrixClientPeg from "./MatrixClientPeg";
|
||||
import dis from "./dispatcher";
|
||||
import Tinter from "./Tinter";
|
||||
import sdk from './index';
|
||||
import Modal from './Modal';
|
||||
|
||||
@ -45,19 +45,25 @@ class Command {
|
||||
}
|
||||
}
|
||||
|
||||
var reject = function(msg) {
|
||||
function reject(msg) {
|
||||
return {
|
||||
error: msg
|
||||
error: msg,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
var success = function(promise) {
|
||||
function success(promise) {
|
||||
return {
|
||||
promise: promise
|
||||
promise: promise,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
var commands = {
|
||||
/* Disable the "unexpected this" error for these commands - all of the run
|
||||
* functions are called with `this` bound to the Command instance.
|
||||
*/
|
||||
|
||||
/* eslint-disable babel/no-invalid-this */
|
||||
|
||||
const commands = {
|
||||
ddg: new Command("ddg", "<query>", function(roomId, args) {
|
||||
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
||||
// TODO Don't explain this away, actually show a search UI here.
|
||||
@ -69,30 +75,30 @@ var commands = {
|
||||
}),
|
||||
|
||||
// Change your nickname
|
||||
nick: new Command("nick", "<display_name>", function(room_id, args) {
|
||||
nick: new Command("nick", "<display_name>", function(roomId, args) {
|
||||
if (args) {
|
||||
return success(
|
||||
MatrixClientPeg.get().setDisplayName(args)
|
||||
MatrixClientPeg.get().setDisplayName(args),
|
||||
);
|
||||
}
|
||||
return reject(this.getUsage());
|
||||
}),
|
||||
|
||||
// Changes the colorscheme of your current room
|
||||
tint: new Command("tint", "<color1> [<color2>]", function(room_id, args) {
|
||||
tint: new Command("tint", "<color1> [<color2>]", function(roomId, args) {
|
||||
if (args) {
|
||||
var matches = args.match(/^(#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}))( +(#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})))?$/);
|
||||
const matches = args.match(/^(#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}))( +(#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})))?$/);
|
||||
if (matches) {
|
||||
Tinter.tint(matches[1], matches[4]);
|
||||
var colorScheme = {};
|
||||
const colorScheme = {};
|
||||
colorScheme.primary_color = matches[1];
|
||||
if (matches[4]) {
|
||||
colorScheme.secondary_color = matches[4];
|
||||
}
|
||||
return success(
|
||||
MatrixClientPeg.get().setRoomAccountData(
|
||||
room_id, "org.matrix.room.color_scheme", colorScheme
|
||||
)
|
||||
roomId, "org.matrix.room.color_scheme", colorScheme,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -100,22 +106,22 @@ var commands = {
|
||||
}),
|
||||
|
||||
// Change the room topic
|
||||
topic: new Command("topic", "<topic>", function(room_id, args) {
|
||||
topic: new Command("topic", "<topic>", function(roomId, args) {
|
||||
if (args) {
|
||||
return success(
|
||||
MatrixClientPeg.get().setRoomTopic(room_id, args)
|
||||
MatrixClientPeg.get().setRoomTopic(roomId, args),
|
||||
);
|
||||
}
|
||||
return reject(this.getUsage());
|
||||
}),
|
||||
|
||||
// Invite a user
|
||||
invite: new Command("invite", "<userId>", function(room_id, args) {
|
||||
invite: new Command("invite", "<userId>", function(roomId, args) {
|
||||
if (args) {
|
||||
var matches = args.match(/^(\S+)$/);
|
||||
const matches = args.match(/^(\S+)$/);
|
||||
if (matches) {
|
||||
return success(
|
||||
MatrixClientPeg.get().invite(room_id, matches[1])
|
||||
MatrixClientPeg.get().invite(roomId, matches[1]),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -123,21 +129,21 @@ var commands = {
|
||||
}),
|
||||
|
||||
// Join a room
|
||||
join: new Command("join", "#alias:domain", function(room_id, args) {
|
||||
join: new Command("join", "#alias:domain", function(roomId, args) {
|
||||
if (args) {
|
||||
var matches = args.match(/^(\S+)$/);
|
||||
const matches = args.match(/^(\S+)$/);
|
||||
if (matches) {
|
||||
var room_alias = matches[1];
|
||||
if (room_alias[0] !== '#') {
|
||||
let roomAlias = matches[1];
|
||||
if (roomAlias[0] !== '#') {
|
||||
return reject(this.getUsage());
|
||||
}
|
||||
if (!room_alias.match(/:/)) {
|
||||
room_alias += ':' + MatrixClientPeg.get().getDomain();
|
||||
if (!roomAlias.match(/:/)) {
|
||||
roomAlias += ':' + MatrixClientPeg.get().getDomain();
|
||||
}
|
||||
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_alias: room_alias,
|
||||
roomAlias: roomAlias,
|
||||
auto_join: true,
|
||||
});
|
||||
|
||||
@ -147,29 +153,29 @@ var commands = {
|
||||
return reject(this.getUsage());
|
||||
}),
|
||||
|
||||
part: new Command("part", "[#alias:domain]", function(room_id, args) {
|
||||
var targetRoomId;
|
||||
part: new Command("part", "[#alias:domain]", function(roomId, args) {
|
||||
let targetRoomId;
|
||||
if (args) {
|
||||
var matches = args.match(/^(\S+)$/);
|
||||
const matches = args.match(/^(\S+)$/);
|
||||
if (matches) {
|
||||
var room_alias = matches[1];
|
||||
if (room_alias[0] !== '#') {
|
||||
let roomAlias = matches[1];
|
||||
if (roomAlias[0] !== '#') {
|
||||
return reject(this.getUsage());
|
||||
}
|
||||
if (!room_alias.match(/:/)) {
|
||||
room_alias += ':' + MatrixClientPeg.get().getDomain();
|
||||
if (!roomAlias.match(/:/)) {
|
||||
roomAlias += ':' + MatrixClientPeg.get().getDomain();
|
||||
}
|
||||
|
||||
// Try to find a room with this alias
|
||||
var rooms = MatrixClientPeg.get().getRooms();
|
||||
for (var i = 0; i < rooms.length; i++) {
|
||||
var aliasEvents = rooms[i].currentState.getStateEvents(
|
||||
"m.room.aliases"
|
||||
const rooms = MatrixClientPeg.get().getRooms();
|
||||
for (let i = 0; i < rooms.length; i++) {
|
||||
const aliasEvents = rooms[i].currentState.getStateEvents(
|
||||
"m.room.aliases",
|
||||
);
|
||||
for (var j = 0; j < aliasEvents.length; j++) {
|
||||
var aliases = aliasEvents[j].getContent().aliases || [];
|
||||
for (var k = 0; k < aliases.length; k++) {
|
||||
if (aliases[k] === room_alias) {
|
||||
for (let j = 0; j < aliasEvents.length; j++) {
|
||||
const aliases = aliasEvents[j].getContent().aliases || [];
|
||||
for (let k = 0; k < aliases.length; k++) {
|
||||
if (aliases[k] === roomAlias) {
|
||||
targetRoomId = rooms[i].roomId;
|
||||
break;
|
||||
}
|
||||
@ -178,27 +184,28 @@ var commands = {
|
||||
}
|
||||
if (targetRoomId) { break; }
|
||||
}
|
||||
}
|
||||
if (!targetRoomId) {
|
||||
return reject("Unrecognised room alias: " + room_alias);
|
||||
if (!targetRoomId) {
|
||||
return reject("Unrecognised room alias: " + roomAlias);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!targetRoomId) targetRoomId = room_id;
|
||||
if (!targetRoomId) targetRoomId = roomId;
|
||||
return success(
|
||||
MatrixClientPeg.get().leave(targetRoomId).then(
|
||||
function() {
|
||||
dis.dispatch({action: 'view_next_room'});
|
||||
})
|
||||
function() {
|
||||
dis.dispatch({action: 'view_next_room'});
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
|
||||
// Kick a user from the room with an optional reason
|
||||
kick: new Command("kick", "<userId> [<reason>]", function(room_id, args) {
|
||||
kick: new Command("kick", "<userId> [<reason>]", function(roomId, args) {
|
||||
if (args) {
|
||||
var matches = args.match(/^(\S+?)( +(.*))?$/);
|
||||
const matches = args.match(/^(\S+?)( +(.*))?$/);
|
||||
if (matches) {
|
||||
return success(
|
||||
MatrixClientPeg.get().kick(room_id, matches[1], matches[3])
|
||||
MatrixClientPeg.get().kick(roomId, matches[1], matches[3]),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -206,12 +213,12 @@ var commands = {
|
||||
}),
|
||||
|
||||
// Ban a user from the room with an optional reason
|
||||
ban: new Command("ban", "<userId> [<reason>]", function(room_id, args) {
|
||||
ban: new Command("ban", "<userId> [<reason>]", function(roomId, args) {
|
||||
if (args) {
|
||||
var matches = args.match(/^(\S+?)( +(.*))?$/);
|
||||
const matches = args.match(/^(\S+?)( +(.*))?$/);
|
||||
if (matches) {
|
||||
return success(
|
||||
MatrixClientPeg.get().ban(room_id, matches[1], matches[3])
|
||||
MatrixClientPeg.get().ban(roomId, matches[1], matches[3]),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -219,13 +226,13 @@ var commands = {
|
||||
}),
|
||||
|
||||
// Unban a user from the room
|
||||
unban: new Command("unban", "<userId>", function(room_id, args) {
|
||||
unban: new Command("unban", "<userId>", function(roomId, args) {
|
||||
if (args) {
|
||||
var matches = args.match(/^(\S+)$/);
|
||||
const matches = args.match(/^(\S+)$/);
|
||||
if (matches) {
|
||||
// Reset the user membership to "leave" to unban him
|
||||
return success(
|
||||
MatrixClientPeg.get().unban(room_id, matches[1])
|
||||
MatrixClientPeg.get().unban(roomId, matches[1]),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -233,27 +240,27 @@ var commands = {
|
||||
}),
|
||||
|
||||
// Define the power level of a user
|
||||
op: new Command("op", "<userId> [<power level>]", function(room_id, args) {
|
||||
op: new Command("op", "<userId> [<power level>]", function(roomId, args) {
|
||||
if (args) {
|
||||
var matches = args.match(/^(\S+?)( +(\d+))?$/);
|
||||
var powerLevel = 50; // default power level for op
|
||||
const matches = args.match(/^(\S+?)( +(\d+))?$/);
|
||||
let powerLevel = 50; // default power level for op
|
||||
if (matches) {
|
||||
var user_id = matches[1];
|
||||
const userId = matches[1];
|
||||
if (matches.length === 4 && undefined !== matches[3]) {
|
||||
powerLevel = parseInt(matches[3]);
|
||||
}
|
||||
if (powerLevel !== NaN) {
|
||||
var room = MatrixClientPeg.get().getRoom(room_id);
|
||||
if (!isNaN(powerLevel)) {
|
||||
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||
if (!room) {
|
||||
return reject("Bad room ID: " + room_id);
|
||||
return reject("Bad room ID: " + roomId);
|
||||
}
|
||||
var powerLevelEvent = room.currentState.getStateEvents(
|
||||
"m.room.power_levels", ""
|
||||
const powerLevelEvent = room.currentState.getStateEvents(
|
||||
"m.room.power_levels", "",
|
||||
);
|
||||
return success(
|
||||
MatrixClientPeg.get().setPowerLevel(
|
||||
room_id, user_id, powerLevel, powerLevelEvent
|
||||
)
|
||||
roomId, userId, powerLevel, powerLevelEvent,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -262,32 +269,87 @@ var commands = {
|
||||
}),
|
||||
|
||||
// Reset the power level of a user
|
||||
deop: new Command("deop", "<userId>", function(room_id, args) {
|
||||
deop: new Command("deop", "<userId>", function(roomId, args) {
|
||||
if (args) {
|
||||
var matches = args.match(/^(\S+)$/);
|
||||
const matches = args.match(/^(\S+)$/);
|
||||
if (matches) {
|
||||
var room = MatrixClientPeg.get().getRoom(room_id);
|
||||
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||
if (!room) {
|
||||
return reject("Bad room ID: " + room_id);
|
||||
return reject("Bad room ID: " + roomId);
|
||||
}
|
||||
|
||||
var powerLevelEvent = room.currentState.getStateEvents(
|
||||
"m.room.power_levels", ""
|
||||
const powerLevelEvent = room.currentState.getStateEvents(
|
||||
"m.room.power_levels", "",
|
||||
);
|
||||
return success(
|
||||
MatrixClientPeg.get().setPowerLevel(
|
||||
room_id, args, undefined, powerLevelEvent
|
||||
)
|
||||
roomId, args, undefined, powerLevelEvent,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return reject(this.getUsage());
|
||||
})
|
||||
}),
|
||||
|
||||
// Verify a user, device, and pubkey tuple
|
||||
verify: new Command("verify", "<userId> <deviceId> <deviceSigningKey>", function(roomId, args) {
|
||||
if (args) {
|
||||
const matches = args.match(/^(\S+) +(\S+) +(\S+)$/);
|
||||
if (matches) {
|
||||
const userId = matches[1];
|
||||
const deviceId = matches[2];
|
||||
const fingerprint = matches[3];
|
||||
|
||||
const device = MatrixClientPeg.get().getStoredDevice(userId, deviceId);
|
||||
if (!device) {
|
||||
return reject(`Unknown (user, device) pair: (${userId}, ${deviceId})`);
|
||||
}
|
||||
|
||||
if (device.isVerified()) {
|
||||
if (device.getFingerprint() === fingerprint) {
|
||||
return reject(`Device already verified!`);
|
||||
} else {
|
||||
return reject(`WARNING: Device already verified, but keys do NOT MATCH!`);
|
||||
}
|
||||
}
|
||||
|
||||
if (device.getFingerprint() === fingerprint) {
|
||||
MatrixClientPeg.get().setDeviceVerified(
|
||||
userId, deviceId, true,
|
||||
);
|
||||
|
||||
// Tell the user we verified everything!
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Verified key",
|
||||
description: (
|
||||
<div>
|
||||
<p>
|
||||
The signing key you provided matches the signing key you received
|
||||
from { userId }'s device { deviceId }. Device marked as verified.
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
hasCancelButton: false,
|
||||
});
|
||||
|
||||
return success();
|
||||
} else {
|
||||
return reject(`WARNING: KEY VERIFICATION FAILED! The signing key for ${userId} and device
|
||||
${deviceId} is "${device.getFingerprint()}" which does not match the provided key
|
||||
"${fingerprint}". This could mean your communications are being intercepted!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return reject(this.getUsage());
|
||||
}),
|
||||
};
|
||||
/* eslint-enable babel/no-invalid-this */
|
||||
|
||||
|
||||
// helpful aliases
|
||||
var aliases = {
|
||||
j: "join"
|
||||
const aliases = {
|
||||
j: "join",
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
@ -304,13 +366,13 @@ module.exports = {
|
||||
// IRC-style commands
|
||||
input = input.replace(/\s+$/, "");
|
||||
if (input[0] === "/" && input[1] !== "/") {
|
||||
var bits = input.match(/^(\S+?)( +((.|\n)*))?$/);
|
||||
var cmd, args;
|
||||
const bits = input.match(/^(\S+?)( +((.|\n)*))?$/);
|
||||
let cmd;
|
||||
let args;
|
||||
if (bits) {
|
||||
cmd = bits[1].substring(1).toLowerCase();
|
||||
args = bits[3];
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
cmd = input;
|
||||
}
|
||||
if (cmd === "me") return null;
|
||||
@ -319,8 +381,7 @@ module.exports = {
|
||||
}
|
||||
if (commands[cmd]) {
|
||||
return commands[cmd].run(roomId, args);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return reject("Unrecognised command: " + input);
|
||||
}
|
||||
}
|
||||
@ -329,12 +390,12 @@ module.exports = {
|
||||
|
||||
getCommandList: function() {
|
||||
// Return all the commands plus /me and /markdown which aren't handled like normal commands
|
||||
var cmds = Object.keys(commands).sort().map(function(cmdKey) {
|
||||
const cmds = Object.keys(commands).sort().map(function(cmdKey) {
|
||||
return commands[cmdKey];
|
||||
});
|
||||
cmds.push(new Command("me", "<action>", function() {}));
|
||||
cmds.push(new Command("markdown", "<on|off>", function() {}));
|
||||
|
||||
return cmds;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -65,8 +65,8 @@ function textForMemberEvent(ev) {
|
||||
} else if (!ev.getPrevContent().avatar_url && ev.getContent().avatar_url) {
|
||||
return senderName + " set a profile picture";
|
||||
} else {
|
||||
// hacky hack for https://github.com/vector-im/vector-web/issues/2020
|
||||
return senderName + " rejoined the room.";
|
||||
// suppress null rejoins
|
||||
return '';
|
||||
}
|
||||
} else {
|
||||
if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key);
|
||||
|
@ -25,7 +25,9 @@ module.exports = {
|
||||
eventTriggersUnreadCount: function(ev) {
|
||||
if (ev.sender && ev.sender.userId == MatrixClientPeg.get().credentials.userId) {
|
||||
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;
|
||||
} else if (ev.getType == 'm.room.message' && ev.getContent().msgtype == 'm.notify') {
|
||||
return false;
|
||||
|
@ -1,253 +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$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 room = client.getRoom(roomId);
|
||||
|
||||
this.noRoom = !room;
|
||||
|
||||
if (room) {
|
||||
var filter = new Matrix.Filter(client.credentials.userId);
|
||||
filter.setDefinition(
|
||||
@ -82,13 +84,22 @@ var FilePanel = React.createClass({
|
||||
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!");
|
||||
}
|
||||
},
|
||||
|
||||
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.
|
||||
var TimelinePanel = sdk.getComponent("structures.TimelinePanel");
|
||||
var Loader = sdk.getComponent("elements.Spinner");
|
||||
|
@ -112,18 +112,6 @@ export default React.createClass({
|
||||
var handled = false;
|
||||
|
||||
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.DOWN:
|
||||
if (ev.altKey && !ev.shiftKey && !ev.ctrlKey && !ev.metaKey) {
|
||||
|
@ -17,27 +17,24 @@ limitations under the License.
|
||||
|
||||
import q from 'q';
|
||||
|
||||
var React = require('react');
|
||||
var Matrix = require("matrix-js-sdk");
|
||||
import React from 'react';
|
||||
import Matrix from "matrix-js-sdk";
|
||||
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
var PlatformPeg = require("../../PlatformPeg");
|
||||
var SdkConfig = require("../../SdkConfig");
|
||||
var ContextualMenu = require("./ContextualMenu");
|
||||
var RoomListSorter = require("../../RoomListSorter");
|
||||
var UserActivity = require("../../UserActivity");
|
||||
var Presence = require("../../Presence");
|
||||
var dis = require("../../dispatcher");
|
||||
import MatrixClientPeg from "../../MatrixClientPeg";
|
||||
import PlatformPeg from "../../PlatformPeg";
|
||||
import SdkConfig from "../../SdkConfig";
|
||||
import * as RoomListSorter from "../../RoomListSorter";
|
||||
import dis from "../../dispatcher";
|
||||
|
||||
var Modal = require("../../Modal");
|
||||
var Tinter = require("../../Tinter");
|
||||
var sdk = require('../../index');
|
||||
var Rooms = require('../../Rooms');
|
||||
var linkifyMatrix = require("../../linkify-matrix");
|
||||
var Lifecycle = require('../../Lifecycle');
|
||||
var PageTypes = require('../../PageTypes');
|
||||
import Modal from "../../Modal";
|
||||
import Tinter from "../../Tinter";
|
||||
import sdk from '../../index';
|
||||
import * as Rooms from '../../Rooms';
|
||||
import linkifyMatrix from "../../linkify-matrix";
|
||||
import * as Lifecycle from '../../Lifecycle';
|
||||
import PageTypes from '../../PageTypes';
|
||||
|
||||
var createRoom = require("../../createRoom");
|
||||
import createRoom from "../../createRoom";
|
||||
import * as UDEHandler from '../../UnknownDeviceErrorHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
@ -89,7 +86,7 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
var s = {
|
||||
const s = {
|
||||
loading: true,
|
||||
screen: undefined,
|
||||
screenAfterLogin: this.props.initialScreenAfterLogin,
|
||||
@ -156,11 +153,9 @@ module.exports = React.createClass({
|
||||
return this.state.register_hs_url;
|
||||
} else if (MatrixClientPeg.get()) {
|
||||
return MatrixClientPeg.get().getHomeserverUrl();
|
||||
}
|
||||
else if (window.localStorage && window.localStorage.getItem("mx_hs_url")) {
|
||||
} else if (window.localStorage && window.localStorage.getItem("mx_hs_url")) {
|
||||
return window.localStorage.getItem("mx_hs_url");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return this.getDefaultHsUrl();
|
||||
}
|
||||
},
|
||||
@ -178,11 +173,9 @@ module.exports = React.createClass({
|
||||
return this.state.register_is_url;
|
||||
} else if (MatrixClientPeg.get()) {
|
||||
return MatrixClientPeg.get().getIdentityServerUrl();
|
||||
}
|
||||
else if (window.localStorage && window.localStorage.getItem("mx_is_url")) {
|
||||
} else if (window.localStorage && window.localStorage.getItem("mx_is_url")) {
|
||||
return window.localStorage.getItem("mx_is_url");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return this.getDefaultIsUrl();
|
||||
}
|
||||
},
|
||||
@ -324,28 +317,14 @@ module.exports = React.createClass({
|
||||
onAction: function(payload) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
var roomIndexDelta = 1;
|
||||
const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog");
|
||||
|
||||
var self = this;
|
||||
switch (payload.action) {
|
||||
case 'logout':
|
||||
Lifecycle.logout();
|
||||
break;
|
||||
case 'start_registration':
|
||||
const params = payload.params || {};
|
||||
this.setStateForNewScreen({
|
||||
screen: 'register',
|
||||
// these params may be undefined, but if they are,
|
||||
// unset them from our state: we don't want to
|
||||
// resume a previous registration session if the
|
||||
// user just clicked 'register'
|
||||
register_client_secret: params.client_secret,
|
||||
register_session_id: params.session_id,
|
||||
register_hs_url: params.hs_url,
|
||||
register_is_url: params.is_url,
|
||||
register_id_sid: params.sid,
|
||||
});
|
||||
this.notifyNewScreen('register');
|
||||
this._startRegistration(payload.params || {});
|
||||
break;
|
||||
case 'start_login':
|
||||
if (MatrixClientPeg.get() &&
|
||||
@ -362,7 +341,7 @@ module.exports = React.createClass({
|
||||
break;
|
||||
case 'start_post_registration':
|
||||
this.setState({ // don't clobber loggedIn status
|
||||
screen: 'post_registration'
|
||||
screen: 'post_registration',
|
||||
});
|
||||
break;
|
||||
case 'start_upgrade_registration':
|
||||
@ -392,33 +371,7 @@ module.exports = React.createClass({
|
||||
this.notifyNewScreen('forgot_password');
|
||||
break;
|
||||
case 'leave_room':
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Leave room",
|
||||
description: "Are you sure you want to leave the room?",
|
||||
onFinished: (should_leave) => {
|
||||
if (should_leave) {
|
||||
const d = MatrixClientPeg.get().leave(payload.room_id);
|
||||
|
||||
// FIXME: controller shouldn't be loading a view :(
|
||||
const Loader = sdk.getComponent("elements.Spinner");
|
||||
const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
|
||||
|
||||
d.then(() => {
|
||||
modal.close();
|
||||
if (this.currentRoomId === payload.room_id) {
|
||||
dis.dispatch({action: 'view_next_room'});
|
||||
}
|
||||
}, (err) => {
|
||||
modal.close();
|
||||
console.error("Failed to leave room " + payload.room_id + " " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to leave room",
|
||||
description: (err && err.message ? err.message : "Server may be unavailable, overloaded, or you hit a bug."),
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
this._leaveRoom(payload.room_id);
|
||||
break;
|
||||
case 'reject_invite':
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
@ -439,11 +392,11 @@ module.exports = React.createClass({
|
||||
modal.close();
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to reject invitation",
|
||||
description: err.toString()
|
||||
description: err.toString(),
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
break;
|
||||
case 'view_user':
|
||||
@ -468,30 +421,13 @@ module.exports = React.createClass({
|
||||
this._viewRoom(payload);
|
||||
break;
|
||||
case 'view_prev_room':
|
||||
roomIndexDelta = -1;
|
||||
this._viewNextRoom(-1);
|
||||
break;
|
||||
case 'view_next_room':
|
||||
var allRooms = RoomListSorter.mostRecentActivityFirst(
|
||||
MatrixClientPeg.get().getRooms()
|
||||
);
|
||||
var roomIndex = -1;
|
||||
for (var i = 0; i < allRooms.length; ++i) {
|
||||
if (allRooms[i].roomId == this.state.currentRoomId) {
|
||||
roomIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
roomIndex = (roomIndex + roomIndexDelta) % allRooms.length;
|
||||
if (roomIndex < 0) roomIndex = allRooms.length - 1;
|
||||
this._viewRoom({ room_id: allRooms[roomIndex].roomId });
|
||||
this._viewNextRoom(1);
|
||||
break;
|
||||
case 'view_indexed_room':
|
||||
var allRooms = RoomListSorter.mostRecentActivityFirst(
|
||||
MatrixClientPeg.get().getRooms()
|
||||
);
|
||||
var roomIndex = payload.roomIndex;
|
||||
if (allRooms[roomIndex]) {
|
||||
this._viewRoom({ room_id: allRooms[roomIndex].roomId });
|
||||
}
|
||||
this._viewIndexedRoom(payload.roomIndex);
|
||||
break;
|
||||
case 'view_user_settings':
|
||||
this._setPage(PageTypes.UserSettings);
|
||||
@ -500,19 +436,17 @@ module.exports = React.createClass({
|
||||
case 'view_create_room':
|
||||
//this._setPage(PageTypes.CreateRoom);
|
||||
//this.notifyNewScreen('new');
|
||||
|
||||
var TextInputDialog = sdk.getComponent("dialogs.TextInputDialog");
|
||||
Modal.createDialog(TextInputDialog, {
|
||||
title: "Create Room",
|
||||
description: "Room name (optional)",
|
||||
button: "Create Room",
|
||||
onFinished: (should_create, name) => {
|
||||
if (should_create) {
|
||||
onFinished: (shouldCreate, name) => {
|
||||
if (shouldCreate) {
|
||||
const createOpts = {};
|
||||
if (name) createOpts.name = name;
|
||||
createRoom({createOpts}).done();
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
break;
|
||||
case 'view_room_directory':
|
||||
@ -583,7 +517,7 @@ module.exports = React.createClass({
|
||||
case 'new_version':
|
||||
this.onVersion(
|
||||
payload.currentVersion, payload.newVersion,
|
||||
payload.releaseNotes
|
||||
payload.releaseNotes,
|
||||
);
|
||||
break;
|
||||
}
|
||||
@ -595,6 +529,47 @@ module.exports = React.createClass({
|
||||
});
|
||||
},
|
||||
|
||||
_startRegistration: function(params) {
|
||||
this.setStateForNewScreen({
|
||||
screen: 'register',
|
||||
// these params may be undefined, but if they are,
|
||||
// unset them from our state: we don't want to
|
||||
// resume a previous registration session if the
|
||||
// user just clicked 'register'
|
||||
register_client_secret: params.client_secret,
|
||||
register_session_id: params.session_id,
|
||||
register_hs_url: params.hs_url,
|
||||
register_is_url: params.is_url,
|
||||
register_id_sid: params.sid,
|
||||
});
|
||||
this.notifyNewScreen('register');
|
||||
},
|
||||
|
||||
_viewNextRoom: function(roomIndexDelta) {
|
||||
const allRooms = RoomListSorter.mostRecentActivityFirst(
|
||||
MatrixClientPeg.get().getRooms(),
|
||||
);
|
||||
let roomIndex = -1;
|
||||
for (let i = 0; i < allRooms.length; ++i) {
|
||||
if (allRooms[i].roomId == this.state.currentRoomId) {
|
||||
roomIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
roomIndex = (roomIndex + roomIndexDelta) % allRooms.length;
|
||||
if (roomIndex < 0) roomIndex = allRooms.length - 1;
|
||||
this._viewRoom({ room_id: allRooms[roomIndex].roomId });
|
||||
},
|
||||
|
||||
_viewIndexedRoom: function(roomIndex) {
|
||||
const allRooms = RoomListSorter.mostRecentActivityFirst(
|
||||
MatrixClientPeg.get().getRooms(),
|
||||
);
|
||||
if (allRooms[roomIndex]) {
|
||||
this._viewRoom({ room_id: allRooms[roomIndex].roomId });
|
||||
}
|
||||
},
|
||||
|
||||
// switch view to the given room
|
||||
//
|
||||
// @param {Object} room_info Object containing data about the room to be joined
|
||||
@ -614,7 +589,7 @@ module.exports = React.createClass({
|
||||
_viewRoom: function(room_info) {
|
||||
this.focusComposer = true;
|
||||
|
||||
var newState = {
|
||||
const newState = {
|
||||
initialEventId: room_info.event_id,
|
||||
highlightedEventId: room_info.event_id,
|
||||
initialEventPixelOffset: undefined,
|
||||
@ -634,7 +609,7 @@ module.exports = React.createClass({
|
||||
//
|
||||
// TODO: do this in RoomView rather than here
|
||||
if (!room_info.event_id && this.refs.loggedInView) {
|
||||
var scrollState = this.refs.loggedInView.getScrollStateForRoom(room_info.room_id);
|
||||
const scrollState = this.refs.loggedInView.getScrollStateForRoom(room_info.room_id);
|
||||
if (scrollState) {
|
||||
newState.initialEventId = scrollState.focussedEvent;
|
||||
newState.initialEventPixelOffset = scrollState.pixelOffset;
|
||||
@ -676,14 +651,14 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
_createChat: function() {
|
||||
var ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
|
||||
const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
|
||||
Modal.createDialog(ChatInviteDialog, {
|
||||
title: "Start a new chat",
|
||||
});
|
||||
},
|
||||
|
||||
_invite: function(roomId) {
|
||||
var ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
|
||||
const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
|
||||
Modal.createDialog(ChatInviteDialog, {
|
||||
title: "Invite new room members",
|
||||
button: "Send Invites",
|
||||
@ -692,6 +667,41 @@ module.exports = React.createClass({
|
||||
});
|
||||
},
|
||||
|
||||
_leaveRoom: function(roomId) {
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
|
||||
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Leave room",
|
||||
description: <span>Are you sure you want to leave the room <i>{roomToLeave.name}</i>?</span>,
|
||||
onFinished: (shouldLeave) => {
|
||||
if (shouldLeave) {
|
||||
const d = MatrixClientPeg.get().leave(roomId);
|
||||
|
||||
// FIXME: controller shouldn't be loading a view :(
|
||||
const Loader = sdk.getComponent("elements.Spinner");
|
||||
const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
|
||||
|
||||
d.then(() => {
|
||||
modal.close();
|
||||
if (this.currentRoomId === roomId) {
|
||||
dis.dispatch({action: 'view_next_room'});
|
||||
}
|
||||
}, (err) => {
|
||||
modal.close();
|
||||
console.error("Failed to leave room " + roomId + " " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to leave room",
|
||||
description: (err && err.message ? err.message :
|
||||
"Server may be unavailable, overloaded, or you hit a bug."),
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the sessionloader has finished
|
||||
*/
|
||||
@ -710,6 +720,8 @@ module.exports = React.createClass({
|
||||
|
||||
/**
|
||||
* Called whenever someone changes the theme
|
||||
*
|
||||
* @param {string} theme new theme
|
||||
*/
|
||||
_onSetTheme: function(theme) {
|
||||
if (!theme) {
|
||||
@ -718,12 +730,12 @@ module.exports = React.createClass({
|
||||
|
||||
// look for the stylesheet elements.
|
||||
// styleElements is a map from style name to HTMLLinkElement.
|
||||
var styleElements = Object.create(null);
|
||||
var i, a;
|
||||
for (i = 0; (a = document.getElementsByTagName("link")[i]); i++) {
|
||||
var href = a.getAttribute("href");
|
||||
const styleElements = Object.create(null);
|
||||
let a;
|
||||
for (let i = 0; (a = document.getElementsByTagName("link")[i]); i++) {
|
||||
const href = a.getAttribute("href");
|
||||
// shouldn't we be using the 'title' tag rather than the href?
|
||||
var match = href.match(/^bundles\/.*\/theme-(.*)\.css$/);
|
||||
const match = href.match(/^bundles\/.*\/theme-(.*)\.css$/);
|
||||
if (match) {
|
||||
styleElements[match[1]] = a;
|
||||
}
|
||||
@ -746,14 +758,15 @@ module.exports = React.createClass({
|
||||
// abuse the tinter to change all the SVG's #fff to #2d2d2d
|
||||
// XXX: obviously this shouldn't be hardcoded here.
|
||||
Tinter.tintSvgWhite('#2d2d2d');
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Tinter.tintSvgWhite('#ffffff');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when a new logged in session has started
|
||||
*
|
||||
* @param {string} teamToken
|
||||
*/
|
||||
_onLoggedIn: function(teamToken) {
|
||||
this.setState({
|
||||
@ -767,8 +780,12 @@ module.exports = React.createClass({
|
||||
this._teamToken = teamToken;
|
||||
dis.dispatch({action: 'view_home_page'});
|
||||
} 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
|
||||
dis.dispatch({action: 'view_user_settings'});
|
||||
dis.dispatch({action: 'view_room_directory'});
|
||||
} else {
|
||||
this._showScreenAfterLogin();
|
||||
}
|
||||
@ -780,7 +797,7 @@ module.exports = React.createClass({
|
||||
if (this.state.screenAfterLogin && this.state.screenAfterLogin.screen) {
|
||||
this.showScreen(
|
||||
this.state.screenAfterLogin.screen,
|
||||
this.state.screenAfterLogin.params
|
||||
this.state.screenAfterLogin.params,
|
||||
);
|
||||
this.notifyNewScreen(this.state.screenAfterLogin.screen);
|
||||
this.setState({screenAfterLogin: null});
|
||||
@ -821,8 +838,8 @@ module.exports = React.createClass({
|
||||
* (useful for setting listeners)
|
||||
*/
|
||||
_onWillStartClient() {
|
||||
var self = this;
|
||||
var cli = MatrixClientPeg.get();
|
||||
const self = this;
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
||||
// Allow the JS SDK to reap timeline events. This reduces the amount of
|
||||
// memory consumed as the JS SDK stores multiple distinct copies of room
|
||||
@ -863,17 +880,17 @@ module.exports = React.createClass({
|
||||
cli.on('Call.incoming', function(call) {
|
||||
dis.dispatch({
|
||||
action: 'incoming_call',
|
||||
call: call
|
||||
call: call,
|
||||
});
|
||||
});
|
||||
cli.on('Session.logged_out', function(call) {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Signed Out",
|
||||
description: "For security, this session has been signed out. Please sign in again."
|
||||
description: "For security, this session has been signed out. Please sign in again.",
|
||||
});
|
||||
dis.dispatch({
|
||||
action: 'logout'
|
||||
action: 'logout',
|
||||
});
|
||||
});
|
||||
cli.on("accountData", function(ev) {
|
||||
@ -896,17 +913,17 @@ module.exports = React.createClass({
|
||||
if (screen == 'register') {
|
||||
dis.dispatch({
|
||||
action: 'start_registration',
|
||||
params: params
|
||||
params: params,
|
||||
});
|
||||
} else if (screen == 'login') {
|
||||
dis.dispatch({
|
||||
action: 'start_login',
|
||||
params: params
|
||||
params: params,
|
||||
});
|
||||
} else if (screen == 'forgot_password') {
|
||||
dis.dispatch({
|
||||
action: 'start_password_recovery',
|
||||
params: params
|
||||
params: params,
|
||||
});
|
||||
} else if (screen == 'new') {
|
||||
dis.dispatch({
|
||||
@ -929,26 +946,26 @@ module.exports = React.createClass({
|
||||
action: 'start_post_registration',
|
||||
});
|
||||
} else if (screen.indexOf('room/') == 0) {
|
||||
var segments = screen.substring(5).split('/');
|
||||
var roomString = segments[0];
|
||||
var eventId = segments[1]; // undefined if no event id given
|
||||
const segments = screen.substring(5).split('/');
|
||||
const roomString = segments[0];
|
||||
const eventId = segments[1]; // undefined if no event id given
|
||||
|
||||
// FIXME: sort_out caseConsistency
|
||||
var third_party_invite = {
|
||||
const thirdPartyInvite = {
|
||||
inviteSignUrl: params.signurl,
|
||||
invitedEmail: params.email,
|
||||
};
|
||||
var oob_data = {
|
||||
const oobData = {
|
||||
name: params.room_name,
|
||||
avatarUrl: params.room_avatar_url,
|
||||
inviterName: params.inviter_name,
|
||||
};
|
||||
|
||||
var payload = {
|
||||
const payload = {
|
||||
action: 'view_room',
|
||||
event_id: eventId,
|
||||
third_party_invite: third_party_invite,
|
||||
oob_data: oob_data,
|
||||
third_party_invite: thirdPartyInvite,
|
||||
oob_data: oobData,
|
||||
};
|
||||
if (roomString[0] == '#') {
|
||||
payload.room_alias = roomString;
|
||||
@ -962,19 +979,18 @@ module.exports = React.createClass({
|
||||
dis.dispatch(payload);
|
||||
}
|
||||
} else if (screen.indexOf('user/') == 0) {
|
||||
var userId = screen.substring(5);
|
||||
const userId = screen.substring(5);
|
||||
this.setState({ viewUserId: userId });
|
||||
this._setPage(PageTypes.UserView);
|
||||
this.notifyNewScreen('user/' + userId);
|
||||
var member = new Matrix.RoomMember(null, userId);
|
||||
const member = new Matrix.RoomMember(null, userId);
|
||||
if (member) {
|
||||
dis.dispatch({
|
||||
action: 'view_user',
|
||||
member: member,
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
console.info("Ignoring showScreen for '%s'", screen);
|
||||
}
|
||||
},
|
||||
@ -993,7 +1009,7 @@ module.exports = React.createClass({
|
||||
onUserClick: function(event, userId) {
|
||||
event.preventDefault();
|
||||
|
||||
var member = new Matrix.RoomMember(null, userId);
|
||||
const member = new Matrix.RoomMember(null, userId);
|
||||
if (!member) { return; }
|
||||
dis.dispatch({
|
||||
action: 'view_user',
|
||||
@ -1003,17 +1019,17 @@ module.exports = React.createClass({
|
||||
|
||||
onLogoutClick: function(event) {
|
||||
dis.dispatch({
|
||||
action: 'logout'
|
||||
action: 'logout',
|
||||
});
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
handleResize: function(e) {
|
||||
var hideLhsThreshold = 1000;
|
||||
var showLhsThreshold = 1000;
|
||||
var hideRhsThreshold = 820;
|
||||
var showRhsThreshold = 820;
|
||||
const hideLhsThreshold = 1000;
|
||||
const showLhsThreshold = 1000;
|
||||
const hideRhsThreshold = 820;
|
||||
const showRhsThreshold = 820;
|
||||
|
||||
if (this.state.width > hideLhsThreshold && window.innerWidth <= hideLhsThreshold) {
|
||||
dis.dispatch({ action: 'hide_left_panel' });
|
||||
@ -1031,10 +1047,10 @@ module.exports = React.createClass({
|
||||
this.setState({width: window.innerWidth});
|
||||
},
|
||||
|
||||
onRoomCreated: function(room_id) {
|
||||
onRoomCreated: function(roomId) {
|
||||
dis.dispatch({
|
||||
action: "view_room",
|
||||
room_id: room_id,
|
||||
room_id: roomId,
|
||||
});
|
||||
},
|
||||
|
||||
@ -1068,7 +1084,7 @@ module.exports = React.createClass({
|
||||
onFinishPostRegistration: function() {
|
||||
// Don't confuse this with "PageType" which is the middle window to show
|
||||
this.setState({
|
||||
screen: undefined
|
||||
screen: undefined,
|
||||
});
|
||||
this.showScreen("settings");
|
||||
},
|
||||
@ -1083,10 +1099,10 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
updateStatusIndicator: function(state, prevState) {
|
||||
var notifCount = 0;
|
||||
let notifCount = 0;
|
||||
|
||||
var rooms = MatrixClientPeg.get().getRooms();
|
||||
for (var i = 0; i < rooms.length; ++i) {
|
||||
const rooms = MatrixClientPeg.get().getRooms();
|
||||
for (let i = 0; i < rooms.length; ++i) {
|
||||
if (rooms[i].hasMembershipState(MatrixClientPeg.get().credentials.userId, 'invite')) {
|
||||
notifCount++;
|
||||
} else if (rooms[i].getUnreadNotificationCount()) {
|
||||
@ -1113,19 +1129,18 @@ module.exports = React.createClass({
|
||||
action: 'view_room',
|
||||
room_id: this.state.currentRoomId,
|
||||
});
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
dis.dispatch({
|
||||
action: 'view_room_directory',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onRoomIdResolved: function(room_id) {
|
||||
onRoomIdResolved: function(roomId) {
|
||||
// It's the RoomView's resposibility to look up room aliases, but we need the
|
||||
// ID to pass into things like the Member List, so the Room View tells us when
|
||||
// its done that resolution so we can display things that take a room ID.
|
||||
this.setState({currentRoomId: room_id});
|
||||
this.setState({currentRoomId: roomId});
|
||||
},
|
||||
|
||||
_makeRegistrationUrl: function(params) {
|
||||
@ -1148,14 +1163,20 @@ module.exports = React.createClass({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// needs to be before normal PageTypes as you are logged in technically
|
||||
else if (this.state.screen == 'post_registration') {
|
||||
if (this.state.screen == 'post_registration') {
|
||||
const PostRegistration = sdk.getComponent('structures.login.PostRegistration');
|
||||
return (
|
||||
<PostRegistration
|
||||
onComplete={this.onFinishPostRegistration} />
|
||||
);
|
||||
} else if (this.state.loggedIn && this.state.ready) {
|
||||
}
|
||||
|
||||
// `ready` and `loggedIn` may be set before `page_type` (because the
|
||||
// latter is set via the dispatcher). If we don't yet have a `page_type`,
|
||||
// keep showing the spinner for now.
|
||||
if (this.state.loggedIn && this.state.ready && this.state.page_type) {
|
||||
/* for now, we stuff the entirety of our props and state into the LoggedInView.
|
||||
* we should go through and figure out what we actually need to pass down, as well
|
||||
* as using something like redux to avoid having a billion bits of state kicking around.
|
||||
@ -1237,5 +1258,5 @@ module.exports = React.createClass({
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@ -271,6 +271,7 @@ module.exports = React.createClass({
|
||||
|
||||
this._updateConfCallNotification();
|
||||
|
||||
window.addEventListener('beforeunload', this.onPageUnload);
|
||||
window.addEventListener('resize', this.onResize);
|
||||
this.onResize();
|
||||
|
||||
@ -353,6 +354,7 @@ module.exports = React.createClass({
|
||||
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
||||
}
|
||||
|
||||
window.removeEventListener('beforeunload', this.onPageUnload);
|
||||
window.removeEventListener('resize', this.onResize);
|
||||
|
||||
document.removeEventListener("keydown", this.onKeyDown);
|
||||
@ -365,6 +367,17 @@ module.exports = React.createClass({
|
||||
// Tinter.tint(); // reset colourscheme
|
||||
},
|
||||
|
||||
onPageUnload(event) {
|
||||
if (ContentMessages.getCurrentUploads().length > 0) {
|
||||
return event.returnValue =
|
||||
'You seem to be uploading files, are you sure you want to quit?';
|
||||
} else if (this._getCallForRoom() && this.state.callState !== 'ended') {
|
||||
return event.returnValue =
|
||||
'You seem to be in a call, are you sure you want to quit?';
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
onKeyDown: function(ev) {
|
||||
let handled = false;
|
||||
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
||||
@ -1759,6 +1772,7 @@ module.exports = React.createClass({
|
||||
oobData={this.props.oobData}
|
||||
editing={this.state.editingRoomSettings}
|
||||
saving={this.state.uploadingRoomSettings}
|
||||
inRoom={myMember && myMember.membership === 'join'}
|
||||
collapsedRhs={ this.props.collapsedRhs }
|
||||
onSearchClick={this.onSearchClick}
|
||||
onSettingsClick={this.onSettingsClick}
|
||||
|
@ -177,8 +177,8 @@ var TimelinePanel = React.createClass({
|
||||
componentWillMount: function() {
|
||||
debuglog("TimelinePanel: mounting");
|
||||
|
||||
this.last_rr_sent_event_id = undefined;
|
||||
this.last_rm_sent_event_id = undefined;
|
||||
this.lastRRSentEventId = undefined;
|
||||
this.lastRMSentEventId = undefined;
|
||||
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
|
||||
@ -504,12 +504,13 @@ var TimelinePanel = React.createClass({
|
||||
// very possible have logged out within that timeframe, so check
|
||||
// we still have a client.
|
||||
const cli = MatrixClientPeg.get();
|
||||
// if no client or client is guest don't send RR
|
||||
// if no client or client is guest don't send RR or RM
|
||||
if (!cli || cli.isGuest()) return;
|
||||
|
||||
var currentReadUpToEventId = this._getCurrentReadReceipt(true);
|
||||
var currentReadUpToEventIndex = this._indexForEventId(currentReadUpToEventId);
|
||||
let shouldSendRR = true;
|
||||
|
||||
const currentRREventId = this._getCurrentReadReceipt(true);
|
||||
const currentRREventIndex = this._indexForEventId(currentRREventId);
|
||||
// We want to avoid sending out read receipts when we are looking at
|
||||
// events in the past which are before the latest RR.
|
||||
//
|
||||
@ -523,43 +524,60 @@ var TimelinePanel = React.createClass({
|
||||
// RRs) - but that is a bit of a niche case. It will sort itself out when
|
||||
// the user eventually hits the live timeline.
|
||||
//
|
||||
if (currentReadUpToEventId && currentReadUpToEventIndex === null &&
|
||||
if (currentRREventId && currentRREventIndex === null &&
|
||||
this._timelineWindow.canPaginate(EventTimeline.FORWARDS)) {
|
||||
return;
|
||||
shouldSendRR = false;
|
||||
}
|
||||
|
||||
var lastReadEventIndex = this._getLastDisplayedEventIndex({
|
||||
ignoreOwn: true
|
||||
const lastReadEventIndex = this._getLastDisplayedEventIndex({
|
||||
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
|
||||
// same one at the server repeatedly
|
||||
if ((lastReadEventIndex > currentReadUpToEventIndex &&
|
||||
this.last_rr_sent_event_id != lastReadEvent.getId()) ||
|
||||
this.last_rm_sent_event_id != this.state.readMarkerEventId) {
|
||||
|
||||
this.last_rr_sent_event_id = lastReadEvent.getId();
|
||||
this.last_rm_sent_event_id = this.state.readMarkerEventId;
|
||||
if (shouldSendRR || shouldSendRM) {
|
||||
if (shouldSendRR) {
|
||||
this.lastRRSentEventId = lastReadEvent.getId();
|
||||
} else {
|
||||
lastReadEvent = null;
|
||||
}
|
||||
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(
|
||||
this.props.timelineSet.room.roomId,
|
||||
this.state.readMarkerEventId,
|
||||
lastReadEvent
|
||||
lastReadEvent, // Could be null, in which case no RR is sent
|
||||
).catch((e) => {
|
||||
// /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(
|
||||
lastReadEvent
|
||||
lastReadEvent,
|
||||
).catch(() => {
|
||||
this.last_rr_sent_event_id = undefined;
|
||||
this.lastRRSentEventId = undefined;
|
||||
});
|
||||
}
|
||||
// it failed, so allow retries next time the user is active
|
||||
this.last_rr_sent_event_id = undefined;
|
||||
this.last_rm_sent_event_id = undefined;
|
||||
this.lastRRSentEventId = undefined;
|
||||
this.lastRMSentEventId = undefined;
|
||||
});
|
||||
|
||||
// do a quick-reset of our unreadNotificationCount to avoid having
|
||||
@ -572,7 +590,6 @@ var TimelinePanel = React.createClass({
|
||||
this.props.timelineSet.room.setUnreadNotificationCount('highlight', 0);
|
||||
dis.dispatch({
|
||||
action: 'on_room_read',
|
||||
room: this.props.timelineSet.room,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ const Email = require('../../email');
|
||||
const AddThreepid = require('../../AddThreepid');
|
||||
const SdkConfig = require('../../SdkConfig');
|
||||
import AccessibleButton from '../views/elements/AccessibleButton';
|
||||
import * as FormattingUtils from '../../utils/FormattingUtils';
|
||||
|
||||
// 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
|
||||
@ -37,7 +38,7 @@ const REACT_SDK_VERSION = 'dist' in packageJson ? packageJson.version : packageJ
|
||||
|
||||
// Simple method to help prettify GH Release Tags and Commit Hashes.
|
||||
const semVerRegex = /^v?(\d+\.\d+\.\d+(?:-rc.+)?)(?:-(?:\d+-g)?([0-9a-fA-F]+))?(?:-dirty)?$/i;
|
||||
const gHVersionLabel = function(repo, token) {
|
||||
const gHVersionLabel = function(repo, token='') {
|
||||
const match = token.match(semVerRegex);
|
||||
let url;
|
||||
if (match && match[1]) { // basic semVer string possibly with commit hash
|
||||
@ -47,7 +48,7 @@ const gHVersionLabel = function(repo, token) {
|
||||
} else {
|
||||
url = `https://github.com/${repo}/commit/${token.split('-')[0]}`;
|
||||
}
|
||||
return <a href={url}>{token}</a>;
|
||||
return <a target="_blank" rel="noopener" href={url}>{token}</a>;
|
||||
};
|
||||
|
||||
// Enumerate some simple 'flip a bit' UI settings (if any).
|
||||
@ -151,10 +152,10 @@ module.exports = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
avatarUrl: null,
|
||||
threePids: [],
|
||||
threepids: [],
|
||||
phase: "UserSettings.LOADING", // LOADING, DISPLAY
|
||||
email_add_pending: false,
|
||||
vectorVersion: null,
|
||||
vectorVersion: undefined,
|
||||
rejectingInvites: false,
|
||||
mediaDevices: null,
|
||||
};
|
||||
@ -618,7 +619,12 @@ module.exports = React.createClass({
|
||||
_renderCryptoInfo: function() {
|
||||
const client = MatrixClientPeg.get();
|
||||
const deviceId = client.deviceId;
|
||||
const identityKey = client.getDeviceEd25519Key() || "<not supported>";
|
||||
let identityKey = client.getDeviceEd25519Key();
|
||||
if (!identityKey) {
|
||||
identityKey = "<not supported>";
|
||||
} else {
|
||||
identityKey = FormattingUtils.formatCryptoKey(identityKey);
|
||||
}
|
||||
|
||||
let importExportButtons = null;
|
||||
|
||||
@ -930,6 +936,7 @@ module.exports = React.createClass({
|
||||
addEmailSection = (
|
||||
<div className="mx_UserSettings_profileTableRow" key="_newEmail">
|
||||
<div className="mx_UserSettings_profileLabelCell">
|
||||
<label>Email</label>
|
||||
</div>
|
||||
<div className="mx_UserSettings_profileInputCell">
|
||||
<EditableText
|
||||
@ -1080,7 +1087,7 @@ module.exports = React.createClass({
|
||||
? gHVersionLabel('matrix-org/matrix-react-sdk', REACT_SDK_VERSION)
|
||||
: REACT_SDK_VERSION
|
||||
}<br/>
|
||||
riot-web version: {(this.state.vectorVersion !== null)
|
||||
riot-web version: {(this.state.vectorVersion !== undefined)
|
||||
? gHVersionLabel('vector-im/riot-web', this.state.vectorVersion)
|
||||
: 'unknown'
|
||||
}<br/>
|
||||
|
@ -47,19 +47,7 @@ export default React.createClass({
|
||||
children: React.PropTypes.node,
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.priorActiveElement = document.activeElement;
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
if (this.priorActiveElement !== null) {
|
||||
this.priorActiveElement.focus();
|
||||
}
|
||||
},
|
||||
|
||||
// Must be when the key is released (and not pressed) otherwise componentWillUnmount
|
||||
// will focus another element which will receive future key events
|
||||
_onKeyUp: function(e) {
|
||||
_onKeyDown: function(e) {
|
||||
if (e.keyCode === KeyCode.ESCAPE) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
@ -79,9 +67,9 @@ export default React.createClass({
|
||||
|
||||
render: function() {
|
||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
|
||||
|
||||
return (
|
||||
<div onKeyUp={this._onKeyUp} className={this.props.className}>
|
||||
<div onKeyDown={this._onKeyDown} className={this.props.className}>
|
||||
<AccessibleButton onClick={this._onCancelClick}
|
||||
className="mx_Dialog_cancelButton"
|
||||
>
|
||||
|
76
src/components/views/dialogs/DeviceVerifyDialog.js
Normal file
76
src/components/views/dialogs/DeviceVerifyDialog.js
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import sdk from '../../../index';
|
||||
import * as FormattingUtils from '../../../utils/FormattingUtils';
|
||||
|
||||
export default function DeviceVerifyDialog(props) {
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
|
||||
const key = FormattingUtils.formatCryptoKey(props.device.getFingerprint());
|
||||
const body = (
|
||||
<div>
|
||||
<p>
|
||||
To verify that this device can be trusted, please contact its
|
||||
owner using some other means (e.g. in person or a phone call)
|
||||
and ask them whether the key they see in their User Settings
|
||||
for this device matches the key below:
|
||||
</p>
|
||||
<div className="mx_UserSettings_cryptoSection">
|
||||
<ul>
|
||||
<li><label>Device name:</label> <span>{ props.device.getDisplayName() }</span></li>
|
||||
<li><label>Device ID:</label> <span><code>{ props.device.deviceId}</code></span></li>
|
||||
<li><label>Device key:</label> <span><code><b>{ key }</b></code></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
If it matches, press the verify button below.
|
||||
If it doesnt, then someone else is intercepting this device
|
||||
and you probably want to press the blacklist button instead.
|
||||
</p>
|
||||
<p>
|
||||
In future this verification process will be more sophisticated.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
function onFinished(confirm) {
|
||||
if (confirm) {
|
||||
MatrixClientPeg.get().setDeviceVerified(
|
||||
props.userId, props.device.deviceId, true,
|
||||
);
|
||||
}
|
||||
props.onFinished(confirm);
|
||||
}
|
||||
|
||||
return (
|
||||
<QuestionDialog
|
||||
title="Verify device"
|
||||
description={body}
|
||||
button="I verify that the keys match"
|
||||
onFinished={onFinished}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
DeviceVerifyDialog.propTypes = {
|
||||
userId: React.PropTypes.string.isRequired,
|
||||
device: React.PropTypes.object.isRequired,
|
||||
onFinished: React.PropTypes.func.isRequired,
|
||||
};
|
@ -47,12 +47,6 @@ export default React.createClass({
|
||||
this.props.onFinished(false);
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
if (this.props.focus) {
|
||||
this.refs.button.focus();
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
const cancelButton = this.props.hasCancelButton ? (
|
||||
@ -69,7 +63,7 @@ export default React.createClass({
|
||||
{this.props.description}
|
||||
</div>
|
||||
<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}
|
||||
</button>
|
||||
{this.props.extraButtons}
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@ -138,7 +139,7 @@ export default React.createClass({
|
||||
onClick={this.onClick.bind(this, i)}
|
||||
onMouseEnter={this.onMouseEnter.bind(this, i)}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
key={this.props.addressList[i].userId}
|
||||
key={this.props.addressList[i].addressType + "/" + this.props.addressList[i].address}
|
||||
ref={(ref) => { this.addressListElement = ref; }}
|
||||
>
|
||||
<AddressTile address={this.props.addressList[i]} justified={true} networkName="vector" networkUrl="img/search-icon-vector.svg" />
|
||||
|
@ -50,42 +50,10 @@ export default React.createClass({
|
||||
},
|
||||
|
||||
onVerifyClick: function() {
|
||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Verify device",
|
||||
description: (
|
||||
<div>
|
||||
<p>
|
||||
To verify that this device can be trusted, please contact its
|
||||
owner using some other means (e.g. in person or a phone call)
|
||||
and ask them whether the key they see in their User Settings
|
||||
for this device matches the key below:
|
||||
</p>
|
||||
<div className="mx_UserSettings_cryptoSection">
|
||||
<ul>
|
||||
<li><label>Device name:</label> <span>{ this.state.device.getDisplayName() }</span></li>
|
||||
<li><label>Device ID:</label> <span><code>{ this.state.device.deviceId}</code></span></li>
|
||||
<li><label>Device key:</label> <span><code><b>{ this.state.device.getFingerprint() }</b></code></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
If it matches, press the verify button below.
|
||||
If it doesnt, then someone else is intercepting this device
|
||||
and you probably want to press the blacklist button instead.
|
||||
</p>
|
||||
<p>
|
||||
In future this verification process will be more sophisticated.
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
button: "I verify that the keys match",
|
||||
onFinished: confirm=>{
|
||||
if (confirm) {
|
||||
MatrixClientPeg.get().setDeviceVerified(
|
||||
this.props.userId, this.state.device.deviceId, true
|
||||
);
|
||||
}
|
||||
},
|
||||
const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog');
|
||||
Modal.createDialog(DeviceVerifyDialog, {
|
||||
userId: this.props.userId,
|
||||
device: this.state.device,
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -93,7 +93,7 @@ export default class DirectorySearchBox extends React.Component {
|
||||
className="mx_DirectorySearchBox_input"
|
||||
ref={this._collectInput}
|
||||
onChange={this._onChange} onKeyUp={this._onKeyUp}
|
||||
placeholder={this.props.placeholder}
|
||||
placeholder={this.props.placeholder} autoFocus
|
||||
/>
|
||||
{join_button}
|
||||
<span className="mx_DirectorySearchBox_clear_wrapper">
|
||||
|
@ -152,10 +152,12 @@ export default class Dropdown extends React.Component {
|
||||
}
|
||||
|
||||
_onInputClick(ev) {
|
||||
this.setState({
|
||||
expanded: !this.state.expanded,
|
||||
});
|
||||
ev.preventDefault();
|
||||
if (!this.state.expanded) {
|
||||
this.setState({
|
||||
expanded: true,
|
||||
});
|
||||
ev.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
_onMenuOptionClick(dropdownKey) {
|
||||
@ -252,7 +254,7 @@ export default class Dropdown extends React.Component {
|
||||
);
|
||||
});
|
||||
if (options.length === 0) {
|
||||
return [<div className="mx_Dropdown_option">
|
||||
return [<div key="0" className="mx_Dropdown_option">
|
||||
No results
|
||||
</div>];
|
||||
}
|
||||
|
@ -269,7 +269,7 @@ module.exports = React.createClass({
|
||||
);
|
||||
});
|
||||
return (
|
||||
<span className="mx_MemberEventListSummary_avatars">
|
||||
<span className="mx_MemberEventListSummary_avatars" onClick={ this._toggleSummary }>
|
||||
{avatars}
|
||||
</span>
|
||||
);
|
||||
|
@ -19,7 +19,6 @@ import React from 'react';
|
||||
import sdk from '../../../index';
|
||||
|
||||
import { COUNTRIES } from '../../../phonenumber';
|
||||
import { charactersToImageNode } from '../../../HtmlUtils';
|
||||
|
||||
const COUNTRIES_BY_ISO2 = new Object(null);
|
||||
for (const c of COUNTRIES) {
|
||||
@ -27,9 +26,14 @@ for (const c of COUNTRIES) {
|
||||
}
|
||||
|
||||
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.iso2 == query.toUpperCase()) return true;
|
||||
if (country.prefix == query) return true;
|
||||
if (country.prefix.indexOf(query) !== -1) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -38,10 +42,11 @@ export default class CountryDropdown extends React.Component {
|
||||
super(props);
|
||||
this._onSearchChange = this._onSearchChange.bind(this);
|
||||
this._onOptionChange = this._onOptionChange.bind(this);
|
||||
this._getShortOption = this._getShortOption.bind(this);
|
||||
|
||||
this.state = {
|
||||
searchQuery: '',
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
@ -64,13 +69,21 @@ export default class CountryDropdown extends React.Component {
|
||||
}
|
||||
|
||||
_flagImgForIso2(iso2) {
|
||||
// Unicode Regional Indicator Symbol letter 'A'
|
||||
const RIS_A = 0x1F1E6;
|
||||
const ASCII_A = 65;
|
||||
return charactersToImageNode(iso2, true,
|
||||
RIS_A + (iso2.charCodeAt(0) - ASCII_A),
|
||||
RIS_A + (iso2.charCodeAt(1) - ASCII_A),
|
||||
);
|
||||
return <img src={`flags/${iso2}.png`}/>;
|
||||
}
|
||||
|
||||
_getShortOption(iso2) {
|
||||
if (!this.props.isSmall) {
|
||||
return undefined;
|
||||
}
|
||||
let countryPrefix;
|
||||
if (this.props.showPrefix) {
|
||||
countryPrefix = '+' + COUNTRIES_BY_ISO2[iso2].prefix;
|
||||
}
|
||||
return <span>
|
||||
{ this._flagImgForIso2(iso2) }
|
||||
{ countryPrefix }
|
||||
</span>;
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -99,7 +112,7 @@ export default class CountryDropdown extends React.Component {
|
||||
const options = displayedCountries.map((country) => {
|
||||
return <div key={country.iso2}>
|
||||
{this._flagImgForIso2(country.iso2)}
|
||||
{country.name}
|
||||
{country.name} <span>(+{country.prefix})</span>
|
||||
</div>;
|
||||
});
|
||||
|
||||
@ -107,21 +120,21 @@ export default class CountryDropdown extends React.Component {
|
||||
// values between mounting and the initial value propgating
|
||||
const value = this.props.value || COUNTRIES[0].iso2;
|
||||
|
||||
const getShortOption = this.props.isSmall ? this._flagImgForIso2 : undefined;
|
||||
|
||||
return <Dropdown className={this.props.className}
|
||||
return <Dropdown className={this.props.className + " left_aligned"}
|
||||
onOptionChange={this._onOptionChange} onSearchChange={this._onSearchChange}
|
||||
menuWidth={298} getShortOption={getShortOption}
|
||||
menuWidth={298} getShortOption={this._getShortOption}
|
||||
value={value} searchEnabled={true}
|
||||
>
|
||||
{options}
|
||||
</Dropdown>
|
||||
</Dropdown>;
|
||||
}
|
||||
}
|
||||
|
||||
CountryDropdown.propTypes = {
|
||||
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,
|
||||
value: React.PropTypes.string,
|
||||
};
|
||||
|
@ -149,28 +149,26 @@ class PasswordLogin extends React.Component {
|
||||
</div>;
|
||||
case PasswordLogin.LOGIN_FIELD_PHONE:
|
||||
const CountryDropdown = sdk.getComponent('views.login.CountryDropdown');
|
||||
const prefix = this.state.phonePrefix;
|
||||
return <div className="mx_Login_phoneSection">
|
||||
<CountryDropdown
|
||||
className="mx_Login_phoneCountry"
|
||||
className="mx_Login_phoneCountry mx_Login_field_prefix"
|
||||
ref="phone_country"
|
||||
onOptionChange={this.onPhoneCountryChanged}
|
||||
value={this.state.phoneCountry}
|
||||
isSmall={true}
|
||||
showPrefix={true}
|
||||
/>
|
||||
<input
|
||||
className="mx_Login_phoneNumberField mx_Login_field mx_Login_field_has_prefix"
|
||||
ref="phoneNumber"
|
||||
key="phone_input"
|
||||
type="text"
|
||||
name="phoneNumber"
|
||||
onChange={this.onPhoneNumberChanged}
|
||||
placeholder="Mobile phone number"
|
||||
value={this.state.phoneNumber}
|
||||
autoFocus
|
||||
/>
|
||||
<div className="mx_Login_field_group">
|
||||
<div className="mx_Login_field_prefix">+{prefix}</div>
|
||||
<input
|
||||
className="mx_Login_phoneNumberField mx_Login_field mx_Login_field_has_prefix"
|
||||
ref="phoneNumber"
|
||||
key="phone_input"
|
||||
type="text"
|
||||
name="phoneNumber"
|
||||
onChange={this.onPhoneNumberChanged}
|
||||
placeholder="Mobile phone number"
|
||||
value={this.state.phoneNumber}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
@ -314,24 +314,23 @@ module.exports = React.createClass({
|
||||
const phoneSection = (
|
||||
<div className="mx_Login_phoneSection">
|
||||
<CountryDropdown ref="phone_country" onOptionChange={this._onPhoneCountryChange}
|
||||
className="mx_Login_phoneCountry"
|
||||
className="mx_Login_phoneCountry mx_Login_field_prefix"
|
||||
value={this.state.phoneCountry}
|
||||
isSmall={true}
|
||||
showPrefix={true}
|
||||
/>
|
||||
<input type="text" ref="phoneNumber"
|
||||
placeholder="Mobile phone number (optional)"
|
||||
defaultValue={this.props.defaultPhoneNumber}
|
||||
className={this._classForField(
|
||||
FIELD_PHONE_NUMBER,
|
||||
'mx_Login_phoneNumberField',
|
||||
'mx_Login_field',
|
||||
'mx_Login_field_has_prefix'
|
||||
)}
|
||||
onBlur={function() {self.validateField(FIELD_PHONE_NUMBER);}}
|
||||
value={self.state.phoneNumber}
|
||||
/>
|
||||
<div className="mx_Login_field_group">
|
||||
<div className="mx_Login_field_prefix">+{this.state.phonePrefix}</div>
|
||||
<input type="text" ref="phoneNumber"
|
||||
placeholder="Mobile phone number (optional)"
|
||||
defaultValue={this.props.defaultPhoneNumber}
|
||||
className={this._classForField(
|
||||
FIELD_PHONE_NUMBER,
|
||||
'mx_Login_phoneNumberField',
|
||||
'mx_Login_field',
|
||||
'mx_Login_field_has_prefix'
|
||||
)}
|
||||
onBlur={function() {self.validateField(FIELD_PHONE_NUMBER);}}
|
||||
value={self.state.phoneNumber}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
@ -295,16 +295,6 @@ module.exports = WithMatrixClient(React.createClass({
|
||||
const receiptOffset = 15;
|
||||
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 || [];
|
||||
for (var i = 0; i < receipts.length; ++i) {
|
||||
var receipt = receipts[i];
|
||||
@ -340,7 +330,6 @@ module.exports = WithMatrixClient(React.createClass({
|
||||
suppressAnimation={this._suppressReadReceiptAnimation}
|
||||
onClick={this.toggleAllReadAvatars}
|
||||
timestamp={receipt.ts}
|
||||
showFullTimestamp={receipt.ts >= dayAfterEventTime}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -492,22 +481,22 @@ module.exports = WithMatrixClient(React.createClass({
|
||||
var e2e;
|
||||
// cosmetic padlocks:
|
||||
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
|
||||
else if (this.props.mxEvent.isEncrypted() || (e2eEnabled && this.props.eventSendStatus)) {
|
||||
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)) {
|
||||
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 {
|
||||
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) {
|
||||
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() ?
|
||||
<MessageTimestamp ts={this.props.mxEvent.getTs()} /> : null;
|
||||
|
@ -100,7 +100,9 @@ module.exports = React.createClass({
|
||||
|
||||
render: function() {
|
||||
var p = this.state.preview;
|
||||
if (!p) return <div/>;
|
||||
if (!p || Object.keys(p).length === 0) {
|
||||
return <div/>;
|
||||
}
|
||||
|
||||
// FIXME: do we want to factor out all image displaying between this and MImageBody - especially for lightboxing?
|
||||
var image = p["og:image"];
|
||||
|
@ -717,8 +717,16 @@ module.exports = WithMatrixClient(React.createClass({
|
||||
|
||||
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 PowerSelector = sdk.getComponent('elements.PowerSelector');
|
||||
var PresenceLabel = sdk.getComponent('rooms.PresenceLabel');
|
||||
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||
return (
|
||||
<div className="mx_MemberInfo">
|
||||
@ -736,6 +744,11 @@ module.exports = WithMatrixClient(React.createClass({
|
||||
<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>
|
||||
</div>
|
||||
<div className="mx_MemberInfo_profileField">
|
||||
<PresenceLabel activeAgo={ presenceLastActiveAgo }
|
||||
currentlyActive={ presenceCurrentlyActive }
|
||||
presenceState={ presenceState } />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ adminTools }
|
||||
|
@ -33,6 +33,7 @@ export default class MessageComposer extends React.Component {
|
||||
this.onHangupClick = this.onHangupClick.bind(this);
|
||||
this.onUploadClick = this.onUploadClick.bind(this);
|
||||
this.onUploadFileSelected = this.onUploadFileSelected.bind(this);
|
||||
this.uploadFiles = this.uploadFiles.bind(this);
|
||||
this.onVoiceCallClick = this.onVoiceCallClick.bind(this);
|
||||
this.onInputContentChanged = this.onInputContentChanged.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.onInputStateChanged = this.onInputStateChanged.bind(this);
|
||||
this.onEvent = this.onEvent.bind(this);
|
||||
this.onPageUnload = this.onPageUnload.bind(this);
|
||||
|
||||
this.state = {
|
||||
autocompleteQuery: '',
|
||||
@ -64,12 +66,21 @@ export default class MessageComposer extends React.Component {
|
||||
// marked as encrypted.
|
||||
// XXX: fragile as all hell - fixme somehow, perhaps with a dedicated Room.encryption event or something.
|
||||
MatrixClientPeg.get().on("event", this.onEvent);
|
||||
|
||||
window.addEventListener('beforeunload', this.onPageUnload);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (MatrixClientPeg.get()) {
|
||||
MatrixClientPeg.get().removeListener("event", this.onEvent);
|
||||
}
|
||||
window.removeEventListener('beforeunload', this.onPageUnload);
|
||||
}
|
||||
|
||||
onPageUnload(event) {
|
||||
if (this.messageComposerInput) {
|
||||
this.messageComposerInput.sentHistory.saveLastTextEntry();
|
||||
}
|
||||
}
|
||||
|
||||
onEvent(event) {
|
||||
@ -91,10 +102,11 @@ export default class MessageComposer extends React.Component {
|
||||
this.refs.uploadInput.click();
|
||||
}
|
||||
|
||||
onUploadFileSelected(files, isPasted) {
|
||||
if (!isPasted)
|
||||
files = files.target.files;
|
||||
onUploadFileSelected(files) {
|
||||
this.uploadFiles(files.target.files);
|
||||
}
|
||||
|
||||
uploadFiles(files) {
|
||||
let QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
let TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
|
||||
@ -300,7 +312,7 @@ export default class MessageComposer extends React.Component {
|
||||
tryComplete={this._tryComplete}
|
||||
onUpArrow={this.onUpArrow}
|
||||
onDownArrow={this.onDownArrow}
|
||||
onUploadFileSelected={this.onUploadFileSelected}
|
||||
onFilesPasted={this.uploadFiles}
|
||||
tabComplete={this.props.tabComplete} // used for old messagecomposerinput/tabcomplete
|
||||
onContentChanged={this.onInputContentChanged}
|
||||
onInputStateChanged={this.onInputStateChanged} />,
|
||||
|
@ -84,7 +84,6 @@ export default class MessageComposerInput extends React.Component {
|
||||
this.onAction = this.onAction.bind(this);
|
||||
this.handleReturn = this.handleReturn.bind(this);
|
||||
this.handleKeyCommand = this.handleKeyCommand.bind(this);
|
||||
this.handlePastedFiles = this.handlePastedFiles.bind(this);
|
||||
this.onEditorContentChanged = this.onEditorContentChanged.bind(this);
|
||||
this.setEditorState = this.setEditorState.bind(this);
|
||||
this.onUpArrow = this.onUpArrow.bind(this);
|
||||
@ -477,10 +476,6 @@ export default class MessageComposerInput extends React.Component {
|
||||
return false;
|
||||
}
|
||||
|
||||
handlePastedFiles(files) {
|
||||
this.props.onUploadFileSelected(files, true);
|
||||
}
|
||||
|
||||
handleReturn(ev) {
|
||||
if (ev.shiftKey) {
|
||||
this.onEditorContentChanged(RichUtils.insertSoftNewline(this.state.editorState));
|
||||
@ -542,9 +537,9 @@ export default class MessageComposerInput extends React.Component {
|
||||
let sendTextFn = this.client.sendTextMessage;
|
||||
|
||||
if (contentText.startsWith('/me')) {
|
||||
contentText = contentText.replace('/me ', '');
|
||||
contentText = contentText.substring(4);
|
||||
// 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;
|
||||
sendTextFn = this.client.sendEmoteMessage;
|
||||
}
|
||||
@ -734,7 +729,7 @@ export default class MessageComposerInput extends React.Component {
|
||||
keyBindingFn={MessageComposerInput.getKeyBinding}
|
||||
handleKeyCommand={this.handleKeyCommand}
|
||||
handleReturn={this.handleReturn}
|
||||
handlePastedFiles={this.handlePastedFiles}
|
||||
handlePastedFiles={this.props.onFilesPasted}
|
||||
stripPastedStyles={!this.state.isRichtextEnabled}
|
||||
onTab={this.onTab}
|
||||
onUpArrow={this.onUpArrow}
|
||||
@ -764,7 +759,7 @@ MessageComposerInput.propTypes = {
|
||||
|
||||
onDownArrow: React.PropTypes.func,
|
||||
|
||||
onUploadFileSelected: React.PropTypes.func,
|
||||
onFilesPasted: React.PropTypes.func,
|
||||
|
||||
// attempts to confirm currently selected completion, returns whether actually confirmed
|
||||
tryComplete: React.PropTypes.func,
|
||||
|
@ -69,6 +69,9 @@ export default React.createClass({
|
||||
|
||||
// The text to use a placeholder in the input box
|
||||
placeholder: React.PropTypes.string.isRequired,
|
||||
|
||||
// callback to handle files pasted into the composer
|
||||
onFilesPasted: React.PropTypes.func,
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
@ -439,10 +442,27 @@ export default React.createClass({
|
||||
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() {
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ var sdk = require('../../../index');
|
||||
var Velociraptor = require('../../../Velociraptor');
|
||||
require('../../../VelocityBounce');
|
||||
|
||||
import DateUtils from '../../../DateUtils';
|
||||
|
||||
var bounce = false;
|
||||
try {
|
||||
if (global.localStorage) {
|
||||
@ -63,9 +65,6 @@ module.exports = React.createClass({
|
||||
|
||||
// Timestamp when the receipt was read
|
||||
timestamp: React.PropTypes.number,
|
||||
|
||||
// True to show the full date/time rather than just the time
|
||||
showFullTimestamp: React.PropTypes.bool,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
@ -170,16 +169,8 @@ module.exports = React.createClass({
|
||||
|
||||
let title;
|
||||
if (this.props.timestamp) {
|
||||
const prefix = "Seen by " + this.props.member.userId + " at ";
|
||||
let ts = 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();
|
||||
}
|
||||
title = "Seen by " + this.props.member.userId + " at " +
|
||||
DateUtils.formatDate(new Date(this.props.timestamp));
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var classNames = require('classnames');
|
||||
var sdk = require('../../../index');
|
||||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||
var Modal = require("../../../Modal");
|
||||
@ -39,6 +40,7 @@ module.exports = React.createClass({
|
||||
oobData: React.PropTypes.object,
|
||||
editing: React.PropTypes.bool,
|
||||
saving: React.PropTypes.bool,
|
||||
inRoom: React.PropTypes.bool,
|
||||
collapsedRhs: React.PropTypes.bool,
|
||||
onSettingsClick: React.PropTypes.func,
|
||||
onSaveClick: React.PropTypes.func,
|
||||
@ -49,7 +51,7 @@ module.exports = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
editing: false,
|
||||
onSettingsClick: function() {},
|
||||
inRoom: false,
|
||||
onSaveClick: function() {},
|
||||
};
|
||||
},
|
||||
@ -225,10 +227,10 @@ module.exports = React.createClass({
|
||||
roomName = this.props.room.name;
|
||||
}
|
||||
|
||||
|
||||
const emojiTextClasses = classNames('mx_RoomHeader_nametext', { mx_RoomHeader_settingsHint: settingsHint });
|
||||
name =
|
||||
<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 }
|
||||
</div>;
|
||||
}
|
||||
@ -299,6 +301,14 @@ module.exports = React.createClass({
|
||||
</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;
|
||||
if (this.props.collapsedRhs) {
|
||||
rightPanel_buttons =
|
||||
@ -313,9 +323,7 @@ module.exports = React.createClass({
|
||||
<div className="mx_RoomHeader_rightRow">
|
||||
{ settings_button }
|
||||
{ forget_button }
|
||||
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onSearchClick} title="Search">
|
||||
<TintableSvg src="img/icons-search.svg" width="35" height="35"/>
|
||||
</AccessibleButton>
|
||||
{ search_button }
|
||||
{ rightPanel_buttons }
|
||||
</div>;
|
||||
}
|
||||
|
@ -21,13 +21,13 @@ var GeminiScrollbar = require('react-gemini-scrollbar');
|
||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||
var CallHandler = require('../../../CallHandler');
|
||||
var RoomListSorter = require("../../../RoomListSorter");
|
||||
var Unread = require('../../../Unread');
|
||||
var dis = require("../../../dispatcher");
|
||||
var sdk = require('../../../index');
|
||||
var rate_limited_func = require('../../../ratelimitedfunc');
|
||||
var Rooms = require('../../../Rooms');
|
||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||
var Receipt = require('../../../utils/Receipt');
|
||||
var constantTimeDispatcher = require('../../../ConstantTimeDispatcher');
|
||||
|
||||
var HIDE_CONFERENCE_CHANS = true;
|
||||
|
||||
@ -37,19 +37,10 @@ module.exports = React.createClass({
|
||||
propTypes: {
|
||||
ConferenceHandler: React.PropTypes.any,
|
||||
collapsed: React.PropTypes.bool.isRequired,
|
||||
selectedRoom: React.PropTypes.string,
|
||||
currentRoom: 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() {
|
||||
return {
|
||||
isLoadingLeftRooms: false,
|
||||
@ -59,6 +50,8 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.mounted = false;
|
||||
|
||||
var cli = MatrixClientPeg.get();
|
||||
cli.on("Room", this.onRoom);
|
||||
cli.on("deleteRoom", this.onDeleteRoom);
|
||||
@ -66,46 +59,23 @@ module.exports = React.createClass({
|
||||
cli.on("Room.name", this.onRoomName);
|
||||
cli.on("Room.tags", this.onRoomTags);
|
||||
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("accountData", this.onAccountData);
|
||||
|
||||
// lookup for which lists a given roomId is currently in.
|
||||
this.listsForRoomId = {};
|
||||
|
||||
var s = this.getRoomLists();
|
||||
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() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
// Initialise the stickyHeaders when the component is created
|
||||
this._updateStickyHeaders(true);
|
||||
|
||||
this.mounted = true;
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
// 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) {
|
||||
componentDidUpdate: function() {
|
||||
// Reinitialise the stickyHeaders when the component is updated
|
||||
this._updateStickyHeaders(true);
|
||||
this._repositionIncomingCallBox(undefined, false);
|
||||
@ -131,29 +101,17 @@ module.exports = React.createClass({
|
||||
}
|
||||
break;
|
||||
case 'on_room_read':
|
||||
// poke the right RoomTile to refresh, using the constantTimeDispatcher
|
||||
// to avoid each and every RoomTile registering to the 'on_room_read' event
|
||||
// XXX: if we like the constantTimeDispatcher we might want to dispatch
|
||||
// directly from TimelinePanel rather than needlessly bouncing via here.
|
||||
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 }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Force an update because the notif count state is too deep to cause
|
||||
// an update. This forces the local echo of reading notifs to be
|
||||
// reflected by the RoomTiles.
|
||||
this.forceUpdate();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this.mounted = false;
|
||||
|
||||
dis.unregister(this.dispatcherRef);
|
||||
if (MatrixClientPeg.get()) {
|
||||
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.tags", this.onRoomTags);
|
||||
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("accountData", this.onAccountData);
|
||||
}
|
||||
@ -171,14 +129,10 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
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();
|
||||
},
|
||||
|
||||
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();
|
||||
},
|
||||
|
||||
@ -201,10 +155,6 @@ module.exports = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
_onMouseOver: function(ev) {
|
||||
this._lastMouseOverTs = Date.now();
|
||||
},
|
||||
|
||||
onSubListHeaderClick: function(isHidden, scrollToPosition) {
|
||||
// The scroll area has expanded or contracted, so re-calculate sticky headers positions
|
||||
this._updateStickyHeaders(true, scrollToPosition);
|
||||
@ -214,98 +164,41 @@ module.exports = React.createClass({
|
||||
if (toStartOfTimeline) return;
|
||||
if (!room) return;
|
||||
if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return;
|
||||
|
||||
// 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, {}
|
||||
);
|
||||
this._delayedRefreshRoomList();
|
||||
},
|
||||
|
||||
onRoomReceipt: function(receiptEvent, room) {
|
||||
// because if we read a notification, it will affect notification count
|
||||
// only bother updating if there's a receipt from us
|
||||
if (Receipt.findReadReceiptFromUserId(receiptEvent, MatrixClientPeg.get().credentials.userId)) {
|
||||
var lists = this.listsForRoomId[room.roomId];
|
||||
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, {}
|
||||
);
|
||||
this._delayedRefreshRoomList();
|
||||
}
|
||||
},
|
||||
|
||||
onRoomName: function(room) {
|
||||
constantTimeDispatcher.dispatch(
|
||||
"RoomTile.refresh", room.roomId, {}
|
||||
);
|
||||
},
|
||||
|
||||
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();
|
||||
},
|
||||
|
||||
onRoomStateMember: function(ev, state, member) {
|
||||
if (ev.getStateKey() === MatrixClientPeg.get().credentials.userId &&
|
||||
ev.getPrevContent() && ev.getPrevContent().membership === "invite")
|
||||
{
|
||||
this._delayedRefreshRoomList();
|
||||
}
|
||||
else {
|
||||
constantTimeDispatcher.dispatch(
|
||||
"RoomTile.refresh", member.roomId, {}
|
||||
);
|
||||
}
|
||||
onRoomTags: function(event, room) {
|
||||
this._delayedRefreshRoomList();
|
||||
},
|
||||
|
||||
onRoomStateEvents: function(ev, state) {
|
||||
this._delayedRefreshRoomList();
|
||||
},
|
||||
|
||||
onRoomMemberName: function(ev, member) {
|
||||
constantTimeDispatcher.dispatch(
|
||||
"RoomTile.refresh", member.roomId, {}
|
||||
);
|
||||
this._delayedRefreshRoomList();
|
||||
},
|
||||
|
||||
onAccountData: function(ev) {
|
||||
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();
|
||||
}
|
||||
},
|
||||
|
||||
_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._delayedRefreshRoomListLoopCount = 0;
|
||||
}
|
||||
else {
|
||||
this._delayedRefreshRoomListLoopCount++;
|
||||
this._delayedRefreshRoomList();
|
||||
}
|
||||
this.refreshRoomList();
|
||||
}, 500),
|
||||
|
||||
refreshRoomList: function() {
|
||||
@ -313,10 +206,12 @@ module.exports = React.createClass({
|
||||
// (!this._lastRefreshRoomListTs ? "-" : (Date.now() - this._lastRefreshRoomListTs))
|
||||
// );
|
||||
|
||||
// TODO: ideally we'd calculate this once at start, and then maintain
|
||||
// any changes to it incrementally, updating the appropriate sublists
|
||||
// as needed.
|
||||
// Alternatively we'd do something magical with Immutable.js or similar.
|
||||
// TODO: rather than bluntly regenerating and re-sorting everything
|
||||
// every time we see any kind of room change from the JS SDK
|
||||
// we could do incremental updates on our copy of the state
|
||||
// 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._lastRefreshRoomListTs = Date.now();
|
||||
@ -333,9 +228,6 @@ module.exports = React.createClass({
|
||||
s.lists["m.lowpriority"] = [];
|
||||
s.lists["im.vector.fake.archived"] = [];
|
||||
|
||||
this.listsForRoomId = {};
|
||||
var otherTagNames = {};
|
||||
|
||||
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
|
||||
|
||||
MatrixClientPeg.get().getRooms().forEach(function(room) {
|
||||
@ -347,12 +239,7 @@ module.exports = React.createClass({
|
||||
// ", target = " + me.events.member.getStateKey() +
|
||||
// ", prevMembership = " + me.events.member.getPrevContent().membership);
|
||||
|
||||
if (!self.listsForRoomId[room.roomId]) {
|
||||
self.listsForRoomId[room.roomId] = [];
|
||||
}
|
||||
|
||||
if (me.membership == "invite") {
|
||||
self.listsForRoomId[room.roomId].push("im.vector.fake.invite");
|
||||
s.lists["im.vector.fake.invite"].push(room);
|
||||
}
|
||||
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
|
||||
var tagNames = Object.keys(room.tags);
|
||||
|
||||
if (tagNames.length) {
|
||||
for (var i = 0; i < tagNames.length; i++) {
|
||||
var tagName = tagNames[i];
|
||||
s.lists[tagName] = s.lists[tagName] || [];
|
||||
s.lists[tagName].push(room);
|
||||
self.listsForRoomId[room.roomId].push(tagName);
|
||||
otherTagNames[tagName] = 1;
|
||||
s.lists[tagNames[i]].push(room);
|
||||
}
|
||||
}
|
||||
else if (dmRoomMap.getUserIdForRoomId(room.roomId)) {
|
||||
// "Direct Message" rooms (that we're still in and that aren't otherwise tagged)
|
||||
self.listsForRoomId[room.roomId].push("im.vector.fake.direct");
|
||||
s.lists["im.vector.fake.direct"].push(room);
|
||||
}
|
||||
else {
|
||||
self.listsForRoomId[room.roomId].push("im.vector.fake.recent");
|
||||
s.lists["im.vector.fake.recent"].push(room);
|
||||
}
|
||||
}
|
||||
else if (me.membership === "leave") {
|
||||
self.listsForRoomId[room.roomId].push("im.vector.fake.archived");
|
||||
s.lists["im.vector.fake.archived"].push(room);
|
||||
}
|
||||
else {
|
||||
@ -391,34 +274,58 @@ module.exports = React.createClass({
|
||||
}
|
||||
});
|
||||
|
||||
// we actually apply the sorting to this when receiving the prop in RoomSubLists.
|
||||
if (s.lists["im.vector.fake.direct"].length == 0 &&
|
||||
MatrixClientPeg.get().getAccountData('m.direct') === undefined &&
|
||||
!MatrixClientPeg.get().isGuest())
|
||||
{
|
||||
// scan through the 'recents' list for any rooms which look like DM rooms
|
||||
// and make them DM rooms
|
||||
const oldRecents = s.lists["im.vector.fake.recent"];
|
||||
s.lists["im.vector.fake.recent"] = [];
|
||||
|
||||
// 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"
|
||||
];
|
||||
*/
|
||||
for (const room of oldRecents) {
|
||||
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||
|
||||
if (me && Rooms.looksLikeDirectMessageRoom(room, me)) {
|
||||
s.lists["im.vector.fake.direct"].push(room);
|
||||
} else {
|
||||
s.lists["im.vector.fake.recent"].push(room);
|
||||
}
|
||||
}
|
||||
|
||||
// save these new guessed DM rooms into the account data
|
||||
const newMDirectEvent = {};
|
||||
for (const room of s.lists["im.vector.fake.direct"]) {
|
||||
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||
const otherPerson = Rooms.getOnlyOtherMember(room, me);
|
||||
if (!otherPerson) continue;
|
||||
|
||||
const roomList = newMDirectEvent[otherPerson.userId] || [];
|
||||
roomList.push(room.roomId);
|
||||
newMDirectEvent[otherPerson.userId] = roomList;
|
||||
}
|
||||
|
||||
// if this fails, fine, we'll just do the same thing next time we get the room lists
|
||||
MatrixClientPeg.get().setAccountData('m.direct', newMDirectEvent).done();
|
||||
}
|
||||
|
||||
//console.log("calculated new roomLists; im.vector.fake.recent = " + s.lists["im.vector.fake.recent"]);
|
||||
|
||||
// we actually apply the sorting to this when receiving the prop in RoomSubLists.
|
||||
|
||||
return s;
|
||||
},
|
||||
|
||||
_getScrollNode: function() {
|
||||
if (!this.mounted) return null;
|
||||
var panel = ReactDOM.findDOMNode(this);
|
||||
if (!panel) return null;
|
||||
|
||||
// empirically, if we have gm-prevented for some reason, the scroll node
|
||||
// is still the 3rd child (i.e. the view child). This looks to be due
|
||||
// to vdh's improved resize updater logic...?
|
||||
return panel.children[2]; // XXX: Fragile!
|
||||
if (panel.classList.contains('gm-prevented')) {
|
||||
return panel;
|
||||
} else {
|
||||
return panel.children[2]; // XXX: Fragile!
|
||||
}
|
||||
},
|
||||
|
||||
_whenScrolling: function(e) {
|
||||
@ -438,6 +345,7 @@ module.exports = React.createClass({
|
||||
var incomingCallBox = document.getElementById("incomingCallBox");
|
||||
if (incomingCallBox && incomingCallBox.parentElement) {
|
||||
var scrollArea = this._getScrollNode();
|
||||
if (!scrollArea) return;
|
||||
// Use the offset of the top of the scroll area from the window
|
||||
// as this is used to calculate the CSS fixed top position for the stickies
|
||||
var scrollAreaOffset = scrollArea.getBoundingClientRect().top + window.pageYOffset;
|
||||
@ -461,10 +369,11 @@ module.exports = React.createClass({
|
||||
// properly through React
|
||||
_initAndPositionStickyHeaders: function(initialise, scrollToPosition) {
|
||||
var scrollArea = this._getScrollNode();
|
||||
if (!scrollArea) return;
|
||||
// Use the offset of the top of the scroll area from the window
|
||||
// as this is used to calculate the CSS fixed top position for the stickies
|
||||
var scrollAreaOffset = scrollArea.getBoundingClientRect().top + window.pageYOffset;
|
||||
// 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
|
||||
var scrollAreaHeight = ReactDOM.findDOMNode(this).getBoundingClientRect().height;
|
||||
|
||||
@ -564,16 +473,15 @@ module.exports = React.createClass({
|
||||
|
||||
return (
|
||||
<GeminiScrollbar className="mx_RoomList_scrollbar"
|
||||
autoshow={true} onScroll={ self._whenScrolling } onResize={ self._whenScrolling } ref="gemscroll">
|
||||
<div className="mx_RoomList" onMouseOver={ this._onMouseOver }>
|
||||
autoshow={true} onScroll={ self._whenScrolling } ref="gemscroll">
|
||||
<div className="mx_RoomList">
|
||||
<RoomSubList list={ self.state.lists['im.vector.fake.invite'] }
|
||||
label="Invites"
|
||||
tagName="im.vector.fake.invite"
|
||||
editable={ false }
|
||||
order="recent"
|
||||
selectedRoom={ self.props.selectedRoom }
|
||||
incomingCall={ self.state.incomingCall }
|
||||
collapsed={ self.props.collapsed }
|
||||
selectedRoom={ self.props.selectedRoom }
|
||||
searchFilter={ self.props.searchFilter }
|
||||
onHeaderClick={ self.onSubListHeaderClick }
|
||||
onShowMoreRooms={ self.onShowMoreRooms } />
|
||||
@ -584,9 +492,9 @@ module.exports = React.createClass({
|
||||
verb="favourite"
|
||||
editable={ true }
|
||||
order="manual"
|
||||
selectedRoom={ self.props.selectedRoom }
|
||||
incomingCall={ self.state.incomingCall }
|
||||
collapsed={ self.props.collapsed }
|
||||
selectedRoom={ self.props.selectedRoom }
|
||||
searchFilter={ self.props.searchFilter }
|
||||
onHeaderClick={ self.onSubListHeaderClick }
|
||||
onShowMoreRooms={ self.onShowMoreRooms } />
|
||||
@ -597,9 +505,9 @@ module.exports = React.createClass({
|
||||
verb="tag direct chat"
|
||||
editable={ true }
|
||||
order="recent"
|
||||
selectedRoom={ self.props.selectedRoom }
|
||||
incomingCall={ self.state.incomingCall }
|
||||
collapsed={ self.props.collapsed }
|
||||
selectedRoom={ self.props.selectedRoom }
|
||||
alwaysShowHeader={ true }
|
||||
searchFilter={ self.props.searchFilter }
|
||||
onHeaderClick={ self.onSubListHeaderClick }
|
||||
@ -607,18 +515,17 @@ module.exports = React.createClass({
|
||||
|
||||
<RoomSubList list={ self.state.lists['im.vector.fake.recent'] }
|
||||
label="Rooms"
|
||||
tagName="im.vector.fake.recent"
|
||||
editable={ true }
|
||||
verb="restore"
|
||||
order="recent"
|
||||
selectedRoom={ self.props.selectedRoom }
|
||||
incomingCall={ self.state.incomingCall }
|
||||
collapsed={ self.props.collapsed }
|
||||
selectedRoom={ self.props.selectedRoom }
|
||||
searchFilter={ self.props.searchFilter }
|
||||
onHeaderClick={ self.onSubListHeaderClick }
|
||||
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))$/)) {
|
||||
return <RoomSubList list={ self.state.lists[tagName] }
|
||||
key={ tagName }
|
||||
@ -627,9 +534,9 @@ module.exports = React.createClass({
|
||||
verb={ "tag as " + tagName }
|
||||
editable={ true }
|
||||
order="manual"
|
||||
selectedRoom={ self.props.selectedRoom }
|
||||
incomingCall={ self.state.incomingCall }
|
||||
collapsed={ self.props.collapsed }
|
||||
selectedRoom={ self.props.selectedRoom }
|
||||
searchFilter={ self.props.searchFilter }
|
||||
onHeaderClick={ self.onSubListHeaderClick }
|
||||
onShowMoreRooms={ self.onShowMoreRooms } />;
|
||||
@ -643,20 +550,19 @@ module.exports = React.createClass({
|
||||
verb="demote"
|
||||
editable={ true }
|
||||
order="recent"
|
||||
selectedRoom={ self.props.selectedRoom }
|
||||
incomingCall={ self.state.incomingCall }
|
||||
collapsed={ self.props.collapsed }
|
||||
selectedRoom={ self.props.selectedRoom }
|
||||
searchFilter={ self.props.searchFilter }
|
||||
onHeaderClick={ self.onSubListHeaderClick }
|
||||
onShowMoreRooms={ self.onShowMoreRooms } />
|
||||
|
||||
<RoomSubList list={ self.state.lists['im.vector.fake.archived'] }
|
||||
label="Historical"
|
||||
tagName="im.vector.fake.archived"
|
||||
editable={ false }
|
||||
order="recent"
|
||||
collapsed={ self.props.collapsed }
|
||||
selectedRoom={ self.props.selectedRoom }
|
||||
collapsed={ self.props.collapsed }
|
||||
alwaysShowHeader={ true }
|
||||
startAsHidden={ true }
|
||||
showSpinner={ self.state.isLoadingLeftRooms }
|
||||
|
@ -47,7 +47,7 @@ module.exports = React.createClass({
|
||||
// The alias that was used to access this room, if appropriate
|
||||
// If given, this will be how the room is referred to (eg.
|
||||
// in error messages).
|
||||
roomAlias: React.PropTypes.object,
|
||||
roomAlias: React.PropTypes.string,
|
||||
},
|
||||
|
||||
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}/>
|
||||
</div>
|
||||
<div className="mx_RoomSettings_powerLevel">
|
||||
<span className="mx_RoomSettings_powerLevelKey">To redact messages, you must be a </span>
|
||||
<span className="mx_RoomSettings_powerLevelKey">To redact other users' messages, you must be a </span>
|
||||
<PowerSelector ref="redact" value={redact_level} controlled={false} disabled={!can_change_levels || current_user_level < redact_level} onChange={this.onPowerLevelsChanged}/>
|
||||
</div>
|
||||
|
||||
|
@ -27,8 +27,6 @@ var RoomNotifs = require('../../../RoomNotifs');
|
||||
var FormattingUtils = require('../../../utils/FormattingUtils');
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
var UserSettingsStore = require('../../../UserSettingsStore');
|
||||
var constantTimeDispatcher = require('../../../ConstantTimeDispatcher');
|
||||
var Unread = require('../../../Unread');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'RoomTile',
|
||||
@ -38,10 +36,12 @@ module.exports = React.createClass({
|
||||
connectDropTarget: React.PropTypes.func,
|
||||
onClick: React.PropTypes.func,
|
||||
isDragging: React.PropTypes.bool,
|
||||
selectedRoom: React.PropTypes.string,
|
||||
|
||||
room: React.PropTypes.object.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,
|
||||
incomingCall: React.PropTypes.object,
|
||||
},
|
||||
@ -54,11 +54,10 @@ module.exports = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
return({
|
||||
hover: false,
|
||||
badgeHover: false,
|
||||
hover : false,
|
||||
badgeHover : false,
|
||||
menuDisplayed: false,
|
||||
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() {
|
||||
constantTimeDispatcher.register("RoomTile.refresh", this.props.room.roomId, this.onRefresh);
|
||||
constantTimeDispatcher.register("RoomTile.select", this.props.room.roomId, this.onSelect);
|
||||
this.onRefresh();
|
||||
MatrixClientPeg.get().on("accountData", this.onAccountData);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
constantTimeDispatcher.unregister("RoomTile.refresh", this.props.room.roomId, this.onRefresh);
|
||||
constantTimeDispatcher.unregister("RoomTile.select", this.props.room.roomId, this.onSelect);
|
||||
},
|
||||
|
||||
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,
|
||||
});
|
||||
var cli = MatrixClientPeg.get();
|
||||
if (cli) {
|
||||
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
||||
}
|
||||
},
|
||||
|
||||
onClick: function(ev) {
|
||||
@ -179,13 +169,13 @@ module.exports = React.createClass({
|
||||
// var highlightCount = this.props.room.getUnreadNotificationCount("highlight");
|
||||
|
||||
const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge();
|
||||
const mentionBadges = this.state.highlight && this._shouldShowMentionBadge();
|
||||
const mentionBadges = this.props.highlight && this._shouldShowMentionBadge();
|
||||
const badges = notifBadges || mentionBadges;
|
||||
|
||||
var classes = classNames({
|
||||
'mx_RoomTile': true,
|
||||
'mx_RoomTile_selected': this.state.selected,
|
||||
'mx_RoomTile_unread': this.state.unread,
|
||||
'mx_RoomTile_selected': this.props.selected,
|
||||
'mx_RoomTile_unread': this.props.unread,
|
||||
'mx_RoomTile_unreadNotify': notifBadges,
|
||||
'mx_RoomTile_highlight': mentionBadges,
|
||||
'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,
|
||||
});
|
||||
|
||||
if (this.state.selected) {
|
||||
if (this.props.selected) {
|
||||
let nameSelected = <EmojiText>{name}</EmojiText>;
|
||||
|
||||
label = <div title={ name } className={ nameClasses }>{ nameSelected }</div>;
|
||||
@ -265,8 +255,7 @@ module.exports = React.createClass({
|
||||
|
||||
let ret = (
|
||||
<div> { /* Only native elements can be wrapped in a DnD object. */}
|
||||
<AccessibleButton className={classes} tabIndex="0" onClick={this.onClick}
|
||||
onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||
<AccessibleButton className={classes} tabIndex="0" onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||
<div className={avatarClasses}>
|
||||
<div className="mx_RoomTile_avatar_container">
|
||||
<RoomAvatar room={this.props.room} width={24} height={24} />
|
||||
|
@ -38,7 +38,7 @@ module.exports = React.createClass({
|
||||
title="Scroll to unread messages"/>
|
||||
Jump to first unread message.
|
||||
</div>
|
||||
<img className="mx_TopUnreadMessagesBar_close"
|
||||
<img className="mx_TopUnreadMessagesBar_close mx_filterFlipColor"
|
||||
src="img/cancel.svg" width="18" height="18"
|
||||
alt="Close" title="Close"
|
||||
onClick={this.props.onCloseClick} />
|
||||
|
@ -147,6 +147,7 @@ export default WithMatrixClient(React.createClass({
|
||||
return (
|
||||
<form className="mx_UserSettings_profileTableRow" onSubmit={this._onAddMsisdnSubmit}>
|
||||
<div className="mx_UserSettings_profileLabelCell">
|
||||
<label>Phone</label>
|
||||
</div>
|
||||
<div className="mx_UserSettings_profileInputCell">
|
||||
<div className="mx_UserSettings_phoneSection">
|
||||
|
@ -26,3 +26,14 @@ export function formatCount(count) {
|
||||
if (count < 100000000) return (count / 1000000).toFixed(0) + "M";
|
||||
return (count / 1000000000).toFixed(1) + "B"; // 10B is enough for anyone, right? :S
|
||||
}
|
||||
|
||||
/**
|
||||
* format a key into groups of 4 characters, for easier visual inspection
|
||||
*
|
||||
* @param {string} key key to format
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
export function formatCryptoKey(key) {
|
||||
return key.match(/.{1,4}/g).join(" ");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user