mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-15 20:54:59 +08:00
commit
711272a7c9
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,4 +1,2 @@
|
||||
node_modules
|
||||
build
|
||||
bundle.css
|
||||
bundle.js
|
||||
lib
|
||||
|
@ -1,3 +1,3 @@
|
||||
example
|
||||
examples
|
||||
build/.module-cache
|
||||
.module-cache
|
||||
|
110
README.md
110
README.md
@ -2,58 +2,29 @@ matrix-react-sdk
|
||||
================
|
||||
|
||||
This is a react-based SDK for inserting a Matrix chat/voip client into a web page.
|
||||
It provides reusable and customisable UI components backed by the matrix-js-sdk.
|
||||
|
||||
Getting started with the trivial example
|
||||
========================================
|
||||
This package provides the logic and 'controller' parts for the UI components. This
|
||||
forms one part of a complete matrix client, but it not useable in isolation. It
|
||||
must be used from a 'skin'. A skin provides:
|
||||
* The HTML for the UI components (in the form of React `render` methods)
|
||||
* The CSS for this HTML
|
||||
* The containing application
|
||||
* Zero or more 'modules' containing non-UI functionality
|
||||
|
||||
1. Install or update `node.js` so that your `npm` is at least at version `2.0.0`
|
||||
2. Clone the repo: `git clone https://github.com/matrix-org/matrix-react-sdk.git`
|
||||
3. Switch to the SDK directory: `cd matrix-react-sdk`
|
||||
4. Install the prerequisites: `npm install`
|
||||
5. Switch to the example directory: `cd examples/trivial`
|
||||
6. Install the example app prerequisites: `npm install`
|
||||
7. Build the example and start a server: `npm start`
|
||||
Skins are modules are exported from such a package in the `lib` directory.
|
||||
`lib/skins` contains one directory per-skin, named after the skin, and the
|
||||
`modules` directory contains modules as their javascript files.
|
||||
|
||||
Now open http://127.0.0.1:8080/ in your browser to see your newly built
|
||||
Matrix client.
|
||||
A basic skin is provided in the matrix-react-skin package. This also contains
|
||||
a minimal application that instantiates the basic skin making a working matrix
|
||||
client.
|
||||
|
||||
Using the example app for development
|
||||
=====================================
|
||||
|
||||
To work on the CSS and Javascript and have the bundle files update as you
|
||||
change the source files, you'll need to do two extra things:
|
||||
|
||||
1. Link the react sdk package into the example:
|
||||
`cd matrix-react-sdk/examples/trivial; npm link ../../`
|
||||
2. Start a watcher for the CSS files:
|
||||
`cd matrix-react-sdk; npm run start:css`
|
||||
|
||||
Note that you may need to restart the CSS builder if you add a new file. Note
|
||||
that `npm start` builds debug versions of the javascript and CSS, which are
|
||||
much larger than the production versions build by the `npm run build` commands.
|
||||
|
||||
IMPORTANT: If you customise components in your application (and hence require
|
||||
react from your app) you must be sure to:
|
||||
|
||||
1. Make your app depend on react directly
|
||||
2. If you `npm link` matrix-react-sdk, manually remove the 'react' directory
|
||||
from matrix-react-sdk's `node_modules` folder, otherwise browserify will
|
||||
pull in both copies of react which causes the app to break.
|
||||
You can use matrix-react-sdk directly, but to do this you would have to provide
|
||||
'views' for each UI component. To get started quickly, use matrix-react-skin.
|
||||
|
||||
How to customise the SDK
|
||||
========================
|
||||
|
||||
The matrix-react-sdk provides well-defined reusable UI components which may be
|
||||
customised/replaced by the developer to build into an app. A set of consistent
|
||||
UI components (View + CSS classes) is called a 'skin' - currently the SDK
|
||||
provides a very vanilla whitelabelled 'base skin'. In future the SDK could
|
||||
provide alternative skins (probably by extending the base skin) that provide more
|
||||
specific look and feels (e.g. "IRC-style", "Skype-style") etc. However, unlike
|
||||
Wordpress themes and similar, we don't normally expect app developers to define
|
||||
reusable skins. Instead you just go and incorporate your view customisations
|
||||
into your actual app.
|
||||
|
||||
The SDK uses the 'atomic' design pattern as seen at http://patternlab.io to
|
||||
encourage a very modular and reusable architecture, making it easy to
|
||||
customise and use UI widgets independently of the rest of the SDK and your app.
|
||||
@ -131,18 +102,41 @@ components to embed a Matrix client into your app:
|
||||
* Create a new NPM project. Be sure to directly depend on react, (otherwise
|
||||
you can end up with two copies of react).
|
||||
* Create an index.js file that sets up react. Add require statements for
|
||||
React, the ComponentBroker and matrix-react-sdk and a call to Render
|
||||
the root React element as in the examples.
|
||||
* Create React classes for any custom components you wish to add. These
|
||||
can be based off the files in `views` in the `matrix-react-sdk` package,
|
||||
modifying the require() statement appropriately.
|
||||
You only need to copy files you want to customise.
|
||||
* Add a ComponentBroker.set() call for each of your custom components. These
|
||||
must come *before* `require("matrix-react-sdk")`.
|
||||
* Add a way to build your project: we suggest copying the browserify calls
|
||||
from the example projects, but you could use grunt or gulp.
|
||||
* Create an index.html file pulling in your compiled index.js file, the
|
||||
CSS bundle from matrix-react-sdk.
|
||||
React and matrix-react-sdk. Load a skin using the 'loadSkin' method on the
|
||||
SDK and call Render. This can be a skin provided by a separate package or
|
||||
a skin in the same package.
|
||||
* Add a way to build your project: we suggest copying the scripts block
|
||||
from matrix-react-skin (which uses babel and webpack). You could use
|
||||
different tools but remember that at least the skins and modules of
|
||||
your project should end up in plain (ie. non ES6, non JSX) javascript in
|
||||
the lib directory at the end of the build process, as well as any
|
||||
packaging that you might do.
|
||||
* Create an index.html file pulling in your compiled javascript and the
|
||||
CSS bundle from the skin you use. For now, you'll also need to manually
|
||||
import CSS from any skins that your skin inherts from.
|
||||
|
||||
For more specific detail on any of these steps, look at the `custom` example in
|
||||
matrix-react-sdk/examples.
|
||||
To Create Your Own Skin
|
||||
=======================
|
||||
To actually change the look of a skin, you can create a base skin (which
|
||||
does not use views from any other skin) or you can make a derived skin.
|
||||
Note that derived skins are currently experimental: for example, the CSS
|
||||
from the skins it is based on will not be automatically included.
|
||||
|
||||
To make a skin, create React classes for any custom components you wish to add
|
||||
in a skin within `src/skins/<skin name>`. These can be based off the files in
|
||||
`views` in the `matrix-react-skin` package, modifying the require() statement
|
||||
appropriately.
|
||||
|
||||
If you make a derived skin, you only need copy the files you wish to customise.
|
||||
|
||||
Once you've made all your view files, you need to make a `skinfo.json`. This
|
||||
contains all the metadata for a skin. This is a JSON file with, currently, a
|
||||
single key, 'baseSkin'. Set this to the empty string if your skin is a base skin,
|
||||
or for a derived skin, set it to the path of your base skin's skinfo.json file, as
|
||||
you would use in a require call.
|
||||
|
||||
Now you have the basis of a skin, you need to generate a skindex.json file. The
|
||||
`reskindex.js` tool in matrix-react-sdk does this for you. It is suggested that
|
||||
you add an npm script to run this, as in matrix-react-skin.
|
||||
|
||||
For more specific detail on any of these steps, look at matrix-react-skin.
|
||||
|
@ -1,40 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var MTextTileController = require("matrix-react-sdk/src/controllers/molecules/MTextTile");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MTextTile',
|
||||
mixins: [MTextTileController],
|
||||
|
||||
render: function() {
|
||||
var content = this.props.mxEvent.getContent();
|
||||
return (
|
||||
<span ref="content" className="mx_MTextTile mx_MessageTile_content" onClick={this.onClick}>
|
||||
{content.body}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
|
||||
onClick: function(ev) {
|
||||
global.alert(this.props.mxEvent.getContent().body);
|
||||
}
|
||||
});
|
||||
|
@ -1,4 +0,0 @@
|
||||
matrix-react-example
|
||||
====================
|
||||
|
||||
An example of how to use the Matrix React SDK to build a more customised app
|
@ -1,12 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en" style="height: 100%; overflow: hidden">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Matrix React SDK Custom Example</title>
|
||||
</head>
|
||||
<body style="height: 100%; ">
|
||||
<section id="matrixchat" style="height: 100%; "></section>
|
||||
<script src="bundle.js"></script>
|
||||
<link rel="stylesheet" href="node_modules/matrix-react-sdk/bundle.css">
|
||||
</body>
|
||||
</html>
|
@ -1,40 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Remember to make your project depend on react directly as soon as
|
||||
// you add a require('react') to any file in your project. Do not rely
|
||||
// on react being pulled in via matrix-react-sdk: browserify breaks
|
||||
// horribly in this situation and can end up pulling in multiple copies
|
||||
// of react.
|
||||
var React = require("react");
|
||||
|
||||
// We pull in the component broker first, separately, as we need to replace
|
||||
// components before the SDK loads.
|
||||
var ComponentBroker = require("matrix-react-sdk/src/ComponentBroker");
|
||||
|
||||
var CustomMTextTile = require('./CustomMTextTile');
|
||||
|
||||
ComponentBroker.set('molecules/MTextTile', CustomMTextTile);
|
||||
|
||||
var MatrixReactSdk = require("matrix-react-sdk");
|
||||
//var MatrixReactSdk = require("../../src/index");
|
||||
|
||||
React.render(
|
||||
<MatrixReactSdk.MatrixChat />,
|
||||
document.getElementById('matrixchat')
|
||||
);
|
@ -1,29 +0,0 @@
|
||||
{
|
||||
"name": "matrix-react-example",
|
||||
"version": "0.0.1",
|
||||
"description": "Example usage of matrix-react-sdk",
|
||||
"author": "matrix.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/matrix-org/matrix-react-sdk"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"browserify": "^10.2.3",
|
||||
"envify": "^3.4.0",
|
||||
"http-server": "^0.8.0",
|
||||
"matrix-react-sdk": "../../",
|
||||
"npm-css": "^0.2.3",
|
||||
"parallelshell": "^1.2.0",
|
||||
"reactify": "^1.1.1",
|
||||
"uglify-js": "^2.4.23",
|
||||
"watchify": "^3.2.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "browserify -t [ envify --NODE_ENV production ] -g reactify index.js | uglifyjs -c -m -o bundle.js",
|
||||
"start": "parallelshell 'watchify -v -d -g reactify index.js -o bundle.js' 'http-server'"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^0.13.3"
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
matrix-react-example
|
||||
====================
|
||||
|
||||
A simple example of how to use the Matrix React SDK
|
@ -1,12 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en" style="height: 100%; overflow: hidden">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Matrix React SDK Example</title>
|
||||
</head>
|
||||
<body style="height: 100%;">
|
||||
<section id="matrixchat" style="height: 100%;"></section>
|
||||
<script src="bundle.js"></script>
|
||||
<link rel="stylesheet" href="node_modules/matrix-react-sdk/bundle.css">
|
||||
</body>
|
||||
</html>
|
@ -1,75 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require("react");
|
||||
// In normal usage of the module:
|
||||
//var MatrixReactSdk = require("matrix-react-sdk");
|
||||
// Or to import the source directly from the file system:
|
||||
// (This is useful for debugging the SDK as it seems source
|
||||
// maps cannot pass through two stages).
|
||||
var MatrixReactSdk = require("../../src/index");
|
||||
|
||||
// Here, we do some crude URL analysis to allow
|
||||
// deep-linking. We only support registration
|
||||
// deep-links in this example.
|
||||
function routeUrl(location) {
|
||||
if (location.hash.indexOf('#/register') == 0) {
|
||||
var hashparts = location.hash.split('?');
|
||||
var params = {};
|
||||
if (hashparts.length == 2) {
|
||||
var pairs = hashparts[1].split('&');
|
||||
for (var i = 0; i < pairs.length; ++i) {
|
||||
var parts = pairs[i].split('=');
|
||||
if (parts.length != 2) continue;
|
||||
params[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]);
|
||||
}
|
||||
}
|
||||
window.matrixChat.showScreen('register', params);
|
||||
}
|
||||
}
|
||||
|
||||
var loaded = false;
|
||||
|
||||
window.onload = function() {
|
||||
routeUrl(window.location);
|
||||
loaded = true;
|
||||
}
|
||||
|
||||
// This will be called whenever the SDK changes screens,
|
||||
// so a web page can update the URL bar appropriately.
|
||||
var onNewScreen = function(screen) {
|
||||
if (!loaded) return;
|
||||
window.location.hash = '#/'+screen;
|
||||
}
|
||||
|
||||
// We use this to work out what URL the SDK should
|
||||
// pass through when registering to allow the user to
|
||||
// click back to the client having registered.
|
||||
// It's up to us to recognise if we're loaded with
|
||||
// this URL and tell MatrixClient to resume registration.
|
||||
var makeRegistrationUrl = function() {
|
||||
return window.location.protocol + '//' +
|
||||
window.location.host +
|
||||
window.location.pathname +
|
||||
'#/register';
|
||||
}
|
||||
|
||||
window.matrixChat = React.render(
|
||||
<MatrixReactSdk.MatrixChat onNewScreen={onNewScreen} registrationUrl={makeRegistrationUrl()} />,
|
||||
document.getElementById('matrixchat')
|
||||
);
|
@ -1,25 +0,0 @@
|
||||
{
|
||||
"name": "matrix-react-example",
|
||||
"version": "0.0.1",
|
||||
"description": "Example usage of matrix-react-sdk",
|
||||
"author": "matrix.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/matrix-org/matrix-react-sdk"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"browserify": "^10.2.3",
|
||||
"envify": "^3.4.0",
|
||||
"http-server": "^0.8.0",
|
||||
"matrix-react-sdk": "../../",
|
||||
"parallelshell": "^1.2.0",
|
||||
"reactify": "^1.1.1",
|
||||
"uglify-js": "^2.4.23",
|
||||
"watchify": "^3.2.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "browserify --ignore olm -t [ envify --NODE_ENV production ] -t reactify index.js | uglifyjs -c -m -o bundle.js",
|
||||
"start": "parallelshell 'watchify --ignore olm -v -d -t reactify index.js -o bundle.js' 'http-server'"
|
||||
}
|
||||
}
|
35
package.json
35
package.json
@ -8,33 +8,34 @@
|
||||
"url": "https://github.com/matrix-org/matrix-react-sdk"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"main": "src/index.js",
|
||||
"style": "bundle.css",
|
||||
"main": "lib/index.js",
|
||||
"bin": {
|
||||
"reskindex": "./reskindex.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build:skins": "jsx skins build/skins",
|
||||
"build:logic": "jsx src build/src",
|
||||
"build:js": "npm run build:skins && npm run build:logic",
|
||||
"start:js": "jsx -w skins/base/views/ build --source-map-inline",
|
||||
"build:css": "catw 'skins/base/css/**/*.css' -o bundle.css -c uglifycss --no-watch",
|
||||
"start:css": "catw 'skins/base/css/**/*.css' -o bundle.css -v",
|
||||
"build": "npm run build:js && npm run build:css",
|
||||
"start": "parallelshell 'npm run start:js' 'npm run start:css'",
|
||||
"build": "babel src -d lib --source-maps",
|
||||
"start": "babel src -w -d lib --source-maps",
|
||||
"clean": "rimraf lib",
|
||||
"prepublish": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.1.2",
|
||||
"filesize": "^3.1.2",
|
||||
"flux": "^2.0.3",
|
||||
"matrix-js-sdk": "0.2.0",
|
||||
"glob": "^5.0.14",
|
||||
"linkifyjs": "^2.0.0-beta.4",
|
||||
"matrix-js-sdk": "^0.2.1",
|
||||
"optimist": "^0.6.1",
|
||||
"q": "^1.4.1",
|
||||
"react": "^0.13.3",
|
||||
"react-loader": "^1.4.0",
|
||||
"linkifyjs": "^2.0.0-beta.4"
|
||||
"react-loader": "^1.4.0"
|
||||
},
|
||||
"//deps": "The loader packages are here because webpack in a project that depends on us needs them in this package's node_modules folder",
|
||||
"//depsbuglink": "https://github.com/webpack/webpack/issues/1472",
|
||||
"devDependencies": {
|
||||
"catw": "^1.0.1",
|
||||
"parallelshell": "^1.1.1",
|
||||
"react-tools": "^0.13.3",
|
||||
"uglifycss": "0.0.15"
|
||||
"babel": "^5.8.23",
|
||||
"rimraf": "^2.4.3",
|
||||
"json-loader": "^0.5.3",
|
||||
"source-map-loader": "^0.1.5"
|
||||
}
|
||||
}
|
||||
|
83
reskindex.js
Executable file
83
reskindex.js
Executable file
@ -0,0 +1,83 @@
|
||||
#!/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;
|
||||
|
||||
if (args._.length == 0) {
|
||||
console.log("No skin given");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var skin = args._[0];
|
||||
|
||||
try {
|
||||
fs.accessSync(path.join('src', 'skins', skin), fs.F_OK);
|
||||
} catch (e) {
|
||||
console.log("Skin "+skin+" not found");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var skinfoFile = path.join('src', 'skins', skin, 'skinfo.json');
|
||||
|
||||
try {
|
||||
fs.accessSync(skinfoFile, fs.F_OK);
|
||||
} catch (e) {
|
||||
console.log("Skin "+skin+" has no skinfo.json");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
fs.accessSync(path.join('src', 'skins', skin, 'views'), fs.F_OK);
|
||||
} catch (e) {
|
||||
console.log("Skin "+skin+" has no views directory");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var skindex = path.join('src', 'skins', skin, 'skindex.js');
|
||||
var viewsDir = path.join('src', 'skins', skin, 'views');
|
||||
|
||||
var strm = fs.createWriteStream(skindex);
|
||||
|
||||
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");
|
||||
|
||||
var mySkinfo = JSON.parse(fs.readFileSync(skinfoFile, "utf8"));
|
||||
|
||||
strm.write("var skin = {};\n");
|
||||
strm.write('\n');
|
||||
|
||||
var files = glob.sync('**/*.js', {cwd: viewsDir});
|
||||
for (var i = 0; i < files.length; ++i) {
|
||||
var file = files[i].replace('.js', '');
|
||||
var module = (file.replace(/\//g, '.'));
|
||||
|
||||
strm.write("skin['"+module+"'] = require('./views/"+file+"');\n");
|
||||
strm.uncork();
|
||||
}
|
||||
|
||||
strm.write("\n");
|
||||
|
||||
if (mySkinfo.baseSkin) {
|
||||
strm.write("module.exports = require('"+mySkinfo.baseSkin+"');");
|
||||
strm.write("var extend = require('matrix-react-sdk/lib/extend');\n");
|
||||
strm.write("extend(module.exports, skin);\n");
|
||||
} else {
|
||||
strm.write("module.exports = skin;");
|
||||
}
|
||||
|
||||
strm.end();
|
||||
|
@ -1,20 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
.mx_MessageTimestamp {
|
||||
display: table-cell;
|
||||
white-space: pre;
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
body {
|
||||
font-family: Helvetica, Arial, Sans-Serif;
|
||||
}
|
||||
|
||||
div.error {
|
||||
color: red;
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
.mx_MessageComposer textarea {
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
.mx_ProgressBar {
|
||||
height: 5px;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.mx_ProgressBar_fill {
|
||||
height: 100%;
|
||||
background-color: #000;
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
.mx_RoomHeader {
|
||||
height: 1em;
|
||||
padding: 0px;
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
.mx_RoomTile {
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_RoomTile.selected {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.mx_RoomTile_name {
|
||||
}
|
||||
|
||||
.mx_RoomTile div {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.mx_RoomTile.unread {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.mx_RoomTile.highlight {
|
||||
background-color: lime;
|
||||
}
|
||||
|
||||
.mx_RoomTile.invited {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.mx_RoomTile:hover {
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
.mx_SenderProfile {
|
||||
display: table-cell;
|
||||
padding: 0px 1em 0em 1em;
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
.mx_RoomView {
|
||||
word-wrap: break-word;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mx_RoomView .mx_RoomHeader {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.mx_RoomView_roomWrapper {
|
||||
display: -webkit-box;
|
||||
display: -moz-box;
|
||||
display: -ms-flexbox;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 32px;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
.mx_RoomView_messagePanel {
|
||||
-webkit-box-ordinal-group: 1;
|
||||
-moz-box-ordinal-group: 1;
|
||||
-ms-flex-order: 1;
|
||||
-webkit-order: 1;
|
||||
order: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* background-color: #ff0; */
|
||||
}
|
||||
|
||||
.mx_RoomView_messageListWrapper {
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.mx_RoomView_MessageList {
|
||||
display: table;
|
||||
}
|
||||
|
||||
.mx_RoomView_MessageList_ul {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.mx_RoomView_invitePrompt {
|
||||
}
|
||||
|
||||
.mx_RoomView .mx_MemberList {
|
||||
-webkit-box-ordinal-group: 2;
|
||||
-moz-box-ordinal-group: 2;
|
||||
-ms-flex-order: 2;
|
||||
-webkit-order: 2;
|
||||
order: 2;
|
||||
|
||||
/* background-color: #0f0; */
|
||||
width: 250px;
|
||||
overflow-y: scroll;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mx_RoomView .mx_MemberList ul {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.mx_RoomView .mx_MessageComposer {
|
||||
width: 100%;
|
||||
bottom: 0px;
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
.mx_MatrixChat {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mx_MatrixChat_chatWrapper {
|
||||
display: -webkit-box;
|
||||
display: -moz-box;
|
||||
display: -ms-flexbox;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 0px;
|
||||
bottom: 42px;
|
||||
}
|
||||
|
||||
.mx_MatrixChat_leftPanel {
|
||||
-webkit-box-ordinal-group: 1;
|
||||
-moz-box-ordinal-group: 1;
|
||||
-ms-flex-order: 1;
|
||||
-webkit-order: 1;
|
||||
order: 1;
|
||||
|
||||
display: -webkit-box;
|
||||
display: -moz-box;
|
||||
display: -ms-flexbox;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
-webkit-flex-direction: column;
|
||||
|
||||
/* background-color: #f00; */
|
||||
width: 250px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mx_MatrixChat_leftPanel .mx_MatrixToolbar {
|
||||
-webkit-box-ordinal-group: 1;
|
||||
-moz-box-ordinal-group: 1;
|
||||
-ms-flex-order: 1;
|
||||
-webkit-order: 1;
|
||||
order: 1;
|
||||
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.mx_MatrixChat_leftPanel .mx_RoomList {
|
||||
-webkit-box-ordinal-group: 2;
|
||||
-moz-box-ordinal-group: 2;
|
||||
-ms-flex-order: 2;
|
||||
-webkit-order: 2;
|
||||
order: 2;
|
||||
|
||||
/* background-color: #0ff; */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.mx_MatrixChat .mx_RoomView {
|
||||
-webkit-box-ordinal-group: 2;
|
||||
-moz-box-ordinal-group: 2;
|
||||
-ms-flex-order: 2;
|
||||
-webkit-order: 2;
|
||||
order: 2;
|
||||
|
||||
/* background-color: #00f; */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
.mx_Login {
|
||||
width: 600px;
|
||||
height: 350px;
|
||||
position: relative;
|
||||
}
|
||||
|
@ -1,68 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var EditableTextController = require("../../../../src/controllers/atoms/EditableText");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'EditableText',
|
||||
mixins: [EditableTextController],
|
||||
|
||||
onKeyUp: function(ev) {
|
||||
if (ev.key == "Enter") {
|
||||
this.onFinish(ev);
|
||||
} else if (ev.key == "Escape") {
|
||||
this.cancelEdit();
|
||||
}
|
||||
},
|
||||
|
||||
onClickDiv: function() {
|
||||
this.setState({
|
||||
phase: this.Phases.Edit,
|
||||
})
|
||||
},
|
||||
|
||||
onFocus: function(ev) {
|
||||
ev.target.setSelectionRange(0, ev.target.value.length);
|
||||
},
|
||||
|
||||
onFinish: function(ev) {
|
||||
this.setValue(ev.target.value);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var editable_el;
|
||||
|
||||
if (this.state.phase == this.Phases.Display) {
|
||||
editable_el = <div ref="display_div" onClick={this.onClickDiv}>{this.state.value}</div>;
|
||||
} else if (this.state.phase == this.Phases.Edit) {
|
||||
editable_el = (
|
||||
<div>
|
||||
<input type="text" defaultValue={this.state.value} onKeyUp={this.onKeyUp} onFocus={this.onFocus} onBlur={this.onFinish} autoFocus/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_EditableText">
|
||||
{editable_el}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
@ -1,38 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var EnableNotificationsButtonController = require("../../../../src/controllers/atoms/EnableNotificationsButton");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'EnableNotificationsButton',
|
||||
mixins: [EnableNotificationsButtonController],
|
||||
|
||||
render: function() {
|
||||
if (this.enabled()) {
|
||||
return (
|
||||
<button className="mx_EnableNotificationsButton" onClick={this.onClick}>Disable Notifications</button>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<button className="mx_EnableNotificationsButton" onClick={this.onClick}>Enable Notifications</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
@ -1,32 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var LogoutButtonController = require("../../../../src/controllers/atoms/LogoutButton");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'LogoutButton',
|
||||
mixins: [LogoutButtonController],
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<button className="mx_LogoutButton" onClick={this.onClick}>Sign out</button>
|
||||
);
|
||||
}
|
||||
});
|
@ -1,36 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var MessageTimestampController = require("../../../../src/controllers/atoms/MessageTimestamp");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MessageTimestamp',
|
||||
mixins: [MessageTimestampController],
|
||||
|
||||
render: function() {
|
||||
var date = new Date(this.props.ts);
|
||||
return (
|
||||
<span className="mx_MessageTimestamp">
|
||||
{date.toLocaleTimeString()}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
@ -1,32 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var CreateRoomButtonController = require("../../../../../src/controllers/atoms/create_room/CreateRoomButton");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'CreateRoomButton',
|
||||
mixins: [CreateRoomButtonController],
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<button className="mx_CreateRoomButton" onClick={this.onClick}>Create Room</button>
|
||||
);
|
||||
}
|
||||
});
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var PresetsController = require("../../../../../src/controllers/atoms/create_room/Presets");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'CreateRoomPresets',
|
||||
mixins: [PresetsController],
|
||||
|
||||
onValueChanged: function(ev) {
|
||||
this.setState({preset: ev.target.value})
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<select className="mx_Presets" onChange={this.onValueChanged} defaultValue={this.state.preset}>
|
||||
<option value="private_chat">Private Chat</option>
|
||||
<option value="public_chat">Public Chat</option>
|
||||
</select>
|
||||
);
|
||||
}
|
||||
});
|
@ -1,36 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var RoomNameTextboxController = require("../../../../../src/controllers/atoms/create_room/RoomNameTextbox");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'RoomNameTextbox',
|
||||
mixins: [RoomNameTextboxController],
|
||||
|
||||
onValueChanged: function(ev) {
|
||||
this.setState({room_name: ev.target.value})
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<input type="text" className="mx_RoomNameTextbox" placeholder="ex. MyNewRoom" onChange={this.onValueChanged}/>
|
||||
);
|
||||
}
|
||||
});
|
@ -1,38 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var MEmoteTileController = require("../../../../src/controllers/molecules/MEmoteTile");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MEmoteTile',
|
||||
mixins: [MEmoteTileController],
|
||||
|
||||
render: function() {
|
||||
var mxEvent = this.props.mxEvent;
|
||||
var content = mxEvent.getContent();
|
||||
var name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender();
|
||||
return (
|
||||
<li className="mx_MEmoteTile mx_MessageTile_content">
|
||||
* {name} {content.body}
|
||||
</li>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var MFileTileController = require("../../../../src/controllers/molecules/MFileTile");
|
||||
|
||||
var MatrixClientPeg = require('../../../../src/MatrixClientPeg');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MFileTile',
|
||||
mixins: [MFileTileController],
|
||||
|
||||
render: function() {
|
||||
var content = this.props.mxEvent.getContent();
|
||||
var cli = MatrixClientPeg.get();
|
||||
|
||||
return (
|
||||
<li className="mx_MFileTile">
|
||||
<a href={cli.mxcUrlToHttp(content.url)} target="_blank">
|
||||
{this.presentableTextForFile(content)}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
},
|
||||
});
|
@ -1,69 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var MImageTileController = require("../../../../src/controllers/molecules/MImageTile");
|
||||
|
||||
var MatrixClientPeg = require('../../../../src/MatrixClientPeg');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MImageTile',
|
||||
mixins: [MImageTileController],
|
||||
|
||||
thumbHeight: function(fullWidth, fullHeight, thumbWidth, thumbHeight) {
|
||||
if (!fullWidth || !fullHeight) {
|
||||
// Cannot calculate thumbnail height for image: missing w/h in metadata. We can't even
|
||||
// log this because it's spammy
|
||||
return undefined;
|
||||
}
|
||||
if (fullWidth < thumbWidth && fullHeight < thumbHeight) {
|
||||
// no scaling needs to be applied
|
||||
return fullHeight;
|
||||
}
|
||||
var widthMulti = thumbWidth / fullWidth;
|
||||
var heightMulti = thumbHeight / fullHeight;
|
||||
if (widthMulti < heightMulti) {
|
||||
// width is the dominant dimension so scaling will be fixed on that
|
||||
return Math.floor(widthMulti * fullHeight);
|
||||
}
|
||||
else {
|
||||
// height is the dominant dimension so scaling will be fixed on that
|
||||
return Math.floor(heightMulti * fullHeight);
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var content = this.props.mxEvent.getContent();
|
||||
var cli = MatrixClientPeg.get();
|
||||
|
||||
var thumbHeight = null;
|
||||
if (content.info) thumbHeight = this.thumbHeight(content.info.w, content.info.h, 320, 240);
|
||||
|
||||
var imgStyle = {};
|
||||
if (thumbHeight) imgStyle['height'] = thumbHeight;
|
||||
|
||||
return (
|
||||
<li className="mx_MImageTile">
|
||||
<a href={cli.mxcUrlToHttp(content.url)} target="_blank">
|
||||
<img src={cli.mxcUrlToHttp(content.url, 320, 240)} alt={content.body} style={imgStyle} />
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
},
|
||||
});
|
@ -1,36 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var MNoticeTileController = require("../../../../src/controllers/molecules/MNoticeTile");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MNoticeTile',
|
||||
mixins: [MNoticeTileController],
|
||||
|
||||
render: function() {
|
||||
var content = this.props.mxEvent.getContent();
|
||||
return (
|
||||
<span ref="content" className="mx_MNoticeTile mx_MessageTile_content">
|
||||
{content.body}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
@ -1,57 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var MRoomMemberTileController = require("../../../../src/controllers/molecules/MRoomMemberTile");
|
||||
|
||||
var ComponentBroker = require('../../../../src/ComponentBroker');
|
||||
var MessageTimestamp = ComponentBroker.get('atoms/MessageTimestamp');
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MRoomMemberTile',
|
||||
mixins: [MRoomMemberTileController],
|
||||
|
||||
getMemberEventText: function() {
|
||||
var ev = this.props.mxEvent;
|
||||
// XXX: SYJS-16
|
||||
var senderName = ev.sender ? ev.sender.name : "Someone";
|
||||
switch (ev.getContent().membership) {
|
||||
case 'invite':
|
||||
return senderName + " invited " + ev.target.name + ".";
|
||||
case 'join':
|
||||
return senderName + " joined the room.";
|
||||
case 'leave':
|
||||
return senderName + " left the room.";
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// XXX: for now, just cheekily borrow the css from message tile...
|
||||
return (
|
||||
<div className="mx_MessageTile">
|
||||
<MessageTimestamp ts={this.props.mxEvent.getTs()} />
|
||||
<span className="mx_SenderProfile"></span>
|
||||
<span className="mx_MessageTile_content">
|
||||
{this.getMemberEventText()}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
@ -1,36 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var MTextTileController = require("../../../../src/controllers/molecules/MTextTile");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MTextTile',
|
||||
mixins: [MTextTileController],
|
||||
|
||||
render: function() {
|
||||
var content = this.props.mxEvent.getContent();
|
||||
return (
|
||||
<span ref="content" className="mx_MTextTile mx_MessageTile_content">
|
||||
{content.body}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var ComponentBroker = require('../../../../src/ComponentBroker');
|
||||
|
||||
var LogoutButton = ComponentBroker.get("atoms/LogoutButton");
|
||||
var EnableNotificationsButton = ComponentBroker.get("atoms/EnableNotificationsButton");
|
||||
|
||||
var MatrixToolbarController = require("../../../../src/controllers/molecules/MatrixToolbar");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MatrixToolbar',
|
||||
mixins: [MatrixToolbarController],
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div className="mx_MatrixToolbar">
|
||||
<LogoutButton />
|
||||
<EnableNotificationsButton />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -1,33 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var MemberTileController = require("../../../../src/controllers/molecules/MemberTile");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MemberTile',
|
||||
mixins: [MemberTileController],
|
||||
render: function() {
|
||||
return (
|
||||
<div className="mx_MemberTile">
|
||||
<div className="mx_MemberTile_name">{this.props.member.name}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
@ -1,66 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var classNames = require("classnames");
|
||||
|
||||
var ComponentBroker = require('../../../../src/ComponentBroker');
|
||||
|
||||
var MessageTimestamp = ComponentBroker.get('atoms/MessageTimestamp');
|
||||
var SenderProfile = ComponentBroker.get('molecules/SenderProfile');
|
||||
|
||||
var UnknownMessageTile = ComponentBroker.get('molecules/UnknownMessageTile');
|
||||
|
||||
var tileTypes = {
|
||||
'm.text': ComponentBroker.get('molecules/MTextTile'),
|
||||
'm.notice': ComponentBroker.get('molecules/MNoticeTile'),
|
||||
'm.emote': ComponentBroker.get('molecules/MEmoteTile'),
|
||||
'm.image': ComponentBroker.get('molecules/MImageTile'),
|
||||
'm.file': ComponentBroker.get('molecules/MFileTile')
|
||||
};
|
||||
|
||||
var MessageTileController = require("../../../../src/controllers/molecules/MessageTile");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MessageTile',
|
||||
mixins: [MessageTileController],
|
||||
|
||||
render: function() {
|
||||
var content = this.props.mxEvent.getContent();
|
||||
var msgtype = content.msgtype;
|
||||
var TileType = UnknownMessageTile;
|
||||
if (msgtype && tileTypes[msgtype]) {
|
||||
TileType = tileTypes[msgtype];
|
||||
}
|
||||
var classes = classNames({
|
||||
mx_MessageTile: true,
|
||||
mx_MessageTile_sending: this.props.mxEvent.status == 'sending',
|
||||
mx_MessageTile_notSent: this.props.mxEvent.status == 'not_sent',
|
||||
mx_MessageTile_highlight: this.shouldHighlight()
|
||||
});
|
||||
return (
|
||||
<li className={classes}>
|
||||
<MessageTimestamp ts={this.props.mxEvent.getTs()} />
|
||||
<SenderProfile mxEvent={this.props.mxEvent} />
|
||||
<TileType mxEvent={this.props.mxEvent} />
|
||||
</li>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
@ -1,37 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var ProgressBarController = require("../../../../src/controllers/molecules/ProgressBar");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'ProgressBar',
|
||||
mixins: [ProgressBarController],
|
||||
|
||||
render: function() {
|
||||
// Would use an HTML5 progress tag but if that doesn't animate if you
|
||||
// use the HTML attributes rather than styles
|
||||
var progressStyle = {
|
||||
width: ((this.props.value / this.props.max) * 100)+"%"
|
||||
};
|
||||
return (
|
||||
<div className="mx_ProgressBar"><div className="mx_ProgressBar_fill" style={progressStyle}></div></div>
|
||||
);
|
||||
}
|
||||
});
|
@ -1,44 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var classNames = require('classnames');
|
||||
|
||||
var RoomTileController = require("../../../../src/controllers/molecules/RoomTile");
|
||||
|
||||
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'RoomTile',
|
||||
mixins: [RoomTileController],
|
||||
render: function() {
|
||||
var myUserId = MatrixClientPeg.get().credentials.userId;
|
||||
var classes = classNames({
|
||||
'mx_RoomTile': true,
|
||||
'selected': this.props.selected,
|
||||
'unread': this.props.unread,
|
||||
'highlight': this.props.highlight,
|
||||
'invited': this.props.room.currentState.members[myUserId].membership == 'invite'
|
||||
});
|
||||
return (
|
||||
<div className={classes} onClick={this.onClick}>
|
||||
<div className="mx_RoomTile_name">{this.props.room.name}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var SenderProfileController = require("../../../../src/controllers/molecules/SenderProfile");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'SenderProfile',
|
||||
mixins: [SenderProfileController],
|
||||
|
||||
render: function() {
|
||||
var mxEvent = this.props.mxEvent;
|
||||
var name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender();
|
||||
|
||||
var msgtype = mxEvent.getContent().msgtype;
|
||||
if (msgtype && msgtype == 'm.emote') {
|
||||
name = ''; // emote message must include the name so don't duplicate it
|
||||
}
|
||||
return (
|
||||
<span className="mx_SenderProfile">
|
||||
{name}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
@ -1,43 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var ServerConfigController = require("../../../../src/controllers/molecules/ServerConfig");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'ServerConfig',
|
||||
mixins: [ServerConfigController],
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div className="HomeServerTextBox">
|
||||
<table className="serverConfig">
|
||||
<tr>
|
||||
<td>Home Server URL</td>
|
||||
<td><input type="text" value={this.state.hs_url} onChange={this.hsChanged} /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Identity Server URL</td>
|
||||
<td><input type="text" value={this.state.is_url} onChange={this.isChanged} /></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
@ -1,34 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var UnknownMessageTileController = require("../../../../src/controllers/molecules/UnknownMessageTile");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'UnknownMessageTile',
|
||||
mixins: [UnknownMessageTileController],
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<span className="mx_UnknownMessageTile">
|
||||
?
|
||||
</span>
|
||||
);
|
||||
},
|
||||
});
|
@ -1,44 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var UserSelectorController = require("../../../../src/controllers/molecules/UserSelector");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'UserSelector',
|
||||
mixins: [UserSelectorController],
|
||||
|
||||
onAddUserId: function() {
|
||||
this.addUser(this.refs.user_id_input.getDOMNode().value);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<ul className="mx_UserSelector_UserIdList" ref="list">
|
||||
{this.state.selected_users.map(function(user_id, i) {
|
||||
return <li key={user_id}>{user_id}</li>
|
||||
})}
|
||||
</ul>
|
||||
<input type="text" ref="user_id_input" className="mx_UserSelector_userIdInput" placeholder="ex. @bob:example.com"/>
|
||||
<button onClick={this.onAddUserId} className="mx_UserSelector_AddUserId">Add User</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
@ -1,73 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var CreateRoomController = require("../../../../src/controllers/organisms/CreateRoom");
|
||||
|
||||
var ComponentBroker = require('../../../../src/ComponentBroker');
|
||||
|
||||
var CreateRoomButton = ComponentBroker.get("atoms/create_room/CreateRoomButton");
|
||||
var RoomNameTextbox = ComponentBroker.get("atoms/create_room/RoomNameTextbox");
|
||||
var Presets = ComponentBroker.get("atoms/create_room/Presets");
|
||||
var UserSelector = ComponentBroker.get("molecules/UserSelector");
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'CreateRoom',
|
||||
mixins: [CreateRoomController],
|
||||
|
||||
getPreset: function() {
|
||||
return this.refs.presets.getPreset();
|
||||
},
|
||||
|
||||
getName: function() {
|
||||
return this.refs.name_textbox.getName();
|
||||
},
|
||||
|
||||
getInvitedUsers: function() {
|
||||
return this.refs.user_selector.getUserIds();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var curr_phase = this.state.phase;
|
||||
if (curr_phase == this.phases.CREATING) {
|
||||
return (
|
||||
<div>Creating...</div>
|
||||
);
|
||||
} else {
|
||||
var error_box = "";
|
||||
if (curr_phase == this.phases.ERROR) {
|
||||
error_box = (
|
||||
<div className="mx_Error">
|
||||
An error occured: {this.state.error_string}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="mx_CreateRoom">
|
||||
<label>Room Name <RoomNameTextbox ref="name_textbox" /></label>
|
||||
<Presets ref="presets"/>
|
||||
<UserSelector ref="user_selector"/>
|
||||
<CreateRoomButton onCreateRoom={this.onCreateRoom} />
|
||||
{error_box}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
@ -1,56 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var MemberListController = require("../../../../src/controllers/organisms/MemberList");
|
||||
|
||||
var ComponentBroker = require('../../../../src/ComponentBroker');
|
||||
|
||||
var MemberTile = ComponentBroker.get("molecules/MemberTile");
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MemberList',
|
||||
mixins: [MemberListController],
|
||||
|
||||
makeMemberTiles: function() {
|
||||
var that = this;
|
||||
return Object.keys(that.state.memberDict).map(function(userId) {
|
||||
var m = that.state.memberDict[userId];
|
||||
return (
|
||||
<li key={userId}>
|
||||
<MemberTile
|
||||
member={m}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div className="mx_MemberList">
|
||||
<ul>
|
||||
{this.makeMemberTiles()}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -1,102 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var NotifierController = require("../../../../src/controllers/organisms/Notifier");
|
||||
|
||||
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
|
||||
var extend = require("../../../../src/extend");
|
||||
var dis = require("../../../../src/dispatcher");
|
||||
|
||||
|
||||
var NotifierView = {
|
||||
notificationMessageForEvent: function(ev) {
|
||||
var senderDisplayName = ev.sender ? ev.sender.name : '';
|
||||
var message = null;
|
||||
|
||||
if (ev.event.type === "m.room.message") {
|
||||
message = ev.getContent().body;
|
||||
if (ev.getContent().msgtype === "m.emote") {
|
||||
message = "* " + senderDisplayName + " " + message;
|
||||
} else if (ev.getContent().msgtype === "m.image") {
|
||||
message = senderDisplayName + " sent an image.";
|
||||
}
|
||||
} else if (ev.event.type == "m.room.member") {
|
||||
if (ev.event.state_key !== MatrixClientPeg.get().credentials.userId && "join" === ev.getContent().membership) {
|
||||
// Notify when another user joins
|
||||
message = senderDisplayName + " joined";
|
||||
} else if (ev.event.state_key === MatrixClientPeg.get().credentials.userId && "invite" === ev.getContent().membership) {
|
||||
// notify when you are invited
|
||||
message = senderDisplayName + " invited you to a room";
|
||||
}
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
displayNotification: function(ev, room) {
|
||||
if (!global.Notification || global.Notification.permission != 'granted') {
|
||||
return;
|
||||
}
|
||||
if (global.document.hasFocus()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var msg = this.notificationMessageForEvent(ev);
|
||||
if (!msg) return;
|
||||
|
||||
var title;
|
||||
if (!ev.sender || room.name == ev.sender.name) {
|
||||
title = room.name;
|
||||
} else if (ev.sender) {
|
||||
title = ev.sender.name + " (" + room.name + ")";
|
||||
}
|
||||
|
||||
var notification = new global.Notification(
|
||||
title,
|
||||
{
|
||||
"body": msg,
|
||||
"icon": MatrixClientPeg.get().getAvatarUrlForMember(ev.sender)
|
||||
}
|
||||
);
|
||||
|
||||
notification.onclick = function() {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: room.roomId
|
||||
});
|
||||
global.focus();
|
||||
};
|
||||
|
||||
/*var audioClip;
|
||||
|
||||
if (audioNotification) {
|
||||
audioClip = playAudio(audioNotification);
|
||||
}*/
|
||||
|
||||
global.setTimeout(function() {
|
||||
notification.close();
|
||||
}, 5 * 1000);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
var NotifierClass = function() {};
|
||||
extend(NotifierClass.prototype, NotifierController);
|
||||
extend(NotifierClass.prototype, NotifierView);
|
||||
|
||||
module.exports = new NotifierClass();
|
||||
|
@ -1,36 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var RoomListController = require("../../../../src/controllers/organisms/RoomList");
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'RoomList',
|
||||
mixins: [RoomListController],
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div className="mx_RoomList">
|
||||
{this.makeRoomTiles()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -1,99 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var MatrixClientPeg = require("../../../../src/MatrixClientPeg");
|
||||
|
||||
var ComponentBroker = require('../../../../src/ComponentBroker');
|
||||
var classNames = require("classnames");
|
||||
|
||||
var MessageTile = ComponentBroker.get('molecules/MessageTile');
|
||||
var RoomHeader = ComponentBroker.get('molecules/RoomHeader');
|
||||
var MemberList = ComponentBroker.get('organisms/MemberList');
|
||||
var MessageComposer = ComponentBroker.get('molecules/MessageComposer');
|
||||
|
||||
var RoomViewController = require("../../../../src/controllers/organisms/RoomView");
|
||||
|
||||
var Loader = require("react-loader");
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'RoomView',
|
||||
mixins: [RoomViewController],
|
||||
|
||||
render: function() {
|
||||
if (!this.state.room) {
|
||||
return (
|
||||
<div />
|
||||
);
|
||||
}
|
||||
|
||||
var myUserId = MatrixClientPeg.get().credentials.userId;
|
||||
if (this.state.room.currentState.members[myUserId].membership == 'invite') {
|
||||
if (this.state.joining) {
|
||||
return (
|
||||
<div className="mx_RoomView">
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
var inviteEvent = this.state.room.currentState.members[myUserId].events.member.event;
|
||||
// XXX: Leaving this intentionally basic for now because invites are about to change totally
|
||||
var joinErrorText = this.state.joinError ? "Failed to join room!" : "";
|
||||
return (
|
||||
<div className="mx_RoomView">
|
||||
<div className="mx_RoomView_invitePrompt">
|
||||
<div>{inviteEvent.user_id} has invited you to a room</div>
|
||||
<button ref="joinButton" onClick={this.onJoinButtonClicked}>Join</button>
|
||||
<div className="error">{joinErrorText}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
var scrollheader_classes = classNames({
|
||||
mx_RoomView_scrollheader: true,
|
||||
loading: this.state.paginating
|
||||
});
|
||||
return (
|
||||
<div className="mx_RoomView">
|
||||
<RoomHeader room={this.state.room} />
|
||||
<div className="mx_RoomView_roomWrapper">
|
||||
<main className="mx_RoomView_messagePanel">
|
||||
<div ref="messageWrapper" className="mx_RoomView_messageListWrapper" onScroll={this.onMessageListScroll}>
|
||||
<div className="mx_RoomView_MessageList">
|
||||
<div className={scrollheader_classes}>
|
||||
</div>
|
||||
<ul className="mx_RoomView_MessageList_ul" aria-live="polite">
|
||||
{this.getEventTiles()}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<MessageComposer roomId={this.props.roomId} />
|
||||
</main>
|
||||
<aside>
|
||||
<MemberList roomId={this.props.roomId} key={this.props.roomId} />
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@ -1,70 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var ComponentBroker = require('../../../../src/ComponentBroker');
|
||||
|
||||
var RoomList = ComponentBroker.get('organisms/RoomList');
|
||||
var RoomView = ComponentBroker.get('organisms/RoomView');
|
||||
var MatrixToolbar = ComponentBroker.get('molecules/MatrixToolbar');
|
||||
var Login = ComponentBroker.get('templates/Login');
|
||||
var Register = ComponentBroker.get('templates/Register');
|
||||
|
||||
var MatrixChatController = require("../../../../src/controllers/pages/MatrixChat");
|
||||
|
||||
// should be atomised
|
||||
var Loader = require("react-loader");
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MatrixChat',
|
||||
mixins: [MatrixChatController],
|
||||
|
||||
render: function() {
|
||||
if (this.state.logged_in && this.state.ready) {
|
||||
return (
|
||||
<div className="mx_MatrixChat">
|
||||
<div className="mx_MatrixChat_chatWrapper">
|
||||
<aside className="mx_MatrixChat_leftPanel">
|
||||
<RoomList selectedRoom={this.state.currentRoom} />
|
||||
<MatrixToolbar />
|
||||
</aside>
|
||||
<RoomView roomId={this.state.currentRoom} key={this.state.currentRoom} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (this.state.logged_in) {
|
||||
return (
|
||||
<Loader />
|
||||
);
|
||||
} else if (this.state.screen == 'register') {
|
||||
return (
|
||||
<Register onLoggedIn={this.onLoggedIn} clientSecret={this.state.register_client_secret}
|
||||
sessionId={this.state.register_session_id} idSid={this.state.register_id_sid}
|
||||
hsUrl={this.state.register_hs_url} isUrl={this.state.register_is_url}
|
||||
registrationUrl={this.props.registrationUrl}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Login onLoggedIn={this.onLoggedIn} />
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1,102 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var ComponentBroker = require("../../../../src/ComponentBroker");
|
||||
|
||||
var ProgressBar = ComponentBroker.get("molecules/ProgressBar");
|
||||
var Loader = require("react-loader");
|
||||
|
||||
var LoginController = require("../../../../src/controllers/templates/Login");
|
||||
|
||||
var ServerConfig = ComponentBroker.get("molecules/ServerConfig");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'Login',
|
||||
mixins: [LoginController],
|
||||
|
||||
getHsUrl: function() {
|
||||
return this.refs.serverConfig.getHsUrl();
|
||||
},
|
||||
|
||||
getIsUrl: function() {
|
||||
return this.refs.serverConfig.getIsUrl();
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the form field values for the current login stage
|
||||
*/
|
||||
getFormVals: function() {
|
||||
return {
|
||||
'username': this.refs.user.getDOMNode().value,
|
||||
'password': this.refs.pass.getDOMNode().value
|
||||
};
|
||||
},
|
||||
|
||||
componentForStep: function(step) {
|
||||
switch (step) {
|
||||
case 'choose_hs':
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={this.onHSChosen}>
|
||||
<ServerConfig ref="serverConfig" />
|
||||
<input type="submit" value="Continue" />
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
// XXX: clearly these should be separate organisms
|
||||
case 'stage_m.login.password':
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={this.onUserPassEntered}>
|
||||
<input ref="user" type="text" placeholder="username" /><br />
|
||||
<input ref="pass" type="password" placeholder="password" /><br />
|
||||
<input type="submit" value="Log in" />
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
loginContent: function() {
|
||||
if (this.state.busy) {
|
||||
return (
|
||||
<Loader />
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<h1>Please log in:</h1>
|
||||
{this.componentForStep(this.state.step)}
|
||||
<div className="error">{this.state.errorText}</div>
|
||||
<a onClick={this.showRegister} href="#">Create a new account</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div className="mx_Login">
|
||||
<ProgressBar value={this.state.currentStep} max={this.state.totalSteps} />
|
||||
{this.loginContent()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
@ -1,131 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var ComponentBroker = require("../../../../src/ComponentBroker");
|
||||
|
||||
var Loader = require("react-loader");
|
||||
|
||||
var RegisterController = require("../../../../src/controllers/templates/Register");
|
||||
|
||||
var ServerConfig = ComponentBroker.get("molecules/ServerConfig");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'Register',
|
||||
mixins: [RegisterController],
|
||||
|
||||
getRegFormVals: function() {
|
||||
return {
|
||||
email: this.refs.email.getDOMNode().value,
|
||||
username: this.refs.username.getDOMNode().value,
|
||||
password: this.refs.password.getDOMNode().value,
|
||||
confirmPassword: this.refs.confirmPassword.getDOMNode().value
|
||||
};
|
||||
},
|
||||
|
||||
getHsUrl: function() {
|
||||
return this.refs.serverConfig.getHsUrl();
|
||||
},
|
||||
|
||||
getIsUrl: function() {
|
||||
return this.refs.serverConfig.getIsUrl();
|
||||
},
|
||||
|
||||
componentForStep: function(step) {
|
||||
switch (step) {
|
||||
case 'initial':
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={this.onInitialStageSubmit}>
|
||||
Email: <input type="text" ref="email" defaultValue={this.savedParams.email} /><br />
|
||||
Username: <input type="text" ref="username" defaultValue={this.savedParams.username} /><br />
|
||||
Password: <input type="password" ref="password" defaultValue={this.savedParams.password} /><br />
|
||||
Confirm Password: <input type="password" ref="confirmPassword" defaultValue={this.savedParams.confirmPassword} /><br />
|
||||
<ServerConfig ref="serverConfig" />
|
||||
<input type="submit" value="Continue" />
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
// XXX: clearly these should be separate organisms
|
||||
case 'stage_m.login.email.identity':
|
||||
return (
|
||||
<div>
|
||||
Please check your email to continue registration.
|
||||
</div>
|
||||
);
|
||||
case 'stage_m.login.recaptcha':
|
||||
return (
|
||||
<div ref="recaptchaContainer">
|
||||
This Home Server would like to make sure you're not a robot
|
||||
<div id="mx_recaptcha"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
registerContent: function() {
|
||||
if (this.state.busy) {
|
||||
return (
|
||||
<Loader />
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<h1>Create a new account:</h1>
|
||||
{this.componentForStep(this.state.step)}
|
||||
<div className="error">{this.state.errorText}</div>
|
||||
<a onClick={this.showLogin} href="#">Sign in with existing account</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
onBadFields: function(bad) {
|
||||
var keys = Object.keys(bad);
|
||||
var strings = [];
|
||||
for (var i = 0; i < keys.length; ++i) {
|
||||
switch (bad[keys[i]]) {
|
||||
case this.FieldErrors.PasswordMismatch:
|
||||
strings.push("Passwords don't match");
|
||||
break;
|
||||
case this.FieldErrors.Missing:
|
||||
strings.push("Missing "+keys[i]);
|
||||
break;
|
||||
case this.FieldErrors.TooShort:
|
||||
strings.push(keys[i]+" is too short");
|
||||
break;
|
||||
case this.FieldErrors.InUse:
|
||||
strings.push(keys[i]+" is already taken");
|
||||
break;
|
||||
}
|
||||
}
|
||||
var errtxt = strings.join(', ');
|
||||
this.setState({
|
||||
errorText: errtxt
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div className="mx_Register">
|
||||
{this.registerContent()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
302
src/CallHandler.js
Normal file
302
src/CallHandler.js
Normal file
@ -0,0 +1,302 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Manages a list of all the currently active calls.
|
||||
*
|
||||
* This handler dispatches when voip calls are added/updated/removed from this list:
|
||||
* {
|
||||
* action: 'call_state'
|
||||
* room_id: <room ID of the call>
|
||||
* }
|
||||
*
|
||||
* To know the state of the call, this handler exposes a getter to
|
||||
* obtain the call for a room:
|
||||
* var call = CallHandler.getCall(roomId)
|
||||
* var state = call.call_state; // ringing|ringback|connected|ended|busy|stop_ringback|stop_ringing
|
||||
*
|
||||
* This handler listens for and handles the following actions:
|
||||
* {
|
||||
* action: 'place_call',
|
||||
* type: 'voice|video',
|
||||
* room_id: <room that the place call button was pressed in>
|
||||
* }
|
||||
*
|
||||
* {
|
||||
* action: 'incoming_call'
|
||||
* call: MatrixCall
|
||||
* }
|
||||
*
|
||||
* {
|
||||
* action: 'hangup'
|
||||
* room_id: <room that the hangup button was pressed in>
|
||||
* }
|
||||
*
|
||||
* {
|
||||
* action: 'answer'
|
||||
* room_id: <room that the answer button was pressed in>
|
||||
* }
|
||||
*/
|
||||
|
||||
var MatrixClientPeg = require('./MatrixClientPeg');
|
||||
var Modal = require('./Modal');
|
||||
var sdk = require('./index');
|
||||
var Matrix = require("matrix-js-sdk");
|
||||
var dis = require("./dispatcher");
|
||||
var Modulator = require("./Modulator");
|
||||
|
||||
global.mxCalls = {
|
||||
//room_id: MatrixCall
|
||||
};
|
||||
var calls = global.mxCalls;
|
||||
|
||||
function play(audioId) {
|
||||
// TODO: Attach an invisible element for this instead
|
||||
// which listens?
|
||||
var audio = document.getElementById(audioId);
|
||||
if (audio) {
|
||||
audio.load();
|
||||
audio.play();
|
||||
}
|
||||
}
|
||||
|
||||
function pause(audioId) {
|
||||
// TODO: Attach an invisible element for this instead
|
||||
// which listens?
|
||||
var audio = document.getElementById(audioId);
|
||||
if (audio) {
|
||||
audio.pause();
|
||||
}
|
||||
}
|
||||
|
||||
function _setCallListeners(call) {
|
||||
call.on("error", function(err) {
|
||||
console.error("Call error: %s", err);
|
||||
console.error(err.stack);
|
||||
call.hangup();
|
||||
_setCallState(undefined, call.roomId, "ended");
|
||||
});
|
||||
call.on("hangup", function() {
|
||||
_setCallState(undefined, call.roomId, "ended");
|
||||
});
|
||||
// map web rtc states to dummy UI state
|
||||
// ringing|ringback|connected|ended|busy|stop_ringback|stop_ringing
|
||||
call.on("state", function(newState, oldState) {
|
||||
if (newState === "ringing") {
|
||||
_setCallState(call, call.roomId, "ringing");
|
||||
pause("ringbackAudio");
|
||||
}
|
||||
else if (newState === "invite_sent") {
|
||||
_setCallState(call, call.roomId, "ringback");
|
||||
play("ringbackAudio");
|
||||
}
|
||||
else if (newState === "ended" && oldState === "connected") {
|
||||
_setCallState(undefined, call.roomId, "ended");
|
||||
pause("ringbackAudio");
|
||||
play("callendAudio");
|
||||
}
|
||||
else if (newState === "ended" && oldState === "invite_sent" &&
|
||||
(call.hangupParty === "remote" ||
|
||||
(call.hangupParty === "local" && call.hangupReason === "invite_timeout")
|
||||
)) {
|
||||
_setCallState(call, call.roomId, "busy");
|
||||
pause("ringbackAudio");
|
||||
play("busyAudio");
|
||||
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Call Timeout",
|
||||
description: "The remote side failed to pick up."
|
||||
});
|
||||
}
|
||||
else if (oldState === "invite_sent") {
|
||||
_setCallState(call, call.roomId, "stop_ringback");
|
||||
pause("ringbackAudio");
|
||||
}
|
||||
else if (oldState === "ringing") {
|
||||
_setCallState(call, call.roomId, "stop_ringing");
|
||||
pause("ringbackAudio");
|
||||
}
|
||||
else if (newState === "connected") {
|
||||
_setCallState(call, call.roomId, "connected");
|
||||
pause("ringbackAudio");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _setCallState(call, roomId, status) {
|
||||
console.log(
|
||||
"Call state in %s changed to %s (%s)", roomId, status, (call ? call.state : "-")
|
||||
);
|
||||
calls[roomId] = call;
|
||||
if (call) {
|
||||
call.call_state = status;
|
||||
}
|
||||
dis.dispatch({
|
||||
action: 'call_state',
|
||||
room_id: roomId
|
||||
});
|
||||
}
|
||||
|
||||
function _onAction(payload) {
|
||||
function placeCall(newCall) {
|
||||
_setCallListeners(newCall);
|
||||
_setCallState(newCall, newCall.roomId, "ringback");
|
||||
if (payload.type === 'voice') {
|
||||
newCall.placeVoiceCall();
|
||||
}
|
||||
else if (payload.type === 'video') {
|
||||
newCall.placeVideoCall(
|
||||
payload.remote_element,
|
||||
payload.local_element
|
||||
);
|
||||
}
|
||||
else {
|
||||
console.error("Unknown conf call type: %s", payload.type);
|
||||
}
|
||||
}
|
||||
|
||||
switch (payload.action) {
|
||||
case 'place_call':
|
||||
if (module.exports.getAnyActiveCall()) {
|
||||
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Existing Call",
|
||||
description: "You are already in a call."
|
||||
});
|
||||
return; // don't allow >1 call to be placed.
|
||||
}
|
||||
var room = MatrixClientPeg.get().getRoom(payload.room_id);
|
||||
if (!room) {
|
||||
console.error("Room %s does not exist.", payload.room_id);
|
||||
return;
|
||||
}
|
||||
|
||||
var members = room.getJoinedMembers();
|
||||
if (members.length <= 1) {
|
||||
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
description: "You cannot place a call with yourself."
|
||||
});
|
||||
return;
|
||||
}
|
||||
else if (members.length === 2) {
|
||||
console.log("Place %s call in %s", payload.type, payload.room_id);
|
||||
var call = Matrix.createNewMatrixCall(
|
||||
MatrixClientPeg.get(), payload.room_id
|
||||
);
|
||||
placeCall(call);
|
||||
}
|
||||
else { // > 2
|
||||
dis.dispatch({
|
||||
action: "place_conference_call",
|
||||
room_id: payload.room_id,
|
||||
type: payload.type,
|
||||
remote_element: payload.remote_element,
|
||||
local_element: payload.local_element
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'place_conference_call':
|
||||
console.log("Place conference call in %s", payload.room_id);
|
||||
if (!Modulator.hasConferenceHandler()) {
|
||||
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
description: "Conference calls are not supported in this client"
|
||||
});
|
||||
} else {
|
||||
var ConferenceHandler = Modulator.getConferenceHandler();
|
||||
ConferenceHandler.createNewMatrixCall(
|
||||
MatrixClientPeg.get(), payload.room_id
|
||||
).done(function(call) {
|
||||
placeCall(call);
|
||||
}, function(err) {
|
||||
console.error("Failed to setup conference call: %s", err);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'incoming_call':
|
||||
if (module.exports.getAnyActiveCall()) {
|
||||
payload.call.hangup("busy");
|
||||
return; // don't allow >1 call to be received, hangup newer one.
|
||||
}
|
||||
var call = payload.call;
|
||||
_setCallListeners(call);
|
||||
_setCallState(call, call.roomId, "ringing");
|
||||
break;
|
||||
case 'hangup':
|
||||
if (!calls[payload.room_id]) {
|
||||
return; // no call to hangup
|
||||
}
|
||||
calls[payload.room_id].hangup();
|
||||
_setCallState(null, payload.room_id, "ended");
|
||||
break;
|
||||
case 'answer':
|
||||
if (!calls[payload.room_id]) {
|
||||
return; // no call to answer
|
||||
}
|
||||
calls[payload.room_id].answer();
|
||||
_setCallState(calls[payload.room_id], payload.room_id, "connected");
|
||||
dis.dispatch({
|
||||
action: "view_room",
|
||||
room_id: payload.room_id
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
// FIXME: Nasty way of making sure we only register
|
||||
// with the dispatcher once
|
||||
if (!global.mxCallHandler) {
|
||||
dis.register(_onAction);
|
||||
}
|
||||
|
||||
var callHandler = {
|
||||
getCallForRoom: function(roomId) {
|
||||
var call = module.exports.getCall(roomId);
|
||||
if (call) return call;
|
||||
|
||||
if (Modulator.hasConferenceHandler()) {
|
||||
var ConferenceHandler = Modulator.getConferenceHandler();
|
||||
call = ConferenceHandler.getConferenceCallForRoom(roomId);
|
||||
}
|
||||
if (call) return call;
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
getCall: function(roomId) {
|
||||
return calls[roomId] || null;
|
||||
},
|
||||
|
||||
getAnyActiveCall: function() {
|
||||
var roomsWithCalls = Object.keys(calls);
|
||||
for (var i = 0; i < roomsWithCalls.length; i++) {
|
||||
if (calls[roomsWithCalls[i]] &&
|
||||
calls[roomsWithCalls[i]].call_state !== "ended") {
|
||||
return calls[roomsWithCalls[i]];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
// Only things in here which actually need to be global are the
|
||||
// calls list (done separately) and making sure we only register
|
||||
// with the dispatcher once (which uses this mechanism but checks
|
||||
// separately). This could be tidied up.
|
||||
if (global.mxCallHandler === undefined) {
|
||||
global.mxCallHandler = callHandler;
|
||||
}
|
||||
|
||||
module.exports = global.mxCallHandler;
|
@ -1,92 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
function load(name) {
|
||||
var module = require("../skins/base/views/"+name);
|
||||
return module;
|
||||
};
|
||||
|
||||
var ComponentBroker = function() {
|
||||
this.components = {};
|
||||
};
|
||||
|
||||
ComponentBroker.prototype = {
|
||||
get: function(name) {
|
||||
if (this.components[name]) {
|
||||
return this.components[name];
|
||||
}
|
||||
|
||||
this.components[name] = load(name);
|
||||
return this.components[name];
|
||||
},
|
||||
|
||||
set: function(name, module) {
|
||||
this.components[name] = module;
|
||||
}
|
||||
};
|
||||
|
||||
// We define one Component Broker globally, because the intention is
|
||||
// very much that it is a singleton. Relying on there only being one
|
||||
// copy of the module can be dicey and not work as browserify's
|
||||
// behaviour with multiple copies of files etc. is erratic at best.
|
||||
// XXX: We can still end up with the same file twice in the resulting
|
||||
// JS bundle which is nonideal.
|
||||
if (global.componentBroker === undefined) {
|
||||
global.componentBroker = new ComponentBroker();
|
||||
}
|
||||
module.exports = global.componentBroker;
|
||||
|
||||
// We need to tell browserify to include all the components
|
||||
// by direct require syntax in here, but we don't want them
|
||||
// to be evaluated in this file because then we wouldn't be
|
||||
// able to override them. if (0) does this.
|
||||
// Must be in this file (because the require is file-specific) and
|
||||
// must be at the end because the components include this file.
|
||||
if (0) {
|
||||
require('../skins/base/views/atoms/LogoutButton');
|
||||
require('../skins/base/views/atoms/EnableNotificationsButton');
|
||||
require('../skins/base/views/atoms/MessageTimestamp');
|
||||
require('../skins/base/views/atoms/create_room/CreateRoomButton');
|
||||
require('../skins/base/views/atoms/create_room/RoomNameTextbox');
|
||||
require('../skins/base/views/atoms/create_room/Presets');
|
||||
require('../skins/base/views/atoms/EditableText');
|
||||
require('../skins/base/views/molecules/MatrixToolbar');
|
||||
require('../skins/base/views/molecules/RoomTile');
|
||||
require('../skins/base/views/molecules/MessageTile');
|
||||
require('../skins/base/views/molecules/SenderProfile');
|
||||
require('../skins/base/views/molecules/UnknownMessageTile');
|
||||
require('../skins/base/views/molecules/MTextTile');
|
||||
require('../skins/base/views/molecules/MNoticeTile');
|
||||
require('../skins/base/views/molecules/MEmoteTile');
|
||||
require('../skins/base/views/molecules/MImageTile');
|
||||
require('../skins/base/views/molecules/MFileTile');
|
||||
require('../skins/base/views/molecules/MRoomMemberTile');
|
||||
require('../skins/base/views/molecules/RoomHeader');
|
||||
require('../skins/base/views/molecules/MessageComposer');
|
||||
require('../skins/base/views/molecules/ProgressBar');
|
||||
require('../skins/base/views/molecules/ServerConfig');
|
||||
require('../skins/base/views/organisms/MemberList');
|
||||
require('../skins/base/views/molecules/MemberTile');
|
||||
require('../skins/base/views/organisms/RoomList');
|
||||
require('../skins/base/views/organisms/RoomView');
|
||||
require('../skins/base/views/templates/Login');
|
||||
require('../skins/base/views/templates/Register');
|
||||
require('../skins/base/views/organisms/Notifier');
|
||||
require('../skins/base/views/organisms/CreateRoom');
|
||||
require('../skins/base/views/molecules/UserSelector');
|
||||
}
|
@ -53,10 +53,14 @@ function sendContentToRoom(file, roomId, matrixClient) {
|
||||
body: file.name,
|
||||
info: {
|
||||
size: file.size,
|
||||
mimetype: file.type
|
||||
}
|
||||
};
|
||||
|
||||
// if we have a mime type for the file, add it to the message metadata
|
||||
if (file.type) {
|
||||
content.info.mimetype = file.type;
|
||||
}
|
||||
|
||||
var def = q.defer();
|
||||
if (file.type.indexOf('image/') == 0) {
|
||||
content.msgtype = 'm.image';
|
||||
|
@ -23,6 +23,16 @@ var matrixClient = null;
|
||||
|
||||
var localStorage = window.localStorage;
|
||||
|
||||
function deviceId() {
|
||||
var id = Math.floor(Math.random()*16777215).toString(16);
|
||||
id = "W" + "000000".substring(id.length) + id;
|
||||
if (localStorage) {
|
||||
id = localStorage.getItem("mx_device_id") || id;
|
||||
localStorage.setItem("mx_device_id", id);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
function createClient(hs_url, is_url, user_id, access_token) {
|
||||
var opts = {
|
||||
baseUrl: hs_url,
|
||||
@ -31,6 +41,11 @@ function createClient(hs_url, is_url, user_id, access_token) {
|
||||
userId: user_id
|
||||
};
|
||||
|
||||
if (localStorage) {
|
||||
opts.sessionStore = new Matrix.WebStorageSessionStore(localStorage);
|
||||
opts.deviceId = deviceId();
|
||||
}
|
||||
|
||||
matrixClient = Matrix.createClient(opts);
|
||||
}
|
||||
|
||||
@ -44,23 +59,33 @@ if (localStorage) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
get: function() {
|
||||
class MatrixClient {
|
||||
get() {
|
||||
return matrixClient;
|
||||
},
|
||||
}
|
||||
|
||||
replaceUsingUrls: function(hs_url, is_url) {
|
||||
unset() {
|
||||
matrixClient = null;
|
||||
}
|
||||
|
||||
replaceUsingUrls(hs_url, is_url) {
|
||||
matrixClient = Matrix.createClient({
|
||||
baseUrl: hs_url,
|
||||
idBaseUrl: is_url
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
replaceUsingAccessToken: function(hs_url, is_url, user_id, access_token) {
|
||||
createClient(hs_url, is_url, user_id, access_token);
|
||||
replaceUsingAccessToken(hs_url, is_url, user_id, access_token) {
|
||||
if (localStorage) {
|
||||
try {
|
||||
localStorage.clear();
|
||||
} catch (e) {
|
||||
console.warn("Error using local storage");
|
||||
}
|
||||
}
|
||||
createClient(hs_url, is_url, user_id, access_token);
|
||||
if (localStorage) {
|
||||
try {
|
||||
localStorage.setItem("mx_hs_url", hs_url);
|
||||
localStorage.setItem("mx_is_url", is_url);
|
||||
localStorage.setItem("mx_user_id", user_id);
|
||||
@ -72,5 +97,9 @@ module.exports = {
|
||||
console.warn("No local storage available: can't persist session!");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (!global.mxMatrixClient) {
|
||||
global.mxMatrixClient = new MatrixClient();
|
||||
}
|
||||
module.exports = global.mxMatrixClient;
|
||||
|
36
src/MatrixTools.js
Normal file
36
src/MatrixTools.js
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Given a room object, return the canonical alias for it
|
||||
* if there is one. Otherwise return null;
|
||||
*/
|
||||
getCanonicalAliasForRoom: function(room) {
|
||||
var aliasEvents = room.currentState.getStateEvents(
|
||||
"m.room.aliases"
|
||||
);
|
||||
// Canonical aliases aren't implemented yet, so just return the first
|
||||
for (var j = 0; j < aliasEvents.length; j++) {
|
||||
var aliases = aliasEvents[j].getContent().aliases;
|
||||
if (aliases && aliases.length) {
|
||||
return aliases[0];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
61
src/Modal.js
Normal file
61
src/Modal.js
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
module.exports = {
|
||||
DialogContainerId: "mx_Dialog_Container",
|
||||
|
||||
getOrCreateContainer: function() {
|
||||
var container = document.getElementById(this.DialogContainerId);
|
||||
|
||||
if (!container) {
|
||||
container = document.createElement("div");
|
||||
container.id = this.DialogContainerId;
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
return container;
|
||||
},
|
||||
|
||||
createDialog: function (Element, props) {
|
||||
var self = this;
|
||||
|
||||
var closeDialog = function() {
|
||||
React.unmountComponentAtNode(self.getOrCreateContainer());
|
||||
|
||||
if (props && props.onFinished) props.onFinished.apply(null, arguments);
|
||||
};
|
||||
|
||||
// FIXME: If a dialog uses getDefaultProps it clobbers the onFinished
|
||||
// property set here so you can't close the dialog from a button click!
|
||||
var dialog = (
|
||||
<div className="mx_Dialog_wrapper">
|
||||
<div className="mx_Dialog">
|
||||
<Element {...props} onFinished={closeDialog}/>
|
||||
</div>
|
||||
<div className="mx_Dialog_background" onClick={closeDialog}></div>
|
||||
</div>
|
||||
);
|
||||
|
||||
React.render(dialog, this.getOrCreateContainer());
|
||||
|
||||
return {close: closeDialog};
|
||||
},
|
||||
};
|
111
src/Modulator.js
Normal file
111
src/Modulator.js
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The modulator stores 'modules': classes that provide
|
||||
* functionality and are not React UI components.
|
||||
* Modules go into named slots, eg. a conference calling
|
||||
* module goes into the 'conference' slot. If two modules
|
||||
* that use the same slot are loaded, this is considered
|
||||
* to be an error.
|
||||
*
|
||||
* There are some module slots that the react SDK knows
|
||||
* about natively: these have explicit getters.
|
||||
*
|
||||
* A module must define:
|
||||
* - 'slot' (string): The name of the slot it goes into
|
||||
* and may define:
|
||||
* - 'start' (function): Called on module load
|
||||
* - 'stop' (function): Called on module unload
|
||||
*/
|
||||
class Modulator {
|
||||
constructor() {
|
||||
this.modules = {};
|
||||
}
|
||||
|
||||
getModule(name) {
|
||||
var m = this.getModuleOrNull(name);
|
||||
if (m === null) {
|
||||
throw new Error("No such module: "+name);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
getModuleOrNull(name) {
|
||||
if (this.modules == {}) {
|
||||
throw new Error(
|
||||
"Attempted to get a module before a skin has been loaded."+
|
||||
"This is probably because a component has called "+
|
||||
"getModule at the root level."
|
||||
);
|
||||
}
|
||||
var module = this.modules[name];
|
||||
if (module) {
|
||||
return module;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
hasModule(name) {
|
||||
var m = this.getModuleOrNull(name);
|
||||
return m !== null;
|
||||
}
|
||||
|
||||
loadModule(moduleObject) {
|
||||
if (!moduleObject.slot) {
|
||||
throw new Error(
|
||||
"Attempted to load something that is not a module "+
|
||||
"(does not have a slot name)"
|
||||
);
|
||||
}
|
||||
if (this.modules[moduleObject.slot] !== undefined) {
|
||||
throw new Error(
|
||||
"Cannot load module: slot '"+moduleObject.slot+"' is occupied!"
|
||||
);
|
||||
}
|
||||
this.modules[moduleObject.slot] = moduleObject;
|
||||
}
|
||||
|
||||
reset() {
|
||||
var keys = Object.keys(this.modules);
|
||||
for (var i = 0; i < keys.length; ++i) {
|
||||
var k = keys[i];
|
||||
var m = this.modules[k];
|
||||
|
||||
if (m.stop) m.stop();
|
||||
}
|
||||
this.modules = {};
|
||||
}
|
||||
|
||||
// ***********
|
||||
// known slots
|
||||
// ***********
|
||||
|
||||
getConferenceHandler() {
|
||||
return this.getModule('conference');
|
||||
}
|
||||
|
||||
hasConferenceHandler() {
|
||||
return this.hasModule('conference');
|
||||
}
|
||||
}
|
||||
|
||||
// Define one Modulator globally (see Skinner.js)
|
||||
if (global.mxModulator === undefined) {
|
||||
global.mxModulator = new Modulator();
|
||||
}
|
||||
module.exports = global.mxModulator;
|
||||
|
107
src/Presence.js
Normal file
107
src/Presence.js
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
var MatrixClientPeg = require("./MatrixClientPeg");
|
||||
|
||||
// Time in ms after that a user is considered as unavailable/away
|
||||
var UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins
|
||||
var PRESENCE_STATES = ["online", "offline", "unavailable"];
|
||||
|
||||
// The current presence state
|
||||
var state, timer;
|
||||
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
* Start listening the user activity to evaluate his presence state.
|
||||
* Any state change will be sent to the Home Server.
|
||||
*/
|
||||
start: function() {
|
||||
var self = this;
|
||||
this.running = true;
|
||||
if (undefined === state) {
|
||||
// The user is online if they move the mouse or press a key
|
||||
document.onmousemove = function() { self._resetTimer(); };
|
||||
document.onkeypress = function() { self._resetTimer(); };
|
||||
this._resetTimer();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop tracking user activity
|
||||
*/
|
||||
stop: function() {
|
||||
this.running = false;
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
timer = undefined;
|
||||
}
|
||||
state = undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the current presence state.
|
||||
* @returns {string} the presence state (see PRESENCE enum)
|
||||
*/
|
||||
getState: function() {
|
||||
return state;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the presence state.
|
||||
* If the state has changed, the Home Server will be notified.
|
||||
* @param {string} newState the new presence state (see PRESENCE enum)
|
||||
*/
|
||||
setState: function(newState) {
|
||||
if (newState === state) {
|
||||
return;
|
||||
}
|
||||
if (PRESENCE_STATES.indexOf(newState) === -1) {
|
||||
throw new Error("Bad presence state: " + newState);
|
||||
}
|
||||
if (!this.running) {
|
||||
return;
|
||||
}
|
||||
state = newState;
|
||||
MatrixClientPeg.get().setPresence(state).done(function() {
|
||||
console.log("Presence: %s", newState);
|
||||
}, function(err) {
|
||||
console.error("Failed to set presence: %s", err);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback called when the user made no action on the page for UNAVAILABLE_TIME ms.
|
||||
* @private
|
||||
*/
|
||||
_onUnavailableTimerFire: function() {
|
||||
this.setState("unavailable");
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback called when the user made an action on the page
|
||||
* @private
|
||||
*/
|
||||
_resetTimer: function() {
|
||||
var self = this;
|
||||
this.setState("online");
|
||||
// Re-arm the timer
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(function() {
|
||||
self._onUnavailableTimerFire();
|
||||
}, UNAVAILABLE_TIME_MS);
|
||||
}
|
||||
};
|
@ -17,7 +17,12 @@ limitations under the License.
|
||||
'use strict';
|
||||
|
||||
function tsOfNewestEvent(room) {
|
||||
return room.timeline[room.timeline.length - 1].getTs();
|
||||
if (room.timeline.length) {
|
||||
return room.timeline[room.timeline.length - 1].getTs();
|
||||
}
|
||||
else {
|
||||
return Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
}
|
||||
|
||||
function mostRecentActivityFirst(roomList) {
|
||||
|
63
src/Skinner.js
Normal file
63
src/Skinner.js
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
class Skinner {
|
||||
constructor() {
|
||||
this.components = null;
|
||||
}
|
||||
|
||||
getComponent(name) {
|
||||
if (this.components === null) {
|
||||
throw new Error(
|
||||
"Attempted to get a component before a skin has been loaded."+
|
||||
"This is probably because either:"+
|
||||
" a) Your app has not called sdk.loadSkin(), or"+
|
||||
" b) A component has called getComponent at the root level"
|
||||
);
|
||||
}
|
||||
var comp = this.components[name];
|
||||
if (comp) {
|
||||
return comp;
|
||||
}
|
||||
throw new Error("No such component: "+name);
|
||||
}
|
||||
|
||||
load(skinObject) {
|
||||
if (this.components !== null) {
|
||||
throw new Error(
|
||||
"Attempted to load a skin while a skin is already loaded"+
|
||||
"If you want to change the active skin, call resetSkin first"
|
||||
);
|
||||
}
|
||||
this.components = skinObject;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.components = null;
|
||||
}
|
||||
}
|
||||
|
||||
// We define one Skinner globally, because the intention is
|
||||
// very much that it is a singleton. Relying on there only being one
|
||||
// copy of the module can be dicey and not work as browserify's
|
||||
// behaviour with multiple copies of files etc. is erratic at best.
|
||||
// XXX: We can still end up with the same file twice in the resulting
|
||||
// JS bundle which is nonideal.
|
||||
if (global.mxSkinner === undefined) {
|
||||
global.mxSkinner = new Skinner();
|
||||
}
|
||||
module.exports = global.mxSkinner;
|
||||
|
312
src/SlashCommands.js
Normal file
312
src/SlashCommands.js
Normal file
@ -0,0 +1,312 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
var MatrixClientPeg = require("./MatrixClientPeg");
|
||||
var dis = require("./dispatcher");
|
||||
var encryption = require("./encryption");
|
||||
|
||||
var reject = function(msg) {
|
||||
return {
|
||||
error: msg
|
||||
};
|
||||
};
|
||||
|
||||
var success = function(promise) {
|
||||
return {
|
||||
promise: promise
|
||||
};
|
||||
};
|
||||
|
||||
var commands = {
|
||||
// Change your nickname
|
||||
nick: function(room_id, args) {
|
||||
if (args) {
|
||||
return success(
|
||||
MatrixClientPeg.get().setDisplayName(args)
|
||||
);
|
||||
}
|
||||
return reject("Usage: /nick <display_name>");
|
||||
},
|
||||
|
||||
encrypt: function(room_id, args) {
|
||||
if (args == "on") {
|
||||
var client = MatrixClientPeg.get();
|
||||
var members = client.getRoom(room_id).currentState.members;
|
||||
var user_ids = Object.keys(members);
|
||||
return success(
|
||||
encryption.enableEncryption(client, room_id, user_ids)
|
||||
);
|
||||
}
|
||||
if (args == "off") {
|
||||
var client = MatrixClientPeg.get();
|
||||
return success(
|
||||
encryption.disableEncryption(client, room_id)
|
||||
);
|
||||
|
||||
}
|
||||
return reject("Usage: encrypt <on/off>");
|
||||
},
|
||||
|
||||
// Change the room topic
|
||||
topic: function(room_id, args) {
|
||||
if (args) {
|
||||
return success(
|
||||
MatrixClientPeg.get().setRoomTopic(room_id, args)
|
||||
);
|
||||
}
|
||||
return reject("Usage: /topic <topic>");
|
||||
},
|
||||
|
||||
// Invite a user
|
||||
invite: function(room_id, args) {
|
||||
if (args) {
|
||||
var matches = args.match(/^(\S+)$/);
|
||||
if (matches) {
|
||||
return success(
|
||||
MatrixClientPeg.get().invite(room_id, matches[1])
|
||||
);
|
||||
}
|
||||
}
|
||||
return reject("Usage: /invite <userId>");
|
||||
},
|
||||
|
||||
// Join a room
|
||||
join: function(room_id, args) {
|
||||
if (args) {
|
||||
var matches = args.match(/^(\S+)$/);
|
||||
if (matches) {
|
||||
var room_alias = matches[1];
|
||||
if (room_alias[0] !== '#') {
|
||||
return reject("Usage: /join #alias:domain");
|
||||
}
|
||||
if (!room_alias.match(/:/)) {
|
||||
var domain = MatrixClientPeg.get().credentials.userId.replace(/^.*:/, '');
|
||||
room_alias += ':' + domain;
|
||||
}
|
||||
|
||||
// Try to find a room with this alias
|
||||
var rooms = MatrixClientPeg.get().getRooms();
|
||||
var roomId;
|
||||
for (var i = 0; i < rooms.length; i++) {
|
||||
var aliasEvents = rooms[i].currentState.getStateEvents(
|
||||
"m.room.aliases"
|
||||
);
|
||||
for (var j = 0; j < aliasEvents.length; j++) {
|
||||
var aliases = aliasEvents[j].getContent().aliases || [];
|
||||
for (var k = 0; k < aliases.length; k++) {
|
||||
if (aliases[k] === room_alias) {
|
||||
roomId = rooms[i].roomId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (roomId) { break; }
|
||||
}
|
||||
if (roomId) { break; }
|
||||
}
|
||||
if (roomId) { // we've already joined this room, view it.
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: roomId
|
||||
});
|
||||
return success();
|
||||
}
|
||||
else {
|
||||
// attempt to join this alias.
|
||||
return success(
|
||||
MatrixClientPeg.get().joinRoom(room_alias).then(
|
||||
function(room) {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: room.roomId
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return reject("Usage: /join <room_alias>");
|
||||
},
|
||||
|
||||
part: function(room_id, args) {
|
||||
var targetRoomId;
|
||||
if (args) {
|
||||
var matches = args.match(/^(\S+)$/);
|
||||
if (matches) {
|
||||
var room_alias = matches[1];
|
||||
if (room_alias[0] !== '#') {
|
||||
return reject("Usage: /part [#alias:domain]");
|
||||
}
|
||||
if (!room_alias.match(/:/)) {
|
||||
var domain = MatrixClientPeg.get().credentials.userId.replace(/^.*:/, '');
|
||||
room_alias += ':' + domain;
|
||||
}
|
||||
|
||||
// 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"
|
||||
);
|
||||
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) {
|
||||
targetRoomId = rooms[i].roomId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (targetRoomId) { break; }
|
||||
}
|
||||
if (targetRoomId) { break; }
|
||||
}
|
||||
}
|
||||
if (!targetRoomId) {
|
||||
return reject("Unrecognised room alias: " + room_alias);
|
||||
}
|
||||
}
|
||||
if (!targetRoomId) targetRoomId = room_id;
|
||||
return success(
|
||||
MatrixClientPeg.get().leave(targetRoomId).then(
|
||||
function() {
|
||||
dis.dispatch({action: 'view_next_room'});
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
// Kick a user from the room with an optional reason
|
||||
kick: function(room_id, args) {
|
||||
if (args) {
|
||||
var matches = args.match(/^(\S+?)( +(.*))?$/);
|
||||
if (matches) {
|
||||
return success(
|
||||
MatrixClientPeg.get().kick(room_id, matches[1], matches[3])
|
||||
);
|
||||
}
|
||||
}
|
||||
return reject("Usage: /kick <userId> [<reason>]");
|
||||
},
|
||||
|
||||
// Ban a user from the room with an optional reason
|
||||
ban: function(room_id, args) {
|
||||
if (args) {
|
||||
var matches = args.match(/^(\S+?)( +(.*))?$/);
|
||||
if (matches) {
|
||||
return success(
|
||||
MatrixClientPeg.get().ban(room_id, matches[1], matches[3])
|
||||
);
|
||||
}
|
||||
}
|
||||
return reject("Usage: /ban <userId> [<reason>]");
|
||||
},
|
||||
|
||||
// Unban a user from the room
|
||||
unban: function(room_id, args) {
|
||||
if (args) {
|
||||
var matches = args.match(/^(\S+)$/);
|
||||
if (matches) {
|
||||
// Reset the user membership to "leave" to unban him
|
||||
return success(
|
||||
MatrixClientPeg.get().unban(room_id, matches[1])
|
||||
);
|
||||
}
|
||||
}
|
||||
return reject("Usage: /unban <userId>");
|
||||
},
|
||||
|
||||
// Define the power level of a user
|
||||
op: function(room_id, args) {
|
||||
if (args) {
|
||||
var matches = args.match(/^(\S+?)( +(\d+))?$/);
|
||||
var powerLevel = 50; // default power level for op
|
||||
if (matches) {
|
||||
var user_id = matches[1];
|
||||
if (matches.length === 4 && undefined !== matches[3]) {
|
||||
powerLevel = parseInt(matches[3]);
|
||||
}
|
||||
if (powerLevel !== NaN) {
|
||||
var room = MatrixClientPeg.get().getRoom(room_id);
|
||||
if (!room) {
|
||||
return reject("Bad room ID: " + room_id);
|
||||
}
|
||||
var powerLevelEvent = room.currentState.getStateEvents(
|
||||
"m.room.power_levels", ""
|
||||
);
|
||||
return success(
|
||||
MatrixClientPeg.get().setPowerLevel(
|
||||
room_id, user_id, powerLevel, powerLevelEvent
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return reject("Usage: /op <userId> [<power level>]");
|
||||
},
|
||||
|
||||
// Reset the power level of a user
|
||||
deop: function(room_id, args) {
|
||||
if (args) {
|
||||
var matches = args.match(/^(\S+)$/);
|
||||
if (matches) {
|
||||
var room = MatrixClientPeg.get().getRoom(room_id);
|
||||
if (!room) {
|
||||
return reject("Bad room ID: " + room_id);
|
||||
}
|
||||
|
||||
var powerLevelEvent = room.currentState.getStateEvents(
|
||||
"m.room.power_levels", ""
|
||||
);
|
||||
return success(
|
||||
MatrixClientPeg.get().setPowerLevel(
|
||||
room_id, args, undefined, powerLevelEvent
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return reject("Usage: /deop <userId>");
|
||||
}
|
||||
};
|
||||
|
||||
// helpful aliases
|
||||
commands.j = commands.join;
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Process the given text for /commands and perform them.
|
||||
* @param {string} roomId The room in which the command was performed.
|
||||
* @param {string} input The raw text input by the user.
|
||||
* @return {Object|null} An object with the property 'error' if there was an error
|
||||
* processing the command, or 'promise' if a request was sent out.
|
||||
* Returns null if the input didn't match a command.
|
||||
*/
|
||||
processInput: function(roomId, input) {
|
||||
// trim any trailing whitespace, as it can confuse the parser for
|
||||
// IRC-style commands
|
||||
input = input.replace(/\s+$/, "");
|
||||
if (input[0] === "/" && input[1] !== "/") {
|
||||
var bits = input.match(/^(\S+?)( +(.*))?$/);
|
||||
var cmd = bits[1].substring(1).toLowerCase();
|
||||
var args = bits[3];
|
||||
if (cmd === "me") return null;
|
||||
if (commands[cmd]) {
|
||||
return commands[cmd](roomId, args);
|
||||
}
|
||||
else {
|
||||
return reject("Unrecognised command: " + input);
|
||||
}
|
||||
}
|
||||
return null; // not a command
|
||||
}
|
||||
};
|
106
src/TextForEvent.js
Normal file
106
src/TextForEvent.js
Normal file
@ -0,0 +1,106 @@
|
||||
|
||||
function textForMemberEvent(ev) {
|
||||
// XXX: SYJS-16
|
||||
var senderName = ev.sender ? ev.sender.name : ev.getSender();
|
||||
var targetName = ev.target ? ev.target.name : ev.getStateKey();
|
||||
var reason = ev.getContent().reason ? (
|
||||
" Reason: " + ev.getContent().reason
|
||||
) : "";
|
||||
switch (ev.getContent().membership) {
|
||||
case 'invite':
|
||||
return senderName + " invited " + targetName + ".";
|
||||
case 'ban':
|
||||
return senderName + " banned " + targetName + "." + reason;
|
||||
case 'join':
|
||||
if (ev.getPrevContent() && ev.getPrevContent().membership == 'join') {
|
||||
if (ev.getPrevContent().displayname && ev.getContent().displayname && ev.getPrevContent().displayname != ev.getContent().displayname) {
|
||||
return ev.getSender() + " changed their display name from " +
|
||||
ev.getPrevContent().displayname + " to " +
|
||||
ev.getContent().displayname;
|
||||
} else if (!ev.getPrevContent().displayname && ev.getContent().displayname) {
|
||||
return ev.getSender() + " set their display name to " + ev.getContent().displayname;
|
||||
} else if (ev.getPrevContent().displayname && !ev.getContent().displayname) {
|
||||
return ev.getSender() + " removed their display name";
|
||||
} else if (ev.getPrevContent().avatar_url && !ev.getContent().avatar_url) {
|
||||
return ev.getSender() + " removed their profile picture";
|
||||
} else if (ev.getPrevContent().avatar_url && ev.getContent().avatar_url && ev.getPrevContent().avatar_url != ev.getContent().avatar_url) {
|
||||
return ev.getSender() + " changed their profile picture";
|
||||
} else if (!ev.getPrevContent().avatar_url && ev.getContent().avatar_url) {
|
||||
return ev.getSender() + " set a profile picture";
|
||||
}
|
||||
} else {
|
||||
if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key);
|
||||
return targetName + " joined the room.";
|
||||
}
|
||||
return '';
|
||||
case 'leave':
|
||||
if (ev.getSender() === ev.getStateKey()) {
|
||||
return targetName + " left the room.";
|
||||
}
|
||||
else if (ev.getPrevContent().membership === "ban") {
|
||||
return senderName + " unbanned " + targetName + ".";
|
||||
}
|
||||
else if (ev.getPrevContent().membership === "join") {
|
||||
return senderName + " kicked " + targetName + "." + reason;
|
||||
}
|
||||
else {
|
||||
return targetName + " left the room.";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function textForTopicEvent(ev) {
|
||||
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
|
||||
|
||||
return senderDisplayName + ' changed the topic to, "' + ev.getContent().topic + '"';
|
||||
};
|
||||
|
||||
function textForMessageEvent(ev) {
|
||||
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
|
||||
|
||||
var message = senderDisplayName + ': ' + ev.getContent().body;
|
||||
if (ev.getContent().msgtype === "m.emote") {
|
||||
message = "* " + senderDisplayName + " " + message;
|
||||
} else if (ev.getContent().msgtype === "m.image") {
|
||||
message = senderDisplayName + " sent an image.";
|
||||
}
|
||||
return message;
|
||||
};
|
||||
|
||||
function textForCallAnswerEvent(event) {
|
||||
var senderName = event.sender ? event.sender.name : "Someone";
|
||||
return senderName + " answered the call.";
|
||||
};
|
||||
|
||||
function textForCallHangupEvent(event) {
|
||||
var senderName = event.sender ? event.sender.name : "Someone";
|
||||
return senderName + " ended the call.";
|
||||
};
|
||||
|
||||
function textForCallInviteEvent(event) {
|
||||
var senderName = event.sender ? event.sender.name : "Someone";
|
||||
// FIXME: Find a better way to determine this from the event?
|
||||
var type = "voice";
|
||||
if (event.getContent().offer && event.getContent().offer.sdp &&
|
||||
event.getContent().offer.sdp.indexOf('m=video') !== -1) {
|
||||
type = "video";
|
||||
}
|
||||
return senderName + " placed a " + type + " call.";
|
||||
};
|
||||
|
||||
var handlers = {
|
||||
'm.room.message': textForMessageEvent,
|
||||
'm.room.topic': textForTopicEvent,
|
||||
'm.room.member': textForMemberEvent,
|
||||
'm.call.invite': textForCallInviteEvent,
|
||||
'm.call.answer': textForCallAnswerEvent,
|
||||
'm.call.hangup': textForCallHangupEvent,
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
textForEvent: function(ev) {
|
||||
var hdlr = handlers[ev.getType()];
|
||||
if (!hdlr) return "";
|
||||
return hdlr(ev);
|
||||
}
|
||||
}
|
49
src/WhoIsTyping.js
Normal file
49
src/WhoIsTyping.js
Normal file
@ -0,0 +1,49 @@
|
||||
var MatrixClientPeg = require("./MatrixClientPeg");
|
||||
|
||||
module.exports = {
|
||||
usersTypingApartFromMe: function(room) {
|
||||
return this.usersTyping(
|
||||
room, [MatrixClientPeg.get().credentials.userId]
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Given a Room object and, optionally, a list of userID strings
|
||||
* to exclude, return a list of user objects who are typing.
|
||||
*/
|
||||
usersTyping: function(room, exclude) {
|
||||
var whoIsTyping = [];
|
||||
|
||||
if (exclude === undefined) {
|
||||
exclude = [];
|
||||
}
|
||||
|
||||
var memberKeys = Object.keys(room.currentState.members);
|
||||
for (var i = 0; i < memberKeys.length; ++i) {
|
||||
var userId = memberKeys[i];
|
||||
|
||||
if (room.currentState.members[userId].typing) {
|
||||
if (exclude.indexOf(userId) == -1) {
|
||||
whoIsTyping.push(room.currentState.members[userId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return whoIsTyping;
|
||||
},
|
||||
|
||||
whoIsTypingString: function(room) {
|
||||
var whoIsTyping = this.usersTypingApartFromMe(room);
|
||||
if (whoIsTyping.length == 0) {
|
||||
return null;
|
||||
} else if (whoIsTyping.length == 1) {
|
||||
return whoIsTyping[0].name + ' is typing';
|
||||
} else {
|
||||
var names = whoIsTyping.map(function(m) {
|
||||
return m.name;
|
||||
});
|
||||
var lastPerson = names.shift();
|
||||
return names.join(', ') + ' and ' + lastPerson + ' are typing';
|
||||
}
|
||||
}
|
||||
}
|
@ -21,7 +21,9 @@ var React = require('react');
|
||||
module.exports = {
|
||||
propTypes: {
|
||||
onValueChanged: React.PropTypes.func,
|
||||
initalValue: React.PropTypes.string,
|
||||
initialValue: React.PropTypes.string,
|
||||
label: React.PropTypes.string,
|
||||
placeHolder: React.PropTypes.string,
|
||||
},
|
||||
|
||||
Phases: {
|
||||
@ -32,37 +34,55 @@ module.exports = {
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
onValueChanged: function() {},
|
||||
initalValue: '',
|
||||
initialValue: '',
|
||||
label: 'Click to set',
|
||||
placeholder: '',
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
value: this.props.initalValue,
|
||||
value: this.props.initialValue,
|
||||
phase: this.Phases.Display,
|
||||
}
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
this.setState({
|
||||
value: nextProps.initialValue
|
||||
});
|
||||
},
|
||||
|
||||
getValue: function() {
|
||||
return this.state.value;
|
||||
},
|
||||
|
||||
setValue: function(val) {
|
||||
setValue: function(val, shouldSubmit, suppressListener) {
|
||||
var self = this;
|
||||
this.setState({
|
||||
value: val,
|
||||
phase: this.Phases.Display,
|
||||
}, function() {
|
||||
if (!suppressListener) {
|
||||
self.onValueChanged(shouldSubmit);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
this.onValueChanged();
|
||||
edit: function() {
|
||||
this.setState({
|
||||
phase: this.Phases.Edit,
|
||||
});
|
||||
},
|
||||
|
||||
cancelEdit: function() {
|
||||
this.setState({
|
||||
phase: this.Phases.Display,
|
||||
});
|
||||
this.onValueChanged(false);
|
||||
},
|
||||
|
||||
onValueChanged: function() {
|
||||
this.props.onValueChanged(this.state.value);
|
||||
onValueChanged: function(shouldSubmit) {
|
||||
this.props.onValueChanged(this.state.value, shouldSubmit);
|
||||
},
|
||||
};
|
||||
|
@ -15,53 +15,44 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
var sdk = require('../../index');
|
||||
var dis = require("../../dispatcher");
|
||||
|
||||
module.exports = {
|
||||
notificationsAvailable: function() {
|
||||
return !!global.Notification;
|
||||
|
||||
componentDidMount: function() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
},
|
||||
|
||||
havePermission: function() {
|
||||
return global.Notification.permission == 'granted';
|
||||
componentWillUnmount: function() {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
},
|
||||
|
||||
onAction: function(payload) {
|
||||
if (payload.action !== "notifier_enabled") {
|
||||
return;
|
||||
}
|
||||
this.forceUpdate();
|
||||
},
|
||||
|
||||
enabled: function() {
|
||||
if (!this.havePermission()) return false;
|
||||
|
||||
if (!global.localStorage) return true;
|
||||
|
||||
var enabled = global.localStorage.getItem('notifications_enabled');
|
||||
if (enabled === null) return true;
|
||||
return enabled === 'true';
|
||||
},
|
||||
|
||||
disable: function() {
|
||||
if (!global.localStorage) return;
|
||||
global.localStorage.setItem('notifications_enabled', 'false');
|
||||
this.forceUpdate();
|
||||
},
|
||||
|
||||
enable: function() {
|
||||
if (!this.havePermission()) {
|
||||
var that = this;
|
||||
global.Notification.requestPermission(function() {
|
||||
that.forceUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
if (!global.localStorage) return;
|
||||
global.localStorage.setItem('notifications_enabled', 'true');
|
||||
this.forceUpdate();
|
||||
var Notifier = sdk.getComponent('organisms.Notifier');
|
||||
return Notifier.isEnabled();
|
||||
},
|
||||
|
||||
onClick: function() {
|
||||
if (!this.notificationsAvailable()) {
|
||||
var Notifier = sdk.getComponent('organisms.Notifier');
|
||||
var self = this;
|
||||
if (!Notifier.supportsDesktopNotifications()) {
|
||||
return;
|
||||
}
|
||||
if (!this.enabled()) {
|
||||
this.enable();
|
||||
if (!Notifier.isEnabled()) {
|
||||
Notifier.setEnabled(true, function() {
|
||||
self.forceUpdate();
|
||||
});
|
||||
} else {
|
||||
this.disable();
|
||||
Notifier.setEnabled(false);
|
||||
}
|
||||
this.forceUpdate();
|
||||
},
|
||||
};
|
||||
|
75
src/controllers/atoms/MemberAvatar.js
Normal file
75
src/controllers/atoms/MemberAvatar.js
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var MatrixClientPeg = require('../../MatrixClientPeg');
|
||||
|
||||
module.exports = {
|
||||
propTypes: {
|
||||
member: React.PropTypes.object.isRequired,
|
||||
width: React.PropTypes.number,
|
||||
height: React.PropTypes.number,
|
||||
resizeMethod: React.PropTypes.string,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
width: 40,
|
||||
height: 40,
|
||||
resizeMethod: 'crop'
|
||||
}
|
||||
},
|
||||
|
||||
defaultAvatarUrl: function(member, width, height, resizeMethod) {
|
||||
if (this.skinnedDefaultAvatarUrl) {
|
||||
return this.skinnedDefaultAvatarUrl(member, width, height, resizeMethod);
|
||||
}
|
||||
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAIAAAADnC86AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADRJREFUeNrszQENADAIACB9QjNbxSKP4eagAFnTseHFErFYLBaLxWKxWCwWi8Vi8cX4CzAABSwCRWJw31gAAAAASUVORK5CYII=";
|
||||
},
|
||||
|
||||
onError: function(ev) {
|
||||
// don't tightloop if the browser can't load a data url
|
||||
if (ev.target.src == this.defaultAvatarUrl(this.props.member)) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
imageUrl: this.defaultAvatarUrl(this.props.member)
|
||||
});
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
var url = MatrixClientPeg.get().getAvatarUrlForMember(
|
||||
this.props.member,
|
||||
this.props.width,
|
||||
this.props.height,
|
||||
this.props.resizeMethod,
|
||||
false
|
||||
);
|
||||
if (!url) {
|
||||
url = this.defaultAvatarUrl(
|
||||
this.props.member,
|
||||
this.props.width,
|
||||
this.props.height,
|
||||
this.props.resizeMethod
|
||||
);
|
||||
}
|
||||
return {
|
||||
imageUrl: url
|
||||
};
|
||||
}
|
||||
};
|
61
src/controllers/atoms/RoomAvatar.js
Normal file
61
src/controllers/atoms/RoomAvatar.js
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var MatrixClientPeg = require('../../MatrixClientPeg');
|
||||
|
||||
module.exports = {
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
width: 40,
|
||||
height: 40,
|
||||
resizeMethod: 'crop'
|
||||
}
|
||||
},
|
||||
|
||||
avatarUrlForRoom: function(room) {
|
||||
var url = MatrixClientPeg.get().getAvatarUrlForRoom(
|
||||
room,
|
||||
this.props.width, this.props.height, this.props.resizeMethod,
|
||||
false
|
||||
);
|
||||
if (url === null) {
|
||||
url = this.defaultAvatarUrl(room);
|
||||
}
|
||||
return url;
|
||||
},
|
||||
|
||||
defaultAvatarUrl: function(member) {
|
||||
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAIAAAADnC86AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADRJREFUeNrszQENADAIACB9QjNbxSKP4eagAFnTseHFErFYLBaLxWKxWCwWi8Vi8cX4CzAABSwCRWJw31gAAAAASUVORK5CYII=";
|
||||
},
|
||||
|
||||
onError: function(ev) {
|
||||
// don't tightloop if the browser can't load a data url
|
||||
if (ev.target.src == this.defaultAvatarUrl(this.props.room)) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
imageUrl: this.defaultAvatarUrl(this.props.room)
|
||||
});
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
imageUrl: this.avatarUrlForRoom(this.props.room)
|
||||
};
|
||||
}
|
||||
};
|
@ -18,24 +18,23 @@ limitations under the License.
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var Presets = {
|
||||
PrivateChat: "private_chat",
|
||||
PublicChat: "public_chat",
|
||||
Custom: "custom",
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
propTypes: {
|
||||
default_preset: React.PropTypes.string
|
||||
onChange: React.PropTypes.func,
|
||||
preset: React.PropTypes.string
|
||||
},
|
||||
|
||||
Presets: Presets,
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
default_preset: 'private_chat',
|
||||
onChange: function() {},
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
preset: this.props.default_preset,
|
||||
}
|
||||
},
|
||||
|
||||
getPreset: function() {
|
||||
return this.state.preset;
|
||||
},
|
||||
};
|
||||
|
47
src/controllers/atoms/create_room/RoomAlias.js
Normal file
47
src/controllers/atoms/create_room/RoomAlias.js
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
var React = require('react');
|
||||
|
||||
module.exports = {
|
||||
propTypes: {
|
||||
// Specifying a homeserver will make magical things happen when you,
|
||||
// e.g. start typing in the room alias box.
|
||||
homeserver: React.PropTypes.string,
|
||||
alias: React.PropTypes.string,
|
||||
onChange: React.PropTypes.func,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
onChange: function() {},
|
||||
alias: '',
|
||||
};
|
||||
},
|
||||
|
||||
getAliasLocalpart: function() {
|
||||
var room_alias = this.props.alias;
|
||||
|
||||
if (room_alias && this.props.homeserver) {
|
||||
var suffix = ":" + this.props.homeserver;
|
||||
if (room_alias.startsWith("#") && room_alias.endsWith(suffix)) {
|
||||
room_alias = room_alias.slice(1, -suffix.length);
|
||||
}
|
||||
}
|
||||
|
||||
return room_alias;
|
||||
},
|
||||
};
|
@ -14,6 +14,6 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_MImageTile {
|
||||
}
|
||||
module.exports = {
|
||||
};
|
||||
|
67
src/controllers/molecules/ChangeAvatar.js
Normal file
67
src/controllers/molecules/ChangeAvatar.js
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
var React = require('react');
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
|
||||
module.exports = {
|
||||
propTypes: {
|
||||
onFinished: React.PropTypes.func,
|
||||
initialAvatarUrl: React.PropTypes.string.isRequired,
|
||||
},
|
||||
|
||||
Phases: {
|
||||
Display: "display",
|
||||
Uploading: "uploading",
|
||||
Error: "error",
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
onFinished: function() {},
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
avatarUrl: this.props.initialAvatarUrl,
|
||||
phase: this.Phases.Display,
|
||||
}
|
||||
},
|
||||
|
||||
setAvatarFromFile: function(file) {
|
||||
var newUrl = null;
|
||||
|
||||
this.setState({
|
||||
phase: this.Phases.Uploading
|
||||
});
|
||||
var self = this;
|
||||
MatrixClientPeg.get().uploadContent(file).then(function(url) {
|
||||
newUrl = url;
|
||||
return MatrixClientPeg.get().setAvatarUrl(url);
|
||||
}).done(function() {
|
||||
self.setState({
|
||||
phase: self.Phases.Display,
|
||||
avatarUrl: MatrixClientPeg.get().mxcUrlToHttp(newUrl)
|
||||
});
|
||||
}, function(error) {
|
||||
self.setState({
|
||||
phase: this.Phases.Error
|
||||
});
|
||||
self.onError(error);
|
||||
});
|
||||
},
|
||||
}
|
76
src/controllers/molecules/ChangePassword.js
Normal file
76
src/controllers/molecules/ChangePassword.js
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
|
||||
module.exports = {
|
||||
propTypes: {
|
||||
onFinished: React.PropTypes.func,
|
||||
},
|
||||
|
||||
Phases: {
|
||||
Edit: "edit",
|
||||
Uploading: "uploading",
|
||||
Error: "error",
|
||||
Success: "Success"
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
onFinished: function() {},
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
phase: this.Phases.Edit,
|
||||
errorString: ''
|
||||
}
|
||||
},
|
||||
|
||||
changePassword: function(old_password, new_password) {
|
||||
var cli = MatrixClientPeg.get();
|
||||
|
||||
var authDict = {
|
||||
type: 'm.login.password',
|
||||
user: cli.credentials.userId,
|
||||
password: old_password
|
||||
};
|
||||
|
||||
this.setState({
|
||||
phase: this.Phases.Uploading,
|
||||
errorString: '',
|
||||
})
|
||||
|
||||
var d = cli.setPassword(authDict, new_password);
|
||||
|
||||
var self = this;
|
||||
d.then(function() {
|
||||
self.setState({
|
||||
phase: self.Phases.Success,
|
||||
errorString: '',
|
||||
})
|
||||
}, function(err) {
|
||||
self.setState({
|
||||
phase: self.Phases.Error,
|
||||
errorString: err.toString()
|
||||
})
|
||||
});
|
||||
},
|
||||
}
|
@ -14,5 +14,6 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_RoomList {
|
||||
}
|
||||
module.exports = {
|
||||
};
|
||||
|
@ -16,6 +16,15 @@ limitations under the License.
|
||||
|
||||
'use strict';
|
||||
|
||||
var linkify = require('linkifyjs');
|
||||
var linkifyElement = require('linkifyjs/element');
|
||||
var linkifyMatrix = require('../../linkify-matrix');
|
||||
|
||||
linkifyMatrix(linkify);
|
||||
|
||||
module.exports = {
|
||||
componentDidMount: function() {
|
||||
linkifyElement(this.refs.content.getDOMNode(), linkifyMatrix.options);
|
||||
}
|
||||
};
|
||||
|
||||
|
317
src/controllers/molecules/MemberInfo.js
Normal file
317
src/controllers/molecules/MemberInfo.js
Normal file
@ -0,0 +1,317 @@
|
||||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* State vars:
|
||||
* 'can': {
|
||||
* kick: boolean,
|
||||
* ban: boolean,
|
||||
* mute: boolean,
|
||||
* modifyLevel: boolean
|
||||
* },
|
||||
* 'muted': boolean,
|
||||
* 'isTargetMod': boolean
|
||||
*/
|
||||
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
var dis = require("../../dispatcher");
|
||||
var Modal = require("../../Modal");
|
||||
var sdk = require('../../index');
|
||||
var Loader = require("react-loader");
|
||||
|
||||
module.exports = {
|
||||
componentDidMount: function() {
|
||||
// work out the current state
|
||||
if (this.props.member) {
|
||||
var memberState = this._calculateOpsPermissions();
|
||||
this.setState(memberState);
|
||||
}
|
||||
},
|
||||
|
||||
onKick: function() {
|
||||
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||
var roomId = this.props.member.roomId;
|
||||
var target = this.props.member.userId;
|
||||
MatrixClientPeg.get().kick(roomId, target).done(function() {
|
||||
// NO-OP; rely on the m.room.member event coming down else we could
|
||||
// get out of sync if we force setState here!
|
||||
console.log("Kick success");
|
||||
}, function(err) {
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Kick error",
|
||||
description: err.message
|
||||
});
|
||||
});
|
||||
this.props.onFinished();
|
||||
},
|
||||
|
||||
onBan: function() {
|
||||
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||
var roomId = this.props.member.roomId;
|
||||
var target = this.props.member.userId;
|
||||
MatrixClientPeg.get().ban(roomId, target).done(function() {
|
||||
// NO-OP; rely on the m.room.member event coming down else we could
|
||||
// get out of sync if we force setState here!
|
||||
console.log("Ban success");
|
||||
}, function(err) {
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Ban error",
|
||||
description: err.message
|
||||
});
|
||||
});
|
||||
this.props.onFinished();
|
||||
},
|
||||
|
||||
onMuteToggle: function() {
|
||||
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||
var roomId = this.props.member.roomId;
|
||||
var target = this.props.member.userId;
|
||||
var room = MatrixClientPeg.get().getRoom(roomId);
|
||||
if (!room) {
|
||||
this.props.onFinished();
|
||||
return;
|
||||
}
|
||||
var powerLevelEvent = room.currentState.getStateEvents(
|
||||
"m.room.power_levels", ""
|
||||
);
|
||||
if (!powerLevelEvent) {
|
||||
this.props.onFinished();
|
||||
return;
|
||||
}
|
||||
var isMuted = this.state.muted;
|
||||
var powerLevels = powerLevelEvent.getContent();
|
||||
var levelToSend = (
|
||||
(powerLevels.events ? powerLevels.events["m.room.message"] : null) ||
|
||||
powerLevels.events_default
|
||||
);
|
||||
var level;
|
||||
if (isMuted) { // unmute
|
||||
level = levelToSend;
|
||||
}
|
||||
else { // mute
|
||||
level = levelToSend - 1;
|
||||
}
|
||||
|
||||
MatrixClientPeg.get().setPowerLevel(roomId, target, level, powerLevelEvent).done(
|
||||
function() {
|
||||
// NO-OP; rely on the m.room.member event coming down else we could
|
||||
// get out of sync if we force setState here!
|
||||
console.log("Mute toggle success");
|
||||
}, function(err) {
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Mute error",
|
||||
description: err.message
|
||||
});
|
||||
});
|
||||
this.props.onFinished();
|
||||
},
|
||||
|
||||
onModToggle: function() {
|
||||
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||
var roomId = this.props.member.roomId;
|
||||
var target = this.props.member.userId;
|
||||
var room = MatrixClientPeg.get().getRoom(roomId);
|
||||
if (!room) {
|
||||
this.props.onFinished();
|
||||
return;
|
||||
}
|
||||
var powerLevelEvent = room.currentState.getStateEvents(
|
||||
"m.room.power_levels", ""
|
||||
);
|
||||
if (!powerLevelEvent) {
|
||||
this.props.onFinished();
|
||||
return;
|
||||
}
|
||||
var me = room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||
if (!me) {
|
||||
this.props.onFinished();
|
||||
return;
|
||||
}
|
||||
var defaultLevel = powerLevelEvent.getContent().users_default;
|
||||
var modLevel = me.powerLevel - 1;
|
||||
// toggle the level
|
||||
var newLevel = this.state.isTargetMod ? defaultLevel : modLevel;
|
||||
MatrixClientPeg.get().setPowerLevel(roomId, target, newLevel, powerLevelEvent).done(
|
||||
function() {
|
||||
// NO-OP; rely on the m.room.member event coming down else we could
|
||||
// get out of sync if we force setState here!
|
||||
console.log("Mod toggle success");
|
||||
}, function(err) {
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Mod error",
|
||||
description: err.message
|
||||
});
|
||||
});
|
||||
this.props.onFinished();
|
||||
},
|
||||
|
||||
onChatClick: function() {
|
||||
// check if there are any existing rooms with just us and them (1:1)
|
||||
// If so, just view that room. If not, create a private room with them.
|
||||
var rooms = MatrixClientPeg.get().getRooms();
|
||||
var userIds = [
|
||||
this.props.member.userId,
|
||||
MatrixClientPeg.get().credentials.userId
|
||||
];
|
||||
var existingRoomId = null;
|
||||
for (var i = 0; i < rooms.length; i++) {
|
||||
var members = rooms[i].getJoinedMembers();
|
||||
if (members.length === 2) {
|
||||
var hasTargetUsers = true;
|
||||
for (var j = 0; j < members.length; j++) {
|
||||
if (userIds.indexOf(members[j].userId) === -1) {
|
||||
hasTargetUsers = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hasTargetUsers) {
|
||||
existingRoomId = rooms[i].roomId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (existingRoomId) {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: existingRoomId
|
||||
});
|
||||
}
|
||||
else {
|
||||
MatrixClientPeg.get().createRoom({
|
||||
invite: [this.props.member.userId],
|
||||
preset: "private_chat"
|
||||
}).done(function(res) {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: res.room_id
|
||||
});
|
||||
}, function(err) {
|
||||
console.error(
|
||||
"Failed to create room: %s", JSON.stringify(err)
|
||||
);
|
||||
});
|
||||
}
|
||||
this.props.onFinished();
|
||||
},
|
||||
|
||||
// FIXME: this is horribly duplicated with MemberTile's onLeaveClick.
|
||||
// Not sure what the right solution to this is.
|
||||
onLeaveClick: function() {
|
||||
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||
var QuestionDialog = sdk.getComponent("organisms.QuestionDialog");
|
||||
|
||||
var roomId = this.props.member.roomId;
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Leave room",
|
||||
description: "Are you sure you want to leave the room?",
|
||||
onFinished: function(should_leave) {
|
||||
if (should_leave) {
|
||||
var d = MatrixClientPeg.get().leave(roomId);
|
||||
|
||||
var modal = Modal.createDialog(Loader);
|
||||
|
||||
d.then(function() {
|
||||
modal.close();
|
||||
dis.dispatch({action: 'view_next_room'});
|
||||
}, function(err) {
|
||||
modal.close();
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to leave room",
|
||||
description: err.toString()
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
this.props.onFinished();
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
can: {
|
||||
kick: false,
|
||||
ban: false,
|
||||
mute: false,
|
||||
modifyLevel: false
|
||||
},
|
||||
muted: false,
|
||||
isTargetMod: false
|
||||
}
|
||||
},
|
||||
|
||||
_calculateOpsPermissions: function() {
|
||||
var defaultPerms = {
|
||||
can: {},
|
||||
muted: false,
|
||||
modifyLevel: false
|
||||
};
|
||||
var room = MatrixClientPeg.get().getRoom(this.props.member.roomId);
|
||||
if (!room) {
|
||||
return defaultPerms;
|
||||
}
|
||||
var powerLevels = room.currentState.getStateEvents(
|
||||
"m.room.power_levels", ""
|
||||
);
|
||||
if (!powerLevels) {
|
||||
return defaultPerms;
|
||||
}
|
||||
var me = room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||
var them = this.props.member;
|
||||
return {
|
||||
can: this._calculateCanPermissions(
|
||||
me, them, powerLevels.getContent()
|
||||
),
|
||||
muted: this._isMuted(them, powerLevels.getContent()),
|
||||
isTargetMod: them.powerLevel > powerLevels.getContent().users_default
|
||||
};
|
||||
},
|
||||
|
||||
_calculateCanPermissions: function(me, them, powerLevels) {
|
||||
var can = {
|
||||
kick: false,
|
||||
ban: false,
|
||||
mute: false,
|
||||
modifyLevel: false
|
||||
};
|
||||
var canAffectUser = them.powerLevel < me.powerLevel;
|
||||
if (!canAffectUser) {
|
||||
//console.log("Cannot affect user: %s >= %s", them.powerLevel, me.powerLevel);
|
||||
return can;
|
||||
}
|
||||
var editPowerLevel = (
|
||||
(powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) ||
|
||||
powerLevels.state_default
|
||||
);
|
||||
can.kick = me.powerLevel >= powerLevels.kick;
|
||||
can.ban = me.powerLevel >= powerLevels.ban;
|
||||
can.mute = me.powerLevel >= editPowerLevel;
|
||||
can.modifyLevel = me.powerLevel > them.powerLevel;
|
||||
return can;
|
||||
},
|
||||
|
||||
_isMuted: function(member, powerLevelContent) {
|
||||
if (!powerLevelContent || !member) {
|
||||
return false;
|
||||
}
|
||||
var levelToSend = (
|
||||
(powerLevelContent.events ? powerLevelContent.events["m.room.message"] : null) ||
|
||||
powerLevelContent.events_default
|
||||
);
|
||||
return member.powerLevel < levelToSend;
|
||||
}
|
||||
};
|
||||
|
@ -17,14 +17,42 @@ limitations under the License.
|
||||
'use strict';
|
||||
|
||||
var dis = require("../../dispatcher");
|
||||
var Modal = require("../../Modal");
|
||||
var sdk = require('../../index.js');
|
||||
var Loader = require("react-loader");
|
||||
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
|
||||
module.exports = {
|
||||
onClick: function() {
|
||||
dis.dispatch({
|
||||
action: 'view_user',
|
||||
user_id: this.props.member.userId
|
||||
});
|
||||
getInitialState: function() {
|
||||
return {};
|
||||
},
|
||||
|
||||
onLeaveClick: function() {
|
||||
var QuestionDialog = sdk.getComponent("organisms.QuestionDialog");
|
||||
|
||||
var roomId = this.props.member.roomId;
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "Leave room",
|
||||
description: "Are you sure you want to leave the room?",
|
||||
onFinished: function(should_leave) {
|
||||
if (should_leave) {
|
||||
var d = MatrixClientPeg.get().leave(roomId);
|
||||
|
||||
var modal = Modal.createDialog(Loader);
|
||||
|
||||
d.then(function() {
|
||||
modal.close();
|
||||
dis.dispatch({action: 'view_next_room'});
|
||||
}, function(err) {
|
||||
modal.close();
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to leave room",
|
||||
description: err.toString()
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -14,19 +14,130 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
var SlashCommands = require("../../SlashCommands");
|
||||
var Modal = require("../../Modal");
|
||||
var sdk = require('../../index');
|
||||
|
||||
var dis = require("../../dispatcher");
|
||||
var KeyCode = {
|
||||
ENTER: 13,
|
||||
TAB: 9,
|
||||
SHIFT: 16,
|
||||
UP: 38,
|
||||
DOWN: 40
|
||||
};
|
||||
|
||||
var TYPING_USER_TIMEOUT = 10000;
|
||||
var TYPING_SERVER_TIMEOUT = 30000;
|
||||
|
||||
module.exports = {
|
||||
componentWillMount: function() {
|
||||
this.tabStruct = {
|
||||
completing: false,
|
||||
original: null,
|
||||
index: 0
|
||||
};
|
||||
this.sentHistory = {
|
||||
// The list of typed messages. Index 0 is more recent
|
||||
data: [],
|
||||
// The position in data currently displayed
|
||||
position: -1,
|
||||
// The room the history is for.
|
||||
roomId: null,
|
||||
// The original text before they hit UP
|
||||
originalText: null,
|
||||
// The textarea element to set text to.
|
||||
element: null,
|
||||
|
||||
init: function(element, roomId) {
|
||||
this.roomId = roomId;
|
||||
this.element = element;
|
||||
this.position = -1;
|
||||
var storedData = window.sessionStorage.getItem(
|
||||
"history_" + roomId
|
||||
);
|
||||
if (storedData) {
|
||||
this.data = JSON.parse(storedData);
|
||||
}
|
||||
if (this.roomId) {
|
||||
this.setLastTextEntry();
|
||||
}
|
||||
},
|
||||
|
||||
push: function(text) {
|
||||
// store a message in the sent history
|
||||
this.data.unshift(text);
|
||||
window.sessionStorage.setItem(
|
||||
"history_" + this.roomId,
|
||||
JSON.stringify(this.data)
|
||||
);
|
||||
// reset history position
|
||||
this.position = -1;
|
||||
this.originalText = null;
|
||||
},
|
||||
|
||||
// move in the history. Returns true if we managed to move.
|
||||
next: function(offset) {
|
||||
if (this.position === -1) {
|
||||
// user is going into the history, save the current line.
|
||||
this.originalText = this.element.value;
|
||||
}
|
||||
else {
|
||||
// user may have modified this line in the history; remember it.
|
||||
this.data[this.position] = this.element.value;
|
||||
}
|
||||
|
||||
if (offset > 0 && this.position === (this.data.length - 1)) {
|
||||
// we've run out of history
|
||||
return false;
|
||||
}
|
||||
|
||||
// retrieve the next item (bounded).
|
||||
var newPosition = this.position + offset;
|
||||
newPosition = Math.max(-1, newPosition);
|
||||
newPosition = Math.min(newPosition, this.data.length - 1);
|
||||
this.position = newPosition;
|
||||
|
||||
if (this.position !== -1) {
|
||||
// show the message
|
||||
this.element.value = this.data[this.position];
|
||||
}
|
||||
else if (this.originalText !== undefined) {
|
||||
// restore the original text the user was typing.
|
||||
this.element.value = this.originalText;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
saveLastTextEntry: function() {
|
||||
// save the currently entered text in order to restore it later.
|
||||
// NB: This isn't 'originalText' because we want to restore
|
||||
// sent history items too!
|
||||
var text = this.element.value;
|
||||
window.sessionStorage.setItem("input_" + this.roomId, text);
|
||||
},
|
||||
|
||||
setLastTextEntry: function() {
|
||||
var text = window.sessionStorage.getItem("input_" + this.roomId);
|
||||
if (text) {
|
||||
this.element.value = text;
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
this.sentHistory.init(
|
||||
this.refs.textarea.getDOMNode(),
|
||||
this.props.room.roomId
|
||||
);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
this.sentHistory.saveLastTextEntry();
|
||||
},
|
||||
|
||||
onAction: function(payload) {
|
||||
@ -38,30 +149,265 @@ module.exports = {
|
||||
},
|
||||
|
||||
onKeyDown: function (ev) {
|
||||
if (ev.keyCode == 13) {
|
||||
var contentText = this.refs.textarea.getDOMNode().value;
|
||||
|
||||
var content = null;
|
||||
if (/^\/me /i.test(contentText)) {
|
||||
content = {
|
||||
msgtype: 'm.emote',
|
||||
body: contentText.substring(4)
|
||||
};
|
||||
} else {
|
||||
content = {
|
||||
msgtype: 'm.text',
|
||||
body: contentText
|
||||
};
|
||||
if (ev.keyCode === KeyCode.ENTER) {
|
||||
var input = this.refs.textarea.getDOMNode().value;
|
||||
if (input.length === 0) {
|
||||
ev.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
MatrixClientPeg.get().sendMessage(this.props.roomId, content).then(function() {
|
||||
dis.dispatch({
|
||||
action: 'message_sent'
|
||||
});
|
||||
});
|
||||
this.refs.textarea.getDOMNode().value = '';
|
||||
this.sentHistory.push(input);
|
||||
this.onEnter(ev);
|
||||
}
|
||||
else if (ev.keyCode === KeyCode.TAB) {
|
||||
var members = [];
|
||||
if (this.props.room) {
|
||||
members = this.props.room.getJoinedMembers();
|
||||
}
|
||||
this.onTab(ev, members);
|
||||
}
|
||||
else if (ev.keyCode === KeyCode.UP || ev.keyCode === KeyCode.DOWN) {
|
||||
this.sentHistory.next(
|
||||
ev.keyCode === KeyCode.UP ? 1 : -1
|
||||
);
|
||||
ev.preventDefault();
|
||||
}
|
||||
else if (ev.keyCode !== KeyCode.SHIFT && this.tabStruct.completing) {
|
||||
// they're resuming typing; reset tab complete state vars.
|
||||
this.tabStruct.completing = false;
|
||||
this.tabStruct.index = 0;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
setTimeout(function() {
|
||||
if (self.refs.textarea && self.refs.textarea.getDOMNode().value != '') {
|
||||
self.onTypingActivity();
|
||||
} else {
|
||||
self.onFinishedTyping();
|
||||
}
|
||||
}, 10); // XXX: what is this 10ms setTimeout doing? Looks hacky :(
|
||||
},
|
||||
|
||||
onEnter: function(ev) {
|
||||
var contentText = this.refs.textarea.getDOMNode().value;
|
||||
|
||||
var cmd = SlashCommands.processInput(this.props.room.roomId, contentText);
|
||||
if (cmd) {
|
||||
ev.preventDefault();
|
||||
if (!cmd.error) {
|
||||
this.refs.textarea.getDOMNode().value = '';
|
||||
}
|
||||
if (cmd.promise) {
|
||||
cmd.promise.done(function() {
|
||||
console.log("Command success.");
|
||||
}, function(err) {
|
||||
console.error("Command failure: %s", err);
|
||||
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Server error",
|
||||
description: err.message
|
||||
});
|
||||
});
|
||||
}
|
||||
else if (cmd.error) {
|
||||
console.error(cmd.error);
|
||||
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Command error",
|
||||
description: cmd.error
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var content = null;
|
||||
if (/^\/me /i.test(contentText)) {
|
||||
content = {
|
||||
msgtype: 'm.emote',
|
||||
body: contentText.substring(4)
|
||||
};
|
||||
} else {
|
||||
content = {
|
||||
msgtype: 'm.text',
|
||||
body: contentText
|
||||
};
|
||||
}
|
||||
|
||||
MatrixClientPeg.get().sendMessage(this.props.room.roomId, content).then(function() {
|
||||
dis.dispatch({
|
||||
action: 'message_sent'
|
||||
});
|
||||
}, function() {
|
||||
dis.dispatch({
|
||||
action: 'message_send_failed'
|
||||
});
|
||||
});
|
||||
this.refs.textarea.getDOMNode().value = '';
|
||||
ev.preventDefault();
|
||||
},
|
||||
|
||||
onTab: function(ev, sortedMembers) {
|
||||
var textArea = this.refs.textarea.getDOMNode();
|
||||
if (!this.tabStruct.completing) {
|
||||
this.tabStruct.completing = true;
|
||||
this.tabStruct.index = 0;
|
||||
// cache starting text
|
||||
this.tabStruct.original = textArea.value;
|
||||
}
|
||||
|
||||
// loop in the right direction
|
||||
if (ev.shiftKey) {
|
||||
this.tabStruct.index --;
|
||||
if (this.tabStruct.index < 0) {
|
||||
// wrap to the last search match, and fix up to a real index
|
||||
// value after we've matched.
|
||||
this.tabStruct.index = Number.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.tabStruct.index++;
|
||||
}
|
||||
|
||||
var searchIndex = 0;
|
||||
var targetIndex = this.tabStruct.index;
|
||||
var text = this.tabStruct.original;
|
||||
|
||||
var search = /@?([a-zA-Z0-9_\-:\.]+)$/.exec(text);
|
||||
// console.log("Searched in '%s' - got %s", text, search);
|
||||
if (targetIndex === 0) { // 0 is always the original text
|
||||
textArea.value = text;
|
||||
}
|
||||
else if (search && search[1]) {
|
||||
// console.log("search found: " + search+" from "+text);
|
||||
var expansion;
|
||||
|
||||
// FIXME: could do better than linear search here
|
||||
for (var i=0; i<sortedMembers.length; i++) {
|
||||
var member = sortedMembers[i];
|
||||
if (member.name && searchIndex < targetIndex) {
|
||||
if (member.name.toLowerCase().indexOf(search[1].toLowerCase()) === 0) {
|
||||
expansion = member.name;
|
||||
searchIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (searchIndex < targetIndex) { // then search raw mxids
|
||||
for (var i=0; i<sortedMembers.length; i++) {
|
||||
if (searchIndex >= targetIndex) {
|
||||
break;
|
||||
}
|
||||
var userId = sortedMembers[i].userId;
|
||||
// === 1 because mxids are @username
|
||||
if (userId.toLowerCase().indexOf(search[1].toLowerCase()) === 1) {
|
||||
expansion = userId;
|
||||
searchIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (searchIndex === targetIndex ||
|
||||
targetIndex === Number.MAX_VALUE) {
|
||||
// xchat-style tab complete, add a colon if tab
|
||||
// completing at the start of the text
|
||||
if (search[0].length === text.length) {
|
||||
expansion += ": ";
|
||||
}
|
||||
else {
|
||||
expansion += " ";
|
||||
}
|
||||
textArea.value = text.replace(
|
||||
/@?([a-zA-Z0-9_\-:\.]+)$/, expansion
|
||||
);
|
||||
// cancel blink
|
||||
textArea.style["background-color"] = "";
|
||||
if (targetIndex === Number.MAX_VALUE) {
|
||||
// wrap the index around to the last index found
|
||||
this.tabStruct.index = searchIndex;
|
||||
targetIndex = searchIndex;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// console.log("wrapped!");
|
||||
textArea.style["background-color"] = "#faa";
|
||||
setTimeout(function() {
|
||||
textArea.style["background-color"] = "";
|
||||
}, 150);
|
||||
textArea.value = text;
|
||||
this.tabStruct.index = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.tabStruct.index = 0;
|
||||
}
|
||||
// prevent the default TAB operation (typically focus shifting)
|
||||
ev.preventDefault();
|
||||
},
|
||||
|
||||
onTypingActivity: function() {
|
||||
this.isTyping = true;
|
||||
if (!this.userTypingTimer) {
|
||||
this.sendTyping(true);
|
||||
}
|
||||
this.startUserTypingTimer();
|
||||
this.startServerTypingTimer();
|
||||
},
|
||||
|
||||
onFinishedTyping: function() {
|
||||
this.isTyping = false;
|
||||
this.sendTyping(false);
|
||||
this.stopUserTypingTimer();
|
||||
this.stopServerTypingTimer();
|
||||
},
|
||||
|
||||
startUserTypingTimer: function() {
|
||||
this.stopUserTypingTimer();
|
||||
var self = this;
|
||||
this.userTypingTimer = setTimeout(function() {
|
||||
self.isTyping = false;
|
||||
self.sendTyping(self.isTyping);
|
||||
self.userTypingTimer = null;
|
||||
}, TYPING_USER_TIMEOUT);
|
||||
},
|
||||
|
||||
stopUserTypingTimer: function() {
|
||||
if (this.userTypingTimer) {
|
||||
clearTimeout(this.userTypingTimer);
|
||||
this.userTypingTimer = null;
|
||||
}
|
||||
},
|
||||
|
||||
startServerTypingTimer: function() {
|
||||
if (!this.serverTypingTimer) {
|
||||
var self = this;
|
||||
this.serverTypingTimer = setTimeout(function() {
|
||||
if (self.isTyping) {
|
||||
self.sendTyping(self.isTyping);
|
||||
self.startServerTypingTimer();
|
||||
}
|
||||
}, TYPING_SERVER_TIMEOUT / 2);
|
||||
}
|
||||
},
|
||||
|
||||
stopServerTypingTimer: function() {
|
||||
if (this.serverTypingTimer) {
|
||||
clearTimeout(this.servrTypingTimer);
|
||||
this.serverTypingTimer = null;
|
||||
}
|
||||
},
|
||||
|
||||
sendTyping: function(isTyping) {
|
||||
MatrixClientPeg.get().sendTyping(
|
||||
this.props.room.roomId,
|
||||
this.isTyping, TYPING_SERVER_TIMEOUT
|
||||
).done();
|
||||
},
|
||||
|
||||
refreshTyping: function() {
|
||||
if (this.typingTimeout) {
|
||||
clearTimeout(this.typingTimeout);
|
||||
this.typingTimeout = null;
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -23,6 +23,28 @@ module.exports = {
|
||||
var actions = MatrixClientPeg.get().getPushActionsForEvent(this.props.mxEvent);
|
||||
if (!actions || !actions.tweaks) { return false; }
|
||||
return actions.tweaks.highlight;
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
resending: false
|
||||
};
|
||||
},
|
||||
|
||||
onResend: function() {
|
||||
var self = this;
|
||||
self.setState({
|
||||
resending: true
|
||||
});
|
||||
MatrixClientPeg.get().resendEvent(
|
||||
this.props.mxEvent, MatrixClientPeg.get().getRoom(
|
||||
this.props.mxEvent.getRoomId()
|
||||
)
|
||||
).finally(function() {
|
||||
self.setState({
|
||||
resending: false
|
||||
});
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -16,6 +16,81 @@ limitations under the License.
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
};
|
||||
/*
|
||||
* State vars:
|
||||
* this.state.call_state = the UI state of the call (see CallHandler)
|
||||
*/
|
||||
|
||||
var React = require('react');
|
||||
var dis = require("../../dispatcher");
|
||||
var CallHandler = require("../../CallHandler");
|
||||
|
||||
module.exports = {
|
||||
propTypes: {
|
||||
room: React.PropTypes.object.isRequired,
|
||||
editing: React.PropTypes.bool,
|
||||
onSettingsClick: React.PropTypes.func,
|
||||
onSaveClick: React.PropTypes.func,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
editing: false,
|
||||
onSettingsClick: function() {},
|
||||
onSaveClick: function() {},
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
if (this.props.room) {
|
||||
var call = CallHandler.getCallForRoom(this.props.room.roomId);
|
||||
var callState = call ? call.call_state : "ended";
|
||||
this.setState({
|
||||
call_state: callState
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
},
|
||||
|
||||
onAction: function(payload) {
|
||||
// don't filter out payloads for room IDs other than props.room because
|
||||
// we may be interested in the conf 1:1 room
|
||||
if (payload.action !== 'call_state' || !payload.room_id) {
|
||||
return;
|
||||
}
|
||||
var call = CallHandler.getCallForRoom(payload.room_id);
|
||||
var callState = call ? call.call_state : "ended";
|
||||
this.setState({
|
||||
call_state: callState
|
||||
});
|
||||
},
|
||||
|
||||
onVideoClick: function() {
|
||||
dis.dispatch({
|
||||
action: 'place_call',
|
||||
type: "video",
|
||||
room_id: this.props.room.roomId
|
||||
});
|
||||
},
|
||||
onVoiceClick: function() {
|
||||
dis.dispatch({
|
||||
action: 'place_call',
|
||||
type: "voice",
|
||||
room_id: this.props.room.roomId
|
||||
});
|
||||
},
|
||||
onHangupClick: function() {
|
||||
var call = CallHandler.getCallForRoom(this.props.room.roomId);
|
||||
if (!call) { return; }
|
||||
dis.dispatch({
|
||||
action: 'hangup',
|
||||
// hangup the call for this room, which may not be the room in props
|
||||
// (e.g. conferences which will hangup the 1:1 room instead)
|
||||
room_id: call.roomId
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -14,22 +14,16 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_MessageTile {
|
||||
display: table-row;
|
||||
}
|
||||
var React = require('react');
|
||||
|
||||
.mx_MessageTile_content {
|
||||
display: table-cell;
|
||||
}
|
||||
module.exports = {
|
||||
propTypes: {
|
||||
room: React.PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
.mx_MessageTile_sending {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.mx_MessageTile_notSent {
|
||||
color: #f11;
|
||||
}
|
||||
|
||||
.mx_MessageTile_highlight {
|
||||
color: #00f;
|
||||
}
|
||||
getInitialState: function() {
|
||||
return {
|
||||
power_levels_changed: false
|
||||
};
|
||||
}
|
||||
};
|
@ -30,26 +30,28 @@ module.exports = {
|
||||
return {
|
||||
onHsUrlChanged: function() {},
|
||||
onIsUrlChanged: function() {},
|
||||
default_hs_url: 'https://matrix.org/',
|
||||
default_is_url: 'https://matrix.org/'
|
||||
defaultHsUrl: 'https://matrix.org/',
|
||||
defaultIsUrl: 'https://matrix.org/'
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
hs_url: this.props.default_hs_url,
|
||||
is_url: this.props.default_is_url,
|
||||
hs_url: this.props.defaultHsUrl,
|
||||
is_url: this.props.defaultIsUrl,
|
||||
}
|
||||
},
|
||||
|
||||
hsChanged: function(ev) {
|
||||
this.setState({hs_url: ev.target.value});
|
||||
this.props.onHsUrlChanged(this.state.hs_url);
|
||||
this.setState({hs_url: ev.target.value}, function() {
|
||||
this.props.onHsUrlChanged(this.state.hs_url);
|
||||
});
|
||||
},
|
||||
|
||||
isChanged: function(ev) {
|
||||
this.setState({is_url: ev.target.value});
|
||||
this.props.onIsUrlChanged(this.state.is_url);
|
||||
this.setState({is_url: ev.target.value}, function() {
|
||||
this.props.onIsUrlChanged(this.state.is_url);
|
||||
});
|
||||
},
|
||||
|
||||
getHsUrl: function() {
|
||||
|
@ -20,38 +20,26 @@ var React = require('react');
|
||||
|
||||
module.exports = {
|
||||
propTypes: {
|
||||
initially_selected: React.PropTypes.arrayOf(React.PropTypes.string),
|
||||
onChange: React.PropTypes.func,
|
||||
selected_users: React.PropTypes.arrayOf(React.PropTypes.string),
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
initially_selected: [],
|
||||
onChange: function() {},
|
||||
selected: [],
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
selected_users: this.props.initially_selected,
|
||||
}
|
||||
},
|
||||
|
||||
addUser: function(user_id) {
|
||||
if (this.state.selected_users.indexOf(user_id == -1)) {
|
||||
this.setState({
|
||||
selected_users: this.state.selected_users.concat([user_id]),
|
||||
});
|
||||
if (this.props.selected_users.indexOf(user_id == -1)) {
|
||||
this.props.onChange(this.props.selected_users.concat([user_id]));
|
||||
}
|
||||
},
|
||||
|
||||
removeUser: function(user_id) {
|
||||
this.setState({
|
||||
selected_users: this.state.selected_users.filter(function(e) {
|
||||
return e != user_id;
|
||||
}),
|
||||
});
|
||||
this.props.onChange(this.props.selected_users.filter(function(e) {
|
||||
return e != user_id;
|
||||
}));
|
||||
},
|
||||
|
||||
getUserIds: function() {
|
||||
return this.state.selected_users;
|
||||
}
|
||||
};
|
||||
|
70
src/controllers/molecules/voip/CallView.js
Normal file
70
src/controllers/molecules/voip/CallView.js
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
var dis = require("../../../dispatcher");
|
||||
var CallHandler = require("../../../CallHandler");
|
||||
|
||||
/*
|
||||
* State vars:
|
||||
* this.state.call = MatrixCall|null
|
||||
*
|
||||
* Props:
|
||||
* this.props.room = Room (JS SDK)
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
|
||||
componentDidMount: function() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
if (this.props.room) {
|
||||
this.showCall(this.props.room.roomId);
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
},
|
||||
|
||||
onAction: function(payload) {
|
||||
// if we were given a room_id to track, don't handle anything else.
|
||||
if (payload.room_id && this.props.room &&
|
||||
this.props.room.roomId !== payload.room_id) {
|
||||
return;
|
||||
}
|
||||
if (payload.action !== 'call_state') {
|
||||
return;
|
||||
}
|
||||
this.showCall(payload.room_id);
|
||||
},
|
||||
|
||||
showCall: function(roomId) {
|
||||
var call = CallHandler.getCall(roomId);
|
||||
if (call) {
|
||||
call.setLocalVideoElement(this.getVideoView().getLocalVideoElement());
|
||||
// N.B. the remote video element is used for playback for audio for voice calls
|
||||
call.setRemoteVideoElement(this.getVideoView().getRemoteVideoElement());
|
||||
}
|
||||
if (call && call.type === "video" && call.state !== 'ended') {
|
||||
this.getVideoView().getLocalVideoElement().style.display = "initial";
|
||||
this.getVideoView().getRemoteVideoElement().style.display = "initial";
|
||||
}
|
||||
else {
|
||||
this.getVideoView().getLocalVideoElement().style.display = "none";
|
||||
this.getVideoView().getRemoteVideoElement().style.display = "none";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
73
src/controllers/molecules/voip/IncomingCallBox.js
Normal file
73
src/controllers/molecules/voip/IncomingCallBox.js
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
var dis = require("../../../dispatcher");
|
||||
var CallHandler = require("../../../CallHandler");
|
||||
|
||||
module.exports = {
|
||||
componentDidMount: function() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
incomingCall: null
|
||||
}
|
||||
},
|
||||
|
||||
onAction: function(payload) {
|
||||
if (payload.action !== 'call_state') {
|
||||
return;
|
||||
}
|
||||
var call = CallHandler.getCall(payload.room_id);
|
||||
if (!call || call.call_state !== 'ringing') {
|
||||
this.setState({
|
||||
incomingCall: null,
|
||||
});
|
||||
this.getRingAudio().pause();
|
||||
return;
|
||||
}
|
||||
if (call.call_state === "ringing") {
|
||||
this.getRingAudio().load();
|
||||
this.getRingAudio().play();
|
||||
}
|
||||
else {
|
||||
this.getRingAudio().pause();
|
||||
}
|
||||
|
||||
this.setState({
|
||||
incomingCall: call
|
||||
});
|
||||
},
|
||||
|
||||
onAnswerClick: function() {
|
||||
dis.dispatch({
|
||||
action: 'answer',
|
||||
room_id: this.state.incomingCall.roomId
|
||||
});
|
||||
},
|
||||
onRejectClick: function() {
|
||||
dis.dispatch({
|
||||
action: 'hangup',
|
||||
room_id: this.state.incomingCall.roomId
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -14,5 +14,6 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_MNoticeTile {
|
||||
}
|
||||
module.exports = {
|
||||
};
|
||||
|
@ -18,6 +18,9 @@ limitations under the License.
|
||||
|
||||
var React = require("react");
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
var PresetValues = require('../atoms/create_room/Presets').Presets;
|
||||
var q = require('q');
|
||||
var encryption = require("../../encryption");
|
||||
|
||||
module.exports = {
|
||||
propTypes: {
|
||||
@ -41,25 +44,52 @@ module.exports = {
|
||||
return {
|
||||
phase: this.phases.CONFIG,
|
||||
error_string: "",
|
||||
is_private: true,
|
||||
share_history: false,
|
||||
default_preset: PresetValues.PrivateChat,
|
||||
topic: '',
|
||||
room_name: '',
|
||||
invited_users: [],
|
||||
};
|
||||
},
|
||||
|
||||
onCreateRoom: function() {
|
||||
var options = {};
|
||||
|
||||
var room_name = this.getName();
|
||||
if (room_name) {
|
||||
options.name = room_name;
|
||||
if (this.state.room_name) {
|
||||
options.name = this.state.room_name;
|
||||
}
|
||||
|
||||
var preset = this.getPreset();
|
||||
if (preset) {
|
||||
options.preset = preset;
|
||||
if (this.state.topic) {
|
||||
options.topic = this.state.topic;
|
||||
}
|
||||
|
||||
var invited_users = this.getInvitedUsers();
|
||||
if (invited_users) {
|
||||
options.invite = invited_users;
|
||||
if (this.state.preset) {
|
||||
if (this.state.preset != PresetValues.Custom) {
|
||||
options.preset = this.state.preset;
|
||||
} else {
|
||||
options.initial_state = [
|
||||
{
|
||||
type: "m.room.join_rules",
|
||||
content: {
|
||||
"join_rules": this.state.is_private ? "invite" : "public"
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "m.room.history_visibility",
|
||||
content: {
|
||||
"history_visibility": this.state.share_history ? "shared" : "invited"
|
||||
}
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
options.invite = this.state.invited_users;
|
||||
|
||||
var alias = this.getAliasLocalpart();
|
||||
if (alias) {
|
||||
options.room_alias_name = alias;
|
||||
}
|
||||
|
||||
var cli = MatrixClientPeg.get();
|
||||
@ -69,7 +99,20 @@ module.exports = {
|
||||
return;
|
||||
}
|
||||
|
||||
var deferred = MatrixClientPeg.get().createRoom(options);
|
||||
var deferred = cli.createRoom(options);
|
||||
|
||||
var response;
|
||||
|
||||
if (this.state.encrypt) {
|
||||
deferred = deferred.then(function(res) {
|
||||
response = res;
|
||||
return encryption.enableEncryption(
|
||||
cli, response.roomId, options.invite
|
||||
);
|
||||
}).then(function() {
|
||||
return q(response) }
|
||||
);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
phase: this.phases.CREATING,
|
||||
@ -77,11 +120,11 @@ module.exports = {
|
||||
|
||||
var self = this;
|
||||
|
||||
deferred.then(function () {
|
||||
deferred.then(function (resp) {
|
||||
self.setState({
|
||||
phase: self.phases.CREATED,
|
||||
});
|
||||
self.props.onRoomCreated();
|
||||
self.props.onRoomCreated(resp.room_id);
|
||||
}, function(err) {
|
||||
self.setState({
|
||||
phase: self.phases.ERROR,
|
||||
|
@ -14,22 +14,23 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
var React = require("react");
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var RoomHeaderController = require("../../../../src/controllers/molecules/RoomHeader");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'RoomHeader',
|
||||
mixins: [RoomHeaderController],
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div className="mx_RoomHeader">
|
||||
{this.props.room.name}
|
||||
</div>
|
||||
);
|
||||
module.exports = {
|
||||
propTypes: {
|
||||
title: React.PropTypes.string,
|
||||
description: React.PropTypes.string,
|
||||
button: React.PropTypes.string,
|
||||
focus: React.PropTypes.bool,
|
||||
onFinished: React.PropTypes.func.isRequired,
|
||||
},
|
||||
});
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
title: "Error",
|
||||
description: "An error has occurred.",
|
||||
button: "OK",
|
||||
focus: true,
|
||||
};
|
||||
},
|
||||
};
|
@ -14,7 +14,20 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_MTextTile {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
var dis = require("../../dispatcher");
|
||||
|
||||
module.exports = {
|
||||
logOut: function() {
|
||||
dis.dispatch({action: 'logout'});
|
||||
if (this.props.onFinished) {
|
||||
this.props.onFinished();
|
||||
}
|
||||
},
|
||||
|
||||
cancelPrompt: function() {
|
||||
if (this.props.onFinished) {
|
||||
this.props.onFinished();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -14,10 +14,9 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require("react");
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
var Modal = require("../../Modal");
|
||||
var sdk = require('../../index');
|
||||
|
||||
var INITIAL_LOAD_NUM_MEMBERS = 50;
|
||||
|
||||
@ -32,39 +31,137 @@ module.exports = {
|
||||
componentWillMount: function() {
|
||||
var cli = MatrixClientPeg.get();
|
||||
cli.on("RoomState.members", this.onRoomStateMember);
|
||||
cli.on("Room", this.onRoom); // invites
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
if (MatrixClientPeg.get()) {
|
||||
MatrixClientPeg.get().removeListener("Room", this.onRoom);
|
||||
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
|
||||
MatrixClientPeg.get().removeListener("User.presence", this.userPresenceFn);
|
||||
}
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
var that = this;
|
||||
var self = this;
|
||||
|
||||
// Lazy-load in more than the first N members
|
||||
setTimeout(function() {
|
||||
if (!that.isMounted()) return;
|
||||
that.setState({
|
||||
memberDict: that.roomMembers()
|
||||
if (!self.isMounted()) return;
|
||||
self.setState({
|
||||
memberDict: self.roomMembers()
|
||||
});
|
||||
}, 50);
|
||||
},
|
||||
|
||||
// Attach a SINGLE listener for global presence changes then locate the
|
||||
// member tile and re-render it. This is more efficient than every tile
|
||||
// evar attaching their own listener.
|
||||
function updateUserState(event, user) {
|
||||
// XXX: evil hack to track the age of this presence info.
|
||||
// this should be removed once syjs-28 is resolved in the JS SDK itself.
|
||||
user.lastPresenceTs = Date.now();
|
||||
|
||||
var tile = self.refs[user.userId];
|
||||
|
||||
console.log("presence event " + JSON.stringify(event) + " user = " + user + " tile = " + tile);
|
||||
|
||||
if (tile) {
|
||||
self._updateList(); // reorder the membership list
|
||||
self.forceUpdate(); // FIXME: is the a more efficient way of reordering with react?
|
||||
// XXX: do we even need to do this, or is it done by the main list?
|
||||
tile.forceUpdate();
|
||||
}
|
||||
}
|
||||
// FIXME: we should probably also reset 'lastActiveAgo' to zero whenever
|
||||
// we see a typing notif from a user, as we don't get presence updates for those.
|
||||
MatrixClientPeg.get().on("User.presence", updateUserState);
|
||||
this.userPresenceFn = updateUserState;
|
||||
},
|
||||
// Remember to set 'key' on a MemberList to the ID of the room it's for
|
||||
/*componentWillReceiveProps: function(newProps) {
|
||||
},*/
|
||||
|
||||
onRoom: function(room) {
|
||||
if (room.roomId !== this.props.roomId) {
|
||||
return;
|
||||
}
|
||||
// We listen for room events because when we accept an invite
|
||||
// we need to wait till the room is fully populated with state
|
||||
// before refreshing the member list else we get a stale list.
|
||||
this._updateList();
|
||||
},
|
||||
|
||||
onRoomStateMember: function(ev, state, member) {
|
||||
this._updateList();
|
||||
},
|
||||
|
||||
_updateList: function() {
|
||||
var members = this.roomMembers();
|
||||
this.setState({
|
||||
memberDict: members
|
||||
});
|
||||
},
|
||||
|
||||
onInvite: function(inputText) {
|
||||
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||
var self = this;
|
||||
// sanity check the input
|
||||
inputText = inputText.trim(); // react requires es5-shim so we know trim() exists
|
||||
if (inputText[0] !== '@' || inputText.indexOf(":") === -1) {
|
||||
console.error("Bad user ID to invite: %s", inputText);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Invite Error",
|
||||
description: "Malformed user ID. Should look like '@localpart:domain'"
|
||||
});
|
||||
return;
|
||||
}
|
||||
self.setState({
|
||||
inviting: true
|
||||
});
|
||||
console.log("Invite %s to %s", inputText, this.props.roomId);
|
||||
MatrixClientPeg.get().invite(this.props.roomId, inputText).done(
|
||||
function(res) {
|
||||
console.log("Invited");
|
||||
self.setState({
|
||||
inviting: false
|
||||
});
|
||||
}, function(err) {
|
||||
console.error("Failed to invite: %s", JSON.stringify(err));
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Server error whilst inviting",
|
||||
description: err.message
|
||||
});
|
||||
self.setState({
|
||||
inviting: false
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
roomMembers: function(limit) {
|
||||
if (!this.props.roomId) return {};
|
||||
var cli = MatrixClientPeg.get();
|
||||
var all_members = cli.getRoom(this.props.roomId).currentState.members;
|
||||
var room = cli.getRoom(this.props.roomId);
|
||||
if (!room) return {};
|
||||
var all_members = room.currentState.members;
|
||||
var all_user_ids = Object.keys(all_members);
|
||||
|
||||
// XXX: evil hack until SYJS-28 is fixed
|
||||
all_user_ids.map(function(userId) {
|
||||
if (all_members[userId].user && !all_members[userId].user.lastPresenceTs) {
|
||||
all_members[userId].user.lastPresenceTs = Date.now();
|
||||
}
|
||||
});
|
||||
|
||||
all_user_ids.sort(function(userIdA, userIdB) {
|
||||
var userA = all_members[userIdA].user;
|
||||
var userB = all_members[userIdB].user;
|
||||
|
||||
var latA = userA ? (userA.lastPresenceTs - (userA.lastActiveAgo || userA.lastPresenceTs)) : 0;
|
||||
var latB = userB ? (userB.lastPresenceTs - (userB.lastActiveAgo || userB.lastPresenceTs)) : 0;
|
||||
|
||||
return latB - latA;
|
||||
});
|
||||
|
||||
var to_display = {};
|
||||
var count = 0;
|
||||
for (var i = 0; i < all_user_ids.length && (limit === undefined || count < limit); ++i) {
|
||||
@ -72,6 +169,8 @@ module.exports = {
|
||||
var m = all_members[user_id];
|
||||
|
||||
if (m.membership == 'join' || m.membership == 'invite') {
|
||||
// XXX: this is evil, and relies on the fact that Object.keys() iterates
|
||||
// over the keys of a dict in insertion order (if those keys are strings)
|
||||
to_display[user_id] = m;
|
||||
++count;
|
||||
}
|
||||
|
@ -17,11 +17,21 @@ limitations under the License.
|
||||
'use strict';
|
||||
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
var dis = require("../../dispatcher");
|
||||
|
||||
/*
|
||||
* Dispatches:
|
||||
* {
|
||||
* action: "notifier_enabled",
|
||||
* value: boolean
|
||||
* }
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
start: function() {
|
||||
this.boundOnRoomTimeline = this.onRoomTimeline.bind(this);
|
||||
MatrixClientPeg.get().on('Room.timeline', this.boundOnRoomTimeline);
|
||||
this.state = { 'toolbarHidden' : false };
|
||||
},
|
||||
|
||||
stop: function() {
|
||||
@ -30,12 +40,80 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
supportsDesktopNotifications: function() {
|
||||
return !!global.Notification;
|
||||
},
|
||||
|
||||
havePermission: function() {
|
||||
if (!this.supportsDesktopNotifications()) return false;
|
||||
return global.Notification.permission == 'granted';
|
||||
},
|
||||
|
||||
setEnabled: function(enable, callback) {
|
||||
if(enable) {
|
||||
if (!this.havePermission()) {
|
||||
global.Notification.requestPermission(function() {
|
||||
if (callback) {
|
||||
callback();
|
||||
dis.dispatch({
|
||||
action: "notifier_enabled",
|
||||
value: true
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!global.localStorage) return;
|
||||
global.localStorage.setItem('notifications_enabled', 'true');
|
||||
|
||||
if (this.havePermission) {
|
||||
dis.dispatch({
|
||||
action: "notifier_enabled",
|
||||
value: true
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!global.localStorage) return;
|
||||
global.localStorage.setItem('notifications_enabled', 'false');
|
||||
dis.dispatch({
|
||||
action: "notifier_enabled",
|
||||
value: false
|
||||
});
|
||||
}
|
||||
|
||||
this.setToolbarHidden(false);
|
||||
},
|
||||
|
||||
isEnabled: function() {
|
||||
if (!this.havePermission()) return false;
|
||||
|
||||
if (!global.localStorage) return true;
|
||||
|
||||
var enabled = global.localStorage.getItem('notifications_enabled');
|
||||
if (enabled === null) return true;
|
||||
return enabled === 'true';
|
||||
},
|
||||
|
||||
setToolbarHidden: function(hidden) {
|
||||
this.state.toolbarHidden = hidden;
|
||||
dis.dispatch({
|
||||
action: "notifier_enabled",
|
||||
value: this.isEnabled()
|
||||
});
|
||||
},
|
||||
|
||||
isToolbarHidden: function() {
|
||||
return this.state.toolbarHidden;
|
||||
},
|
||||
|
||||
onRoomTimeline: function(ev, room, toStartOfTimeline) {
|
||||
if (toStartOfTimeline) return;
|
||||
if (ev.sender && ev.sender.userId == MatrixClientPeg.get().credentials.userId) return;
|
||||
|
||||
var enabled = global.localStorage.getItem('notifications_enabled');
|
||||
if (enabled === 'false') return;
|
||||
if (!this.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
|
||||
if (actions && actions.notify) {
|
||||
|
@ -14,22 +14,23 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
var React = require("react");
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var MessageComposerController = require("../../../../src/controllers/molecules/MessageComposer");
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MessageComposer',
|
||||
mixins: [MessageComposerController],
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div className="mx_MessageComposer">
|
||||
<textarea ref="textarea" onKeyDown={this.onKeyDown} />
|
||||
</div>
|
||||
);
|
||||
module.exports = {
|
||||
propTypes: {
|
||||
title: React.PropTypes.string,
|
||||
description: React.PropTypes.string,
|
||||
button: React.PropTypes.string,
|
||||
focus: React.PropTypes.bool,
|
||||
onFinished: React.PropTypes.func.isRequired,
|
||||
},
|
||||
});
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
title: "",
|
||||
description: "",
|
||||
button: "OK",
|
||||
focus: true,
|
||||
};
|
||||
},
|
||||
};
|
@ -20,9 +20,7 @@ var React = require("react");
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
var RoomListSorter = require("../../RoomListSorter");
|
||||
|
||||
var ComponentBroker = require('../../ComponentBroker');
|
||||
|
||||
var RoomTile = ComponentBroker.get("molecules/RoomTile");
|
||||
var sdk = require('../../index');
|
||||
|
||||
module.exports = {
|
||||
componentWillMount: function() {
|
||||
@ -73,13 +71,11 @@ module.exports = {
|
||||
if (actions && actions.tweaks && actions.tweaks.highlight) {
|
||||
hl = 2;
|
||||
}
|
||||
if (actions.notify) {
|
||||
// obviously this won't deep copy but this shouldn't be necessary
|
||||
var amap = this.state.activityMap;
|
||||
amap[room.roomId] = Math.max(amap[room.roomId] || 0, hl);
|
||||
// obviously this won't deep copy but this shouldn't be necessary
|
||||
var amap = this.state.activityMap;
|
||||
amap[room.roomId] = Math.max(amap[room.roomId] || 0, hl);
|
||||
|
||||
newState.activityMap = amap;
|
||||
}
|
||||
newState.activityMap = amap;
|
||||
}
|
||||
this.setState(newState);
|
||||
},
|
||||
@ -96,23 +92,28 @@ module.exports = {
|
||||
},
|
||||
|
||||
getRoomList: function() {
|
||||
return RoomListSorter.mostRecentActivityFirst(MatrixClientPeg.get().getRooms());
|
||||
return RoomListSorter.mostRecentActivityFirst(
|
||||
MatrixClientPeg.get().getRooms().filter(function(room) {
|
||||
var member = room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||
return member && (member.membership == "join" || member.membership == "invite");
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
makeRoomTiles: function() {
|
||||
var that = this;
|
||||
var RoomTile = sdk.getComponent('molecules.RoomTile');
|
||||
var self = this;
|
||||
return this.state.roomList.map(function(room) {
|
||||
var selected = room.roomId == that.props.selectedRoom;
|
||||
var selected = room.roomId == self.props.selectedRoom;
|
||||
return (
|
||||
<RoomTile
|
||||
room={room}
|
||||
key={room.roomId}
|
||||
selected={selected}
|
||||
unread={that.state.activityMap[room.roomId] === 1}
|
||||
highlight={that.state.activityMap[room.roomId] === 2}
|
||||
unread={self.state.activityMap[room.roomId] === 1}
|
||||
highlight={self.state.activityMap[room.roomId] === 2}
|
||||
/>
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -14,36 +14,36 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
var React = require("react");
|
||||
var q = require("q");
|
||||
var ContentMessages = require("../../ContentMessages");
|
||||
var WhoIsTyping = require("../../WhoIsTyping");
|
||||
var Modal = require("../../Modal");
|
||||
var sdk = require('../../index');
|
||||
|
||||
var dis = require("../../dispatcher");
|
||||
|
||||
var PAGINATE_SIZE = 20;
|
||||
var INITIAL_SIZE = 100;
|
||||
|
||||
var ComponentBroker = require('../../ComponentBroker');
|
||||
|
||||
var tileTypes = {
|
||||
'm.room.message': ComponentBroker.get('molecules/MessageTile'),
|
||||
'm.room.member': ComponentBroker.get('molecules/MRoomMemberTile')
|
||||
};
|
||||
var INITIAL_SIZE = 20;
|
||||
|
||||
module.exports = {
|
||||
getInitialState: function() {
|
||||
return {
|
||||
room: this.props.roomId ? MatrixClientPeg.get().getRoom(this.props.roomId) : null,
|
||||
messageCap: INITIAL_SIZE
|
||||
messageCap: INITIAL_SIZE,
|
||||
editingRoomSettings: false,
|
||||
uploadingRoomSettings: false,
|
||||
numUnreadMessages: 0,
|
||||
draggingFile: false,
|
||||
}
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
|
||||
MatrixClientPeg.get().on("Room.name", this.onRoomName);
|
||||
MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping);
|
||||
this.atBottom = true;
|
||||
},
|
||||
|
||||
@ -51,19 +51,40 @@ module.exports = {
|
||||
if (this.refs.messageWrapper) {
|
||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
||||
messageWrapper.removeEventListener('drop', this.onDrop);
|
||||
messageWrapper.removeEventListener('dragover', this.onDragOver);
|
||||
messageWrapper.removeEventListener('dragleave', this.onDragLeaveOrEnd);
|
||||
messageWrapper.removeEventListener('dragend', this.onDragLeaveOrEnd);
|
||||
}
|
||||
dis.unregister(this.dispatcherRef);
|
||||
if (MatrixClientPeg.get()) {
|
||||
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
|
||||
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
|
||||
MatrixClientPeg.get().removeListener("RoomMember.typing", this.onRoomMemberTyping);
|
||||
}
|
||||
},
|
||||
|
||||
onAction: function(payload) {
|
||||
switch (payload.action) {
|
||||
case 'message_send_failed':
|
||||
case 'message_sent':
|
||||
this.setState({
|
||||
room: MatrixClientPeg.get().getRoom(this.props.roomId)
|
||||
});
|
||||
this.forceUpdate();
|
||||
break;
|
||||
case 'notifier_enabled':
|
||||
this.forceUpdate();
|
||||
break;
|
||||
case 'call_state':
|
||||
if (this.props.roomId !== payload.room_id) {
|
||||
break;
|
||||
}
|
||||
// scroll to bottom
|
||||
var messageWrapper = this.refs.messageWrapper;
|
||||
if (messageWrapper) {
|
||||
messageWrapper = messageWrapper.getDOMNode();
|
||||
messageWrapper.scrollTop = messageWrapper.scrollHeight;
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
@ -87,13 +108,31 @@ module.exports = {
|
||||
// we'll only be showing a spinner.
|
||||
if (this.state.joining) return;
|
||||
if (room.roomId != this.props.roomId) return;
|
||||
|
||||
|
||||
if (this.refs.messageWrapper) {
|
||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
||||
this.atBottom = messageWrapper.scrollHeight - messageWrapper.scrollTop <= messageWrapper.clientHeight;
|
||||
this.atBottom = (
|
||||
messageWrapper.scrollHeight - messageWrapper.scrollTop <=
|
||||
(messageWrapper.clientHeight + 150)
|
||||
);
|
||||
}
|
||||
|
||||
var currentUnread = this.state.numUnreadMessages;
|
||||
if (!toStartOfTimeline &&
|
||||
(ev.getSender() !== MatrixClientPeg.get().credentials.userId)) {
|
||||
// update unread count when scrolled up
|
||||
if (this.atBottom) {
|
||||
currentUnread = 0;
|
||||
}
|
||||
else {
|
||||
currentUnread += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.setState({
|
||||
room: MatrixClientPeg.get().getRoom(this.props.roomId)
|
||||
room: MatrixClientPeg.get().getRoom(this.props.roomId),
|
||||
numUnreadMessages: currentUnread
|
||||
});
|
||||
|
||||
if (toStartOfTimeline && !this.state.paginating) {
|
||||
@ -101,12 +140,26 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
onRoomName: function(room) {
|
||||
if (room.roomId == this.props.roomId) {
|
||||
this.setState({
|
||||
room: room
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onRoomMemberTyping: function(ev, member) {
|
||||
this.forceUpdate();
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
if (this.refs.messageWrapper) {
|
||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
||||
|
||||
messageWrapper.addEventListener('drop', this.onDrop);
|
||||
messageWrapper.addEventListener('dragover', this.onDragOver);
|
||||
messageWrapper.addEventListener('dragleave', this.onDragLeaveOrEnd);
|
||||
messageWrapper.addEventListener('dragend', this.onDragLeaveOrEnd);
|
||||
|
||||
messageWrapper.scrollTop = messageWrapper.scrollHeight;
|
||||
|
||||
@ -128,10 +181,14 @@ module.exports = {
|
||||
}
|
||||
} else if (this.atBottom) {
|
||||
messageWrapper.scrollTop = messageWrapper.scrollHeight;
|
||||
if (this.state.numUnreadMessages !== 0) {
|
||||
this.setState({numUnreadMessages: 0});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
fillSpace: function() {
|
||||
if (!this.refs.messageWrapper) return;
|
||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
||||
if (messageWrapper.scrollTop < messageWrapper.clientHeight && this.state.room.oldState.paginationToken) {
|
||||
this.setState({paginating: true});
|
||||
@ -146,12 +203,12 @@ module.exports = {
|
||||
this.waiting_for_paginate = true;
|
||||
var cap = this.state.messageCap + PAGINATE_SIZE;
|
||||
this.setState({messageCap: cap, paginating: true});
|
||||
var that = this;
|
||||
var self = this;
|
||||
MatrixClientPeg.get().scrollback(this.state.room, PAGINATE_SIZE).finally(function() {
|
||||
that.waiting_for_paginate = false;
|
||||
if (that.isMounted()) {
|
||||
that.setState({
|
||||
room: MatrixClientPeg.get().getRoom(that.props.roomId)
|
||||
self.waiting_for_paginate = false;
|
||||
if (self.isMounted()) {
|
||||
self.setState({
|
||||
room: MatrixClientPeg.get().getRoom(self.props.roomId)
|
||||
});
|
||||
}
|
||||
// wait and set paginating to false when the component updates
|
||||
@ -164,14 +221,14 @@ module.exports = {
|
||||
},
|
||||
|
||||
onJoinButtonClicked: function(ev) {
|
||||
var that = this;
|
||||
var self = this;
|
||||
MatrixClientPeg.get().joinRoom(this.props.roomId).then(function() {
|
||||
that.setState({
|
||||
self.setState({
|
||||
joining: false,
|
||||
room: MatrixClientPeg.get().getRoom(that.props.roomId)
|
||||
room: MatrixClientPeg.get().getRoom(self.props.roomId)
|
||||
});
|
||||
}, function(error) {
|
||||
that.setState({
|
||||
self.setState({
|
||||
joining: false,
|
||||
joinError: error
|
||||
});
|
||||
@ -184,7 +241,11 @@ module.exports = {
|
||||
onMessageListScroll: function(ev) {
|
||||
if (this.refs.messageWrapper) {
|
||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
||||
var wasAtBottom = this.atBottom;
|
||||
this.atBottom = messageWrapper.scrollHeight - messageWrapper.scrollTop <= messageWrapper.clientHeight;
|
||||
if (this.atBottom && !wasAtBottom) {
|
||||
this.forceUpdate(); // remove unread msg count
|
||||
}
|
||||
}
|
||||
if (!this.state.paginating) this.fillSpace();
|
||||
},
|
||||
@ -198,6 +259,7 @@ module.exports = {
|
||||
var items = ev.dataTransfer.items;
|
||||
if (items.length == 1) {
|
||||
if (items[0].kind == 'file') {
|
||||
this.setState({ draggingFile : true });
|
||||
ev.dataTransfer.dropEffect = 'copy';
|
||||
}
|
||||
}
|
||||
@ -206,33 +268,178 @@ module.exports = {
|
||||
onDrop: function(ev) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.setState({ draggingFile : false });
|
||||
var files = ev.dataTransfer.files;
|
||||
|
||||
if (files.length == 1) {
|
||||
ContentMessages.sendContentToRoom(
|
||||
files[0], this.props.roomId, MatrixClientPeg.get()
|
||||
).progress(function(ev) {
|
||||
//console.log("Upload: "+ev.loaded+" / "+ev.total);
|
||||
}).done(undefined, function() {
|
||||
// display error message
|
||||
});
|
||||
this.uploadFile(files[0]);
|
||||
}
|
||||
},
|
||||
|
||||
onDragLeaveOrEnd: function(ev) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.setState({ draggingFile : false });
|
||||
},
|
||||
|
||||
uploadFile: function(file) {
|
||||
this.setState({
|
||||
upload: {
|
||||
fileName: file.name,
|
||||
uploadedBytes: 0,
|
||||
totalBytes: file.size
|
||||
}
|
||||
});
|
||||
var self = this;
|
||||
ContentMessages.sendContentToRoom(
|
||||
file, this.props.roomId, MatrixClientPeg.get()
|
||||
).progress(function(ev) {
|
||||
//console.log("Upload: "+ev.loaded+" / "+ev.total);
|
||||
self.setState({
|
||||
upload: {
|
||||
fileName: file.name,
|
||||
uploadedBytes: ev.loaded,
|
||||
totalBytes: ev.total
|
||||
}
|
||||
});
|
||||
}).finally(function() {
|
||||
self.setState({
|
||||
upload: undefined
|
||||
});
|
||||
}).done(undefined, function() {
|
||||
// display error message
|
||||
});
|
||||
},
|
||||
|
||||
getWhoIsTypingString: function() {
|
||||
return WhoIsTyping.whoIsTypingString(this.state.room);
|
||||
},
|
||||
|
||||
getEventTiles: function() {
|
||||
var tileTypes = {
|
||||
'm.room.message': sdk.getComponent('molecules.MessageTile'),
|
||||
'm.room.member' : sdk.getComponent('molecules.EventAsTextTile'),
|
||||
'm.call.invite' : sdk.getComponent('molecules.EventAsTextTile'),
|
||||
'm.call.answer' : sdk.getComponent('molecules.EventAsTextTile'),
|
||||
'm.call.hangup' : sdk.getComponent('molecules.EventAsTextTile'),
|
||||
'm.room.topic' : sdk.getComponent('molecules.EventAsTextTile'),
|
||||
};
|
||||
|
||||
var ret = [];
|
||||
var count = 0;
|
||||
|
||||
for (var i = this.state.room.timeline.length-1; i >= 0 && count < this.state.messageCap; --i) {
|
||||
var mxEv = this.state.room.timeline[i];
|
||||
var TileType = tileTypes[mxEv.getType()];
|
||||
var continuation = false;
|
||||
var last = false;
|
||||
if (i == this.state.room.timeline.length - 1) {
|
||||
last = true;
|
||||
}
|
||||
if (i > 0 && count < this.state.messageCap - 1) {
|
||||
if (this.state.room.timeline[i].sender &&
|
||||
this.state.room.timeline[i - 1].sender &&
|
||||
(this.state.room.timeline[i].sender.userId ===
|
||||
this.state.room.timeline[i - 1].sender.userId) &&
|
||||
(this.state.room.timeline[i].getType() ==
|
||||
this.state.room.timeline[i - 1].getType())
|
||||
)
|
||||
{
|
||||
continuation = true;
|
||||
}
|
||||
}
|
||||
if (!TileType) continue;
|
||||
ret.unshift(
|
||||
<TileType key={mxEv.getId()} mxEvent={mxEv} />
|
||||
<li key={mxEv.getId()}><TileType mxEvent={mxEv} continuation={continuation} last={last}/></li>
|
||||
);
|
||||
++count;
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
|
||||
uploadNewState: function(new_name, new_topic, new_join_rule, new_history_visibility, new_power_levels) {
|
||||
var old_name = this.state.room.name;
|
||||
|
||||
var old_topic = this.state.room.currentState.getStateEvents('m.room.topic', '');
|
||||
if (old_topic) {
|
||||
old_topic = old_topic.getContent().topic;
|
||||
} else {
|
||||
old_topic = "";
|
||||
}
|
||||
|
||||
var old_join_rule = this.state.room.currentState.getStateEvents('m.room.join_rules', '');
|
||||
if (old_join_rule) {
|
||||
old_join_rule = old_join_rule.getContent().join_rule;
|
||||
} else {
|
||||
old_join_rule = "invite";
|
||||
}
|
||||
|
||||
var old_history_visibility = this.state.room.currentState.getStateEvents('m.room.history_visibility', '');
|
||||
if (old_history_visibility) {
|
||||
old_history_visibility = old_history_visibility.getContent().history_visibility;
|
||||
} else {
|
||||
old_history_visibility = "shared";
|
||||
}
|
||||
|
||||
var deferreds = [];
|
||||
|
||||
if (old_name != new_name && new_name != undefined && new_name) {
|
||||
deferreds.push(
|
||||
MatrixClientPeg.get().setRoomName(this.state.room.roomId, new_name)
|
||||
);
|
||||
}
|
||||
|
||||
if (old_topic != new_topic && new_topic != undefined) {
|
||||
deferreds.push(
|
||||
MatrixClientPeg.get().setRoomTopic(this.state.room.roomId, new_topic)
|
||||
);
|
||||
}
|
||||
|
||||
if (old_join_rule != new_join_rule && new_join_rule != undefined) {
|
||||
deferreds.push(
|
||||
MatrixClientPeg.get().sendStateEvent(
|
||||
this.state.room.roomId, "m.room.join_rules", {
|
||||
join_rule: new_join_rule,
|
||||
}, ""
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (old_history_visibility != new_history_visibility && new_history_visibility != undefined) {
|
||||
deferreds.push(
|
||||
MatrixClientPeg.get().sendStateEvent(
|
||||
this.state.room.roomId, "m.room.history_visibility", {
|
||||
history_visibility: new_history_visibility,
|
||||
}, ""
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (new_power_levels) {
|
||||
deferreds.push(
|
||||
MatrixClientPeg.get().sendStateEvent(
|
||||
this.state.room.roomId, "m.room.power_levels", new_power_levels, ""
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (deferreds.length) {
|
||||
var self = this;
|
||||
q.all(deferreds).fail(function(err) {
|
||||
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Failed to set state",
|
||||
description: err.toString()
|
||||
});
|
||||
}).finally(function() {
|
||||
self.setState({
|
||||
uploadingRoomSettings: false,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
editingRoomSettings: false,
|
||||
uploadingRoomSettings: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
66
src/controllers/organisms/UserSettings.js
Normal file
66
src/controllers/organisms/UserSettings.js
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright 2015 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
var q = require('q');
|
||||
var version = require('../../../package.json').version;
|
||||
|
||||
module.exports = {
|
||||
Phases: {
|
||||
Loading: "loading",
|
||||
Display: "display",
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
displayName: null,
|
||||
avatarUrl: null,
|
||||
threePids: [],
|
||||
clientVersion: version,
|
||||
phase: this.Phases.Loading,
|
||||
};
|
||||
},
|
||||
|
||||
changeDisplayname: function(new_displayname) {
|
||||
if (this.state.displayName == new_displayname) return;
|
||||
|
||||
var self = this;
|
||||
return MatrixClientPeg.get().setDisplayName(new_displayname).then(
|
||||
function() { self.setState({displayName: new_displayname}); },
|
||||
function(err) { console.err(err); }
|
||||
);
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
var self = this;
|
||||
var cli = MatrixClientPeg.get();
|
||||
|
||||
var profile_d = cli.getProfileInfo(cli.credentials.userId);
|
||||
var threepid_d = cli.getThreePids();
|
||||
|
||||
q.all([profile_d, threepid_d]).then(
|
||||
function(resps) {
|
||||
self.setState({
|
||||
displayName: resps[0].displayname,
|
||||
avatarUrl: resps[0].avatar_url,
|
||||
threepids: resps[1].threepids,
|
||||
phase: self.Phases.Display,
|
||||
});
|
||||
},
|
||||
function(err) { console.err(err); }
|
||||
);
|
||||
}
|
||||
}
|
@ -14,26 +14,40 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// should be atomised
|
||||
var Loader = require("react-loader");
|
||||
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
var RoomListSorter = require("../../RoomListSorter");
|
||||
|
||||
var Presence = require("../../Presence");
|
||||
var dis = require("../../dispatcher");
|
||||
var q = require("q");
|
||||
|
||||
var ComponentBroker = require('../../ComponentBroker');
|
||||
|
||||
var Notifier = ComponentBroker.get('organisms/Notifier');
|
||||
var sdk = require('../../index');
|
||||
var MatrixTools = require('../../MatrixTools');
|
||||
|
||||
module.exports = {
|
||||
PageTypes: {
|
||||
RoomView: "room_view",
|
||||
UserSettings: "user_settings",
|
||||
CreateRoom: "create_room",
|
||||
RoomDirectory: "room_directory",
|
||||
},
|
||||
|
||||
AuxPanel: {
|
||||
RoomSettings: "room_settings",
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
var s = {
|
||||
logged_in: !!(MatrixClientPeg.get() && MatrixClientPeg.get().credentials),
|
||||
ready: false
|
||||
ready: false,
|
||||
};
|
||||
if (s.logged_in) {
|
||||
if (MatrixClientPeg.get().getRooms().length) {
|
||||
s.page_type = this.PageTypes.RoomView;
|
||||
} else {
|
||||
s.page_type = this.PageTypes.RoomDirectory;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
@ -54,6 +68,7 @@ module.exports = {
|
||||
componentWillUnmount: function() {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
document.removeEventListener("keydown", this.onKeyDown);
|
||||
window.removeEventListener("focus", this.onFocus);
|
||||
},
|
||||
|
||||
componentDidUpdate: function() {
|
||||
@ -65,6 +80,7 @@ module.exports = {
|
||||
|
||||
onAction: function(payload) {
|
||||
var roomIndexDelta = 1;
|
||||
var Notifier = sdk.getComponent('organisms.Notifier');
|
||||
|
||||
switch (payload.action) {
|
||||
case 'logout':
|
||||
@ -76,8 +92,11 @@ module.exports = {
|
||||
window.localStorage.clear();
|
||||
}
|
||||
Notifier.stop();
|
||||
Presence.stop();
|
||||
MatrixClientPeg.get().stopClient();
|
||||
MatrixClientPeg.get().removeAllListeners();
|
||||
MatrixClientPeg.replace(null);
|
||||
MatrixClientPeg.unset();
|
||||
this.notifyNewScreen('');
|
||||
break;
|
||||
case 'start_registration':
|
||||
if (this.state.logged_in) return;
|
||||
@ -110,8 +129,23 @@ module.exports = {
|
||||
case 'view_room':
|
||||
this.focusComposer = true;
|
||||
this.setState({
|
||||
currentRoom: payload.room_id
|
||||
currentRoom: payload.room_id,
|
||||
page_type: this.PageTypes.RoomView,
|
||||
});
|
||||
if (this.sdkReady) {
|
||||
// if the SDK is not ready yet, remember what room
|
||||
// we're supposed to be on but don't notify about
|
||||
// the new screen yet (we won't be showing it yet)
|
||||
// The normal case where this happens is navigating
|
||||
// to the room in the URL bar on page load.
|
||||
var presentedId = payload.room_id;
|
||||
var room = MatrixClientPeg.get().getRoom(payload.room_id);
|
||||
if (room) {
|
||||
var theAlias = MatrixTools.getCanonicalAliasForRoom(room);
|
||||
if (theAlias) presentedId = theAlias;
|
||||
}
|
||||
this.notifyNewScreen('room/'+presentedId);
|
||||
}
|
||||
break;
|
||||
case 'view_prev_room':
|
||||
roomIndexDelta = -1;
|
||||
@ -127,9 +161,43 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
roomIndex = (roomIndex + roomIndexDelta) % allRooms.length;
|
||||
if (roomIndex < 0) roomIndex = allRooms.length - 1;
|
||||
this.focusComposer = true;
|
||||
this.setState({
|
||||
currentRoom: allRooms[roomIndex].roomId
|
||||
});
|
||||
this.notifyNewScreen('room/'+allRooms[roomIndex].roomId);
|
||||
break;
|
||||
case 'view_indexed_room':
|
||||
var allRooms = RoomListSorter.mostRecentActivityFirst(
|
||||
MatrixClientPeg.get().getRooms()
|
||||
);
|
||||
var roomIndex = payload.roomIndex;
|
||||
if (allRooms[roomIndex]) {
|
||||
this.focusComposer = true;
|
||||
this.setState({
|
||||
currentRoom: allRooms[roomIndex].roomId
|
||||
});
|
||||
this.notifyNewScreen('room/'+allRooms[roomIndex].roomId);
|
||||
}
|
||||
break;
|
||||
case 'view_user_settings':
|
||||
this.setState({
|
||||
page_type: this.PageTypes.UserSettings,
|
||||
});
|
||||
break;
|
||||
case 'view_create_room':
|
||||
this.setState({
|
||||
page_type: this.PageTypes.CreateRoom,
|
||||
});
|
||||
break;
|
||||
case 'view_room_directory':
|
||||
this.setState({
|
||||
page_type: this.PageTypes.RoomDirectory,
|
||||
});
|
||||
break;
|
||||
case 'notifier_enabled':
|
||||
this.forceUpdate();
|
||||
break;
|
||||
}
|
||||
},
|
||||
@ -144,32 +212,83 @@ module.exports = {
|
||||
},
|
||||
|
||||
startMatrixClient: function() {
|
||||
var Notifier = sdk.getComponent('organisms.Notifier');
|
||||
var cli = MatrixClientPeg.get();
|
||||
var that = this;
|
||||
var self = this;
|
||||
cli.on('syncComplete', function() {
|
||||
var firstRoom = null;
|
||||
if (cli.getRooms() && cli.getRooms().length) {
|
||||
firstRoom = RoomListSorter.mostRecentActivityFirst(
|
||||
cli.getRooms()
|
||||
)[0].roomId;
|
||||
self.sdkReady = true;
|
||||
|
||||
var defer = q.defer();
|
||||
if (self.starting_room_alias) {
|
||||
MatrixClientPeg.get().getRoomIdForAlias(self.starting_room_alias).done(function(result) {
|
||||
self.setState({currentRoom: result.room_id});
|
||||
defer.resolve();
|
||||
}, function(error) {
|
||||
defer.resolve();
|
||||
});
|
||||
} else {
|
||||
defer.resolve();
|
||||
}
|
||||
that.setState({ready: true, currentRoom: firstRoom});
|
||||
dis.dispatch({action: 'focus_composer'});
|
||||
|
||||
defer.promise.done(function() {
|
||||
if (!self.state.currentRoom) {
|
||||
var firstRoom = null;
|
||||
if (cli.getRooms() && cli.getRooms().length) {
|
||||
firstRoom = RoomListSorter.mostRecentActivityFirst(
|
||||
cli.getRooms()
|
||||
)[0].roomId;
|
||||
self.setState({ready: true, currentRoom: firstRoom, page_type: self.PageTypes.RoomView});
|
||||
} else {
|
||||
self.setState({ready: true, page_type: self.PageTypes.RoomDirectory});
|
||||
}
|
||||
} else {
|
||||
self.setState({ready: true, page_type: self.PageTypes.RoomView});
|
||||
}
|
||||
|
||||
// we notifyNewScreen now because now the room will actually be displayed,
|
||||
// and (mostly) now we can get the correct alias.
|
||||
var presentedId = self.state.currentRoom;
|
||||
var room = MatrixClientPeg.get().getRoom(self.state.currentRoom);
|
||||
if (room) {
|
||||
var theAlias = MatrixTools.getCanonicalAliasForRoom(room);
|
||||
if (theAlias) presentedId = theAlias;
|
||||
}
|
||||
self.notifyNewScreen('room/'+presentedId);
|
||||
dis.dispatch({action: 'focus_composer'});
|
||||
});
|
||||
});
|
||||
cli.on('Call.incoming', function(call) {
|
||||
dis.dispatch({
|
||||
action: 'incoming_call',
|
||||
call: call
|
||||
});
|
||||
});
|
||||
Notifier.start();
|
||||
Presence.start();
|
||||
cli.startClient();
|
||||
},
|
||||
|
||||
onKeyDown: function(ev) {
|
||||
if (ev.altKey) {
|
||||
if (ev.ctrlKey && ev.keyCode > 48 && ev.keyCode < 58) {
|
||||
dis.dispatch({
|
||||
action: 'view_indexed_room',
|
||||
roomIndex: ev.keyCode - 49,
|
||||
});
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
return;
|
||||
}
|
||||
switch (ev.keyCode) {
|
||||
case 38:
|
||||
dis.dispatch({action: 'view_prev_room'});
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
break;
|
||||
case 40:
|
||||
dis.dispatch({action: 'view_next_room'});
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -190,6 +309,16 @@ module.exports = {
|
||||
action: 'start_login',
|
||||
params: params
|
||||
});
|
||||
} else if (screen.indexOf('room/') == 0) {
|
||||
var roomString = screen.split('/')[1];
|
||||
if (roomString[0] == '#') {
|
||||
this.starting_room_alias = roomString;
|
||||
} else {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: roomString
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -199,4 +328,3 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -16,14 +16,9 @@ limitations under the License.
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
var Matrix = require("matrix-js-sdk");
|
||||
var dis = require("../../dispatcher");
|
||||
|
||||
var ComponentBroker = require("../../ComponentBroker");
|
||||
|
||||
module.exports = {
|
||||
getInitialState: function() {
|
||||
return {
|
||||
@ -35,57 +30,73 @@ module.exports = {
|
||||
},
|
||||
|
||||
setStep: function(step) {
|
||||
this.setState({ step: step, errorText: '', busy: false });
|
||||
this.setState({ step: step, busy: false });
|
||||
},
|
||||
|
||||
onHSChosen: function(ev) {
|
||||
ev.preventDefault();
|
||||
onHSChosen: function() {
|
||||
MatrixClientPeg.replaceUsingUrls(
|
||||
this.getHsUrl(),
|
||||
this.getIsUrl()
|
||||
);
|
||||
this.setState({
|
||||
hs_url: this.getHsUrl(),
|
||||
is_url: this.getIsUrl()
|
||||
is_url: this.getIsUrl(),
|
||||
});
|
||||
this.setStep("fetch_stages");
|
||||
var cli = MatrixClientPeg.get();
|
||||
this.setState({busy: true});
|
||||
var that = this;
|
||||
this.setState({
|
||||
busy: true,
|
||||
errorText: "",
|
||||
});
|
||||
var self = this;
|
||||
cli.loginFlows().done(function(result) {
|
||||
that.setState({
|
||||
self.setState({
|
||||
flows: result.flows,
|
||||
currentStep: 1,
|
||||
totalSteps: result.flows.length+1
|
||||
});
|
||||
that.setStep('stage_'+result.flows[0].type);
|
||||
self.setStep('stage_'+result.flows[0].type);
|
||||
}, function(error) {
|
||||
that.setStep("choose_hs");
|
||||
that.setState({errorText: 'Unable to contact the given Home Server'});
|
||||
self.setStep("choose_hs");
|
||||
self.setState({errorText: 'Unable to contact the given Home Server'});
|
||||
});
|
||||
},
|
||||
|
||||
onUserPassEntered: function(ev) {
|
||||
ev.preventDefault();
|
||||
this.setState({busy: true});
|
||||
var that = this;
|
||||
this.setState({
|
||||
busy: true,
|
||||
errorText: "",
|
||||
});
|
||||
var self = this;
|
||||
|
||||
var formVals = this.getFormVals();
|
||||
|
||||
MatrixClientPeg.get().login('m.login.password', {
|
||||
'user': formVals.username,
|
||||
'password': formVals.password
|
||||
}).done(function(data) {
|
||||
var loginParams = {
|
||||
password: formVals.password
|
||||
};
|
||||
if (formVals.username.indexOf('@') > 0) {
|
||||
loginParams.medium = 'email';
|
||||
loginParams.address = formVals.username;
|
||||
} else {
|
||||
loginParams.user = formVals.username;
|
||||
}
|
||||
|
||||
MatrixClientPeg.get().login('m.login.password', loginParams).done(function(data) {
|
||||
MatrixClientPeg.replaceUsingAccessToken(
|
||||
that.state.hs_url, that.state.is_url,
|
||||
self.state.hs_url, self.state.is_url,
|
||||
data.user_id, data.access_token
|
||||
);
|
||||
if (that.props.onLoggedIn) {
|
||||
that.props.onLoggedIn();
|
||||
if (self.props.onLoggedIn) {
|
||||
self.props.onLoggedIn();
|
||||
}
|
||||
}, function(error) {
|
||||
that.setStep("stage_m.login.password");
|
||||
that.setState({errorText: 'Login failed.'});
|
||||
self.setStep("stage_m.login.password");
|
||||
if (error.httpStatus == 400 && loginParams.medium) {
|
||||
self.setState({errorText: 'This Home Server does not support login using email address.'});
|
||||
} else {
|
||||
self.setState({errorText: 'Login failed.'});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user