You've already forked matrix-react-sdk
							
							
				mirror of
				https://github.com/matrix-org/matrix-react-sdk.git
				synced 2025-11-03 00:33:22 +03:00 
			
		
		
		
	
							
								
								
									
										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) {
 | 
			
		||||
    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,9 +149,76 @@ module.exports = {
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onKeyDown: function (ev) {
 | 
			
		||||
        if (ev.keyCode == 13) {
 | 
			
		||||
        if (ev.keyCode === KeyCode.ENTER) {
 | 
			
		||||
            var input = this.refs.textarea.getDOMNode().value;
 | 
			
		||||
            if (input.length === 0) {
 | 
			
		||||
                ev.preventDefault();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            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 = {
 | 
			
		||||
@@ -54,14 +232,182 @@ module.exports = {
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            MatrixClientPeg.get().sendMessage(this.props.roomId, content).then(function() {
 | 
			
		||||
        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.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.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) {
 | 
			
		||||
        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,14 +71,12 @@ 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);
 | 
			
		||||
 | 
			
		||||
            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;
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
@@ -90,10 +111,28 @@ module.exports = {
 | 
			
		||||
 | 
			
		||||
        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() {
 | 
			
		||||
            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();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            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});
 | 
			
		||||
                    }
 | 
			
		||||
            that.setState({ready: true, currentRoom: firstRoom});
 | 
			
		||||
                } 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
		Reference in New Issue
	
	Block a user