mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-16 05:04:57 +08:00
commit
711272a7c9
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,4 +1,2 @@
|
|||||||
node_modules
|
node_modules
|
||||||
build
|
lib
|
||||||
bundle.css
|
|
||||||
bundle.js
|
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
example
|
example
|
||||||
examples
|
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.
|
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`
|
Skins are modules are exported from such a package in the `lib` directory.
|
||||||
2. Clone the repo: `git clone https://github.com/matrix-org/matrix-react-sdk.git`
|
`lib/skins` contains one directory per-skin, named after the skin, and the
|
||||||
3. Switch to the SDK directory: `cd matrix-react-sdk`
|
`modules` directory contains modules as their javascript files.
|
||||||
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`
|
|
||||||
|
|
||||||
Now open http://127.0.0.1:8080/ in your browser to see your newly built
|
A basic skin is provided in the matrix-react-skin package. This also contains
|
||||||
Matrix client.
|
a minimal application that instantiates the basic skin making a working matrix
|
||||||
|
client.
|
||||||
|
|
||||||
Using the example app for development
|
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.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
How to customise the SDK
|
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
|
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
|
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.
|
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
|
* Create a new NPM project. Be sure to directly depend on react, (otherwise
|
||||||
you can end up with two copies of react).
|
you can end up with two copies of react).
|
||||||
* Create an index.js file that sets up react. Add require statements for
|
* 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
|
React and matrix-react-sdk. Load a skin using the 'loadSkin' method on the
|
||||||
the root React element as in the examples.
|
SDK and call Render. This can be a skin provided by a separate package or
|
||||||
* Create React classes for any custom components you wish to add. These
|
a skin in the same package.
|
||||||
can be based off the files in `views` in the `matrix-react-sdk` package,
|
* Add a way to build your project: we suggest copying the scripts block
|
||||||
modifying the require() statement appropriately.
|
from matrix-react-skin (which uses babel and webpack). You could use
|
||||||
You only need to copy files you want to customise.
|
different tools but remember that at least the skins and modules of
|
||||||
* Add a ComponentBroker.set() call for each of your custom components. These
|
your project should end up in plain (ie. non ES6, non JSX) javascript in
|
||||||
must come *before* `require("matrix-react-sdk")`.
|
the lib directory at the end of the build process, as well as any
|
||||||
* Add a way to build your project: we suggest copying the browserify calls
|
packaging that you might do.
|
||||||
from the example projects, but you could use grunt or gulp.
|
* Create an index.html file pulling in your compiled javascript and the
|
||||||
* Create an index.html file pulling in your compiled index.js file, the
|
CSS bundle from the skin you use. For now, you'll also need to manually
|
||||||
CSS bundle from matrix-react-sdk.
|
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
|
To Create Your Own Skin
|
||||||
matrix-react-sdk/examples.
|
=======================
|
||||||
|
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"
|
"url": "https://github.com/matrix-org/matrix-react-sdk"
|
||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"main": "src/index.js",
|
"main": "lib/index.js",
|
||||||
"style": "bundle.css",
|
"bin": {
|
||||||
|
"reskindex": "./reskindex.js"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:skins": "jsx skins build/skins",
|
"build": "babel src -d lib --source-maps",
|
||||||
"build:logic": "jsx src build/src",
|
"start": "babel src -w -d lib --source-maps",
|
||||||
"build:js": "npm run build:skins && npm run build:logic",
|
"clean": "rimraf lib",
|
||||||
"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'",
|
|
||||||
"prepublish": "npm run build"
|
"prepublish": "npm run build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"classnames": "^2.1.2",
|
"classnames": "^2.1.2",
|
||||||
"filesize": "^3.1.2",
|
"filesize": "^3.1.2",
|
||||||
"flux": "^2.0.3",
|
"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",
|
"q": "^1.4.1",
|
||||||
"react": "^0.13.3",
|
"react": "^0.13.3",
|
||||||
"react-loader": "^1.4.0",
|
"react-loader": "^1.4.0"
|
||||||
"linkifyjs": "^2.0.0-beta.4"
|
|
||||||
},
|
},
|
||||||
|
"//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": {
|
"devDependencies": {
|
||||||
"catw": "^1.0.1",
|
"babel": "^5.8.23",
|
||||||
"parallelshell": "^1.1.1",
|
"rimraf": "^2.4.3",
|
||||||
"react-tools": "^0.13.3",
|
"json-loader": "^0.5.3",
|
||||||
"uglifycss": "0.0.15"
|
"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,
|
body: file.name,
|
||||||
info: {
|
info: {
|
||||||
size: file.size,
|
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();
|
var def = q.defer();
|
||||||
if (file.type.indexOf('image/') == 0) {
|
if (file.type.indexOf('image/') == 0) {
|
||||||
content.msgtype = 'm.image';
|
content.msgtype = 'm.image';
|
||||||
|
@ -23,6 +23,16 @@ var matrixClient = null;
|
|||||||
|
|
||||||
var localStorage = window.localStorage;
|
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) {
|
function createClient(hs_url, is_url, user_id, access_token) {
|
||||||
var opts = {
|
var opts = {
|
||||||
baseUrl: hs_url,
|
baseUrl: hs_url,
|
||||||
@ -31,6 +41,11 @@ function createClient(hs_url, is_url, user_id, access_token) {
|
|||||||
userId: user_id
|
userId: user_id
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (localStorage) {
|
||||||
|
opts.sessionStore = new Matrix.WebStorageSessionStore(localStorage);
|
||||||
|
opts.deviceId = deviceId();
|
||||||
|
}
|
||||||
|
|
||||||
matrixClient = Matrix.createClient(opts);
|
matrixClient = Matrix.createClient(opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,23 +59,33 @@ if (localStorage) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
class MatrixClient {
|
||||||
get: function() {
|
get() {
|
||||||
return matrixClient;
|
return matrixClient;
|
||||||
},
|
}
|
||||||
|
|
||||||
replaceUsingUrls: function(hs_url, is_url) {
|
unset() {
|
||||||
|
matrixClient = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceUsingUrls(hs_url, is_url) {
|
||||||
matrixClient = Matrix.createClient({
|
matrixClient = Matrix.createClient({
|
||||||
baseUrl: hs_url,
|
baseUrl: hs_url,
|
||||||
idBaseUrl: is_url
|
idBaseUrl: is_url
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
replaceUsingAccessToken: function(hs_url, is_url, user_id, access_token) {
|
replaceUsingAccessToken(hs_url, is_url, user_id, access_token) {
|
||||||
createClient(hs_url, is_url, user_id, access_token);
|
|
||||||
if (localStorage) {
|
if (localStorage) {
|
||||||
try {
|
try {
|
||||||
localStorage.clear();
|
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_hs_url", hs_url);
|
||||||
localStorage.setItem("mx_is_url", is_url);
|
localStorage.setItem("mx_is_url", is_url);
|
||||||
localStorage.setItem("mx_user_id", user_id);
|
localStorage.setItem("mx_user_id", user_id);
|
||||||
@ -72,5 +97,9 @@ module.exports = {
|
|||||||
console.warn("No local storage available: can't persist session!");
|
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';
|
'use strict';
|
||||||
|
|
||||||
function tsOfNewestEvent(room) {
|
function tsOfNewestEvent(room) {
|
||||||
return room.timeline[room.timeline.length - 1].getTs();
|
if (room.timeline.length) {
|
||||||
|
return room.timeline[room.timeline.length - 1].getTs();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Number.MAX_SAFE_INTEGER;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mostRecentActivityFirst(roomList) {
|
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 = {
|
module.exports = {
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onValueChanged: React.PropTypes.func,
|
onValueChanged: React.PropTypes.func,
|
||||||
initalValue: React.PropTypes.string,
|
initialValue: React.PropTypes.string,
|
||||||
|
label: React.PropTypes.string,
|
||||||
|
placeHolder: React.PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
Phases: {
|
Phases: {
|
||||||
@ -32,37 +34,55 @@ module.exports = {
|
|||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
onValueChanged: function() {},
|
onValueChanged: function() {},
|
||||||
initalValue: '',
|
initialValue: '',
|
||||||
|
label: 'Click to set',
|
||||||
|
placeholder: '',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
value: this.props.initalValue,
|
value: this.props.initialValue,
|
||||||
phase: this.Phases.Display,
|
phase: this.Phases.Display,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentWillReceiveProps: function(nextProps) {
|
||||||
|
this.setState({
|
||||||
|
value: nextProps.initialValue
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
getValue: function() {
|
getValue: function() {
|
||||||
return this.state.value;
|
return this.state.value;
|
||||||
},
|
},
|
||||||
|
|
||||||
setValue: function(val) {
|
setValue: function(val, shouldSubmit, suppressListener) {
|
||||||
|
var self = this;
|
||||||
this.setState({
|
this.setState({
|
||||||
value: val,
|
value: val,
|
||||||
phase: this.Phases.Display,
|
phase: this.Phases.Display,
|
||||||
|
}, function() {
|
||||||
|
if (!suppressListener) {
|
||||||
|
self.onValueChanged(shouldSubmit);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
this.onValueChanged();
|
edit: function() {
|
||||||
|
this.setState({
|
||||||
|
phase: this.Phases.Edit,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
cancelEdit: function() {
|
cancelEdit: function() {
|
||||||
this.setState({
|
this.setState({
|
||||||
phase: this.Phases.Display,
|
phase: this.Phases.Display,
|
||||||
});
|
});
|
||||||
|
this.onValueChanged(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
onValueChanged: function() {
|
onValueChanged: function(shouldSubmit) {
|
||||||
this.props.onValueChanged(this.state.value);
|
this.props.onValueChanged(this.state.value, shouldSubmit);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -15,53 +15,44 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
var sdk = require('../../index');
|
||||||
|
var dis = require("../../dispatcher");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
notificationsAvailable: function() {
|
|
||||||
return !!global.Notification;
|
componentDidMount: function() {
|
||||||
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
},
|
},
|
||||||
|
|
||||||
havePermission: function() {
|
componentWillUnmount: function() {
|
||||||
return global.Notification.permission == 'granted';
|
dis.unregister(this.dispatcherRef);
|
||||||
|
},
|
||||||
|
|
||||||
|
onAction: function(payload) {
|
||||||
|
if (payload.action !== "notifier_enabled") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.forceUpdate();
|
||||||
},
|
},
|
||||||
|
|
||||||
enabled: function() {
|
enabled: function() {
|
||||||
if (!this.havePermission()) return false;
|
var Notifier = sdk.getComponent('organisms.Notifier');
|
||||||
|
return Notifier.isEnabled();
|
||||||
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();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onClick: function() {
|
onClick: function() {
|
||||||
if (!this.notificationsAvailable()) {
|
var Notifier = sdk.getComponent('organisms.Notifier');
|
||||||
|
var self = this;
|
||||||
|
if (!Notifier.supportsDesktopNotifications()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.enabled()) {
|
if (!Notifier.isEnabled()) {
|
||||||
this.enable();
|
Notifier.setEnabled(true, function() {
|
||||||
|
self.forceUpdate();
|
||||||
|
});
|
||||||
} else {
|
} 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 React = require('react');
|
||||||
|
|
||||||
|
var Presets = {
|
||||||
|
PrivateChat: "private_chat",
|
||||||
|
PublicChat: "public_chat",
|
||||||
|
Custom: "custom",
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
propTypes: {
|
propTypes: {
|
||||||
default_preset: React.PropTypes.string
|
onChange: React.PropTypes.func,
|
||||||
|
preset: React.PropTypes.string
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Presets: Presets,
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
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.
|
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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_RoomList {
|
module.exports = {
|
||||||
}
|
};
|
||||||
|
|
@ -16,6 +16,15 @@ limitations under the License.
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var linkify = require('linkifyjs');
|
||||||
|
var linkifyElement = require('linkifyjs/element');
|
||||||
|
var linkifyMatrix = require('../../linkify-matrix');
|
||||||
|
|
||||||
|
linkifyMatrix(linkify);
|
||||||
|
|
||||||
module.exports = {
|
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';
|
'use strict';
|
||||||
|
|
||||||
var dis = require("../../dispatcher");
|
var dis = require("../../dispatcher");
|
||||||
|
var Modal = require("../../Modal");
|
||||||
|
var sdk = require('../../index.js');
|
||||||
|
var Loader = require("react-loader");
|
||||||
|
|
||||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
onClick: function() {
|
getInitialState: function() {
|
||||||
dis.dispatch({
|
return {};
|
||||||
action: 'view_user',
|
|
||||||
user_id: this.props.member.userId
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||||
|
var SlashCommands = require("../../SlashCommands");
|
||||||
|
var Modal = require("../../Modal");
|
||||||
|
var sdk = require('../../index');
|
||||||
|
|
||||||
var dis = require("../../dispatcher");
|
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 = {
|
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() {
|
componentDidMount: function() {
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
|
this.sentHistory.init(
|
||||||
|
this.refs.textarea.getDOMNode(),
|
||||||
|
this.props.room.roomId
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
|
this.sentHistory.saveLastTextEntry();
|
||||||
},
|
},
|
||||||
|
|
||||||
onAction: function(payload) {
|
onAction: function(payload) {
|
||||||
@ -38,30 +149,265 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
onKeyDown: function (ev) {
|
onKeyDown: function (ev) {
|
||||||
if (ev.keyCode == 13) {
|
if (ev.keyCode === KeyCode.ENTER) {
|
||||||
var contentText = this.refs.textarea.getDOMNode().value;
|
var input = this.refs.textarea.getDOMNode().value;
|
||||||
|
if (input.length === 0) {
|
||||||
var content = null;
|
ev.preventDefault();
|
||||||
if (/^\/me /i.test(contentText)) {
|
return;
|
||||||
content = {
|
|
||||||
msgtype: 'm.emote',
|
|
||||||
body: contentText.substring(4)
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
content = {
|
|
||||||
msgtype: 'm.text',
|
|
||||||
body: contentText
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
this.sentHistory.push(input);
|
||||||
MatrixClientPeg.get().sendMessage(this.props.roomId, content).then(function() {
|
this.onEnter(ev);
|
||||||
dis.dispatch({
|
}
|
||||||
action: 'message_sent'
|
else if (ev.keyCode === KeyCode.TAB) {
|
||||||
});
|
var members = [];
|
||||||
});
|
if (this.props.room) {
|
||||||
this.refs.textarea.getDOMNode().value = '';
|
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();
|
ev.preventDefault();
|
||||||
}
|
}
|
||||||
|
else if (ev.keyCode !== KeyCode.SHIFT && this.tabStruct.completing) {
|
||||||
|
// they're resuming typing; reset tab complete state vars.
|
||||||
|
this.tabStruct.completing = false;
|
||||||
|
this.tabStruct.index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
setTimeout(function() {
|
||||||
|
if (self.refs.textarea && self.refs.textarea.getDOMNode().value != '') {
|
||||||
|
self.onTypingActivity();
|
||||||
|
} else {
|
||||||
|
self.onFinishedTyping();
|
||||||
|
}
|
||||||
|
}, 10); // XXX: what is this 10ms setTimeout doing? Looks hacky :(
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onEnter: function(ev) {
|
||||||
|
var contentText = this.refs.textarea.getDOMNode().value;
|
||||||
|
|
||||||
|
var cmd = SlashCommands.processInput(this.props.room.roomId, contentText);
|
||||||
|
if (cmd) {
|
||||||
|
ev.preventDefault();
|
||||||
|
if (!cmd.error) {
|
||||||
|
this.refs.textarea.getDOMNode().value = '';
|
||||||
|
}
|
||||||
|
if (cmd.promise) {
|
||||||
|
cmd.promise.done(function() {
|
||||||
|
console.log("Command success.");
|
||||||
|
}, function(err) {
|
||||||
|
console.error("Command failure: %s", err);
|
||||||
|
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
title: "Server error",
|
||||||
|
description: err.message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (cmd.error) {
|
||||||
|
console.error(cmd.error);
|
||||||
|
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
title: "Command error",
|
||||||
|
description: cmd.error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var content = null;
|
||||||
|
if (/^\/me /i.test(contentText)) {
|
||||||
|
content = {
|
||||||
|
msgtype: 'm.emote',
|
||||||
|
body: contentText.substring(4)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
content = {
|
||||||
|
msgtype: 'm.text',
|
||||||
|
body: contentText
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
MatrixClientPeg.get().sendMessage(this.props.room.roomId, content).then(function() {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'message_sent'
|
||||||
|
});
|
||||||
|
}, function() {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'message_send_failed'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.refs.textarea.getDOMNode().value = '';
|
||||||
|
ev.preventDefault();
|
||||||
|
},
|
||||||
|
|
||||||
|
onTab: function(ev, sortedMembers) {
|
||||||
|
var textArea = this.refs.textarea.getDOMNode();
|
||||||
|
if (!this.tabStruct.completing) {
|
||||||
|
this.tabStruct.completing = true;
|
||||||
|
this.tabStruct.index = 0;
|
||||||
|
// cache starting text
|
||||||
|
this.tabStruct.original = textArea.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop in the right direction
|
||||||
|
if (ev.shiftKey) {
|
||||||
|
this.tabStruct.index --;
|
||||||
|
if (this.tabStruct.index < 0) {
|
||||||
|
// wrap to the last search match, and fix up to a real index
|
||||||
|
// value after we've matched.
|
||||||
|
this.tabStruct.index = Number.MAX_VALUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.tabStruct.index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
var searchIndex = 0;
|
||||||
|
var targetIndex = this.tabStruct.index;
|
||||||
|
var text = this.tabStruct.original;
|
||||||
|
|
||||||
|
var search = /@?([a-zA-Z0-9_\-:\.]+)$/.exec(text);
|
||||||
|
// console.log("Searched in '%s' - got %s", text, search);
|
||||||
|
if (targetIndex === 0) { // 0 is always the original text
|
||||||
|
textArea.value = text;
|
||||||
|
}
|
||||||
|
else if (search && search[1]) {
|
||||||
|
// console.log("search found: " + search+" from "+text);
|
||||||
|
var expansion;
|
||||||
|
|
||||||
|
// FIXME: could do better than linear search here
|
||||||
|
for (var i=0; i<sortedMembers.length; i++) {
|
||||||
|
var member = sortedMembers[i];
|
||||||
|
if (member.name && searchIndex < targetIndex) {
|
||||||
|
if (member.name.toLowerCase().indexOf(search[1].toLowerCase()) === 0) {
|
||||||
|
expansion = member.name;
|
||||||
|
searchIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchIndex < targetIndex) { // then search raw mxids
|
||||||
|
for (var i=0; i<sortedMembers.length; i++) {
|
||||||
|
if (searchIndex >= targetIndex) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
var userId = sortedMembers[i].userId;
|
||||||
|
// === 1 because mxids are @username
|
||||||
|
if (userId.toLowerCase().indexOf(search[1].toLowerCase()) === 1) {
|
||||||
|
expansion = userId;
|
||||||
|
searchIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchIndex === targetIndex ||
|
||||||
|
targetIndex === Number.MAX_VALUE) {
|
||||||
|
// xchat-style tab complete, add a colon if tab
|
||||||
|
// completing at the start of the text
|
||||||
|
if (search[0].length === text.length) {
|
||||||
|
expansion += ": ";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
expansion += " ";
|
||||||
|
}
|
||||||
|
textArea.value = text.replace(
|
||||||
|
/@?([a-zA-Z0-9_\-:\.]+)$/, expansion
|
||||||
|
);
|
||||||
|
// cancel blink
|
||||||
|
textArea.style["background-color"] = "";
|
||||||
|
if (targetIndex === Number.MAX_VALUE) {
|
||||||
|
// wrap the index around to the last index found
|
||||||
|
this.tabStruct.index = searchIndex;
|
||||||
|
targetIndex = searchIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// console.log("wrapped!");
|
||||||
|
textArea.style["background-color"] = "#faa";
|
||||||
|
setTimeout(function() {
|
||||||
|
textArea.style["background-color"] = "";
|
||||||
|
}, 150);
|
||||||
|
textArea.value = text;
|
||||||
|
this.tabStruct.index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.tabStruct.index = 0;
|
||||||
|
}
|
||||||
|
// prevent the default TAB operation (typically focus shifting)
|
||||||
|
ev.preventDefault();
|
||||||
|
},
|
||||||
|
|
||||||
|
onTypingActivity: function() {
|
||||||
|
this.isTyping = true;
|
||||||
|
if (!this.userTypingTimer) {
|
||||||
|
this.sendTyping(true);
|
||||||
|
}
|
||||||
|
this.startUserTypingTimer();
|
||||||
|
this.startServerTypingTimer();
|
||||||
|
},
|
||||||
|
|
||||||
|
onFinishedTyping: function() {
|
||||||
|
this.isTyping = false;
|
||||||
|
this.sendTyping(false);
|
||||||
|
this.stopUserTypingTimer();
|
||||||
|
this.stopServerTypingTimer();
|
||||||
|
},
|
||||||
|
|
||||||
|
startUserTypingTimer: function() {
|
||||||
|
this.stopUserTypingTimer();
|
||||||
|
var self = this;
|
||||||
|
this.userTypingTimer = setTimeout(function() {
|
||||||
|
self.isTyping = false;
|
||||||
|
self.sendTyping(self.isTyping);
|
||||||
|
self.userTypingTimer = null;
|
||||||
|
}, TYPING_USER_TIMEOUT);
|
||||||
|
},
|
||||||
|
|
||||||
|
stopUserTypingTimer: function() {
|
||||||
|
if (this.userTypingTimer) {
|
||||||
|
clearTimeout(this.userTypingTimer);
|
||||||
|
this.userTypingTimer = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
startServerTypingTimer: function() {
|
||||||
|
if (!this.serverTypingTimer) {
|
||||||
|
var self = this;
|
||||||
|
this.serverTypingTimer = setTimeout(function() {
|
||||||
|
if (self.isTyping) {
|
||||||
|
self.sendTyping(self.isTyping);
|
||||||
|
self.startServerTypingTimer();
|
||||||
|
}
|
||||||
|
}, TYPING_SERVER_TIMEOUT / 2);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
stopServerTypingTimer: function() {
|
||||||
|
if (this.serverTypingTimer) {
|
||||||
|
clearTimeout(this.servrTypingTimer);
|
||||||
|
this.serverTypingTimer = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
sendTyping: function(isTyping) {
|
||||||
|
MatrixClientPeg.get().sendTyping(
|
||||||
|
this.props.room.roomId,
|
||||||
|
this.isTyping, TYPING_SERVER_TIMEOUT
|
||||||
|
).done();
|
||||||
|
},
|
||||||
|
|
||||||
|
refreshTyping: function() {
|
||||||
|
if (this.typingTimeout) {
|
||||||
|
clearTimeout(this.typingTimeout);
|
||||||
|
this.typingTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -23,6 +23,28 @@ module.exports = {
|
|||||||
var actions = MatrixClientPeg.get().getPushActionsForEvent(this.props.mxEvent);
|
var actions = MatrixClientPeg.get().getPushActionsForEvent(this.props.mxEvent);
|
||||||
if (!actions || !actions.tweaks) { return false; }
|
if (!actions || !actions.tweaks) { return false; }
|
||||||
return actions.tweaks.highlight;
|
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';
|
'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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_MessageTile {
|
var React = require('react');
|
||||||
display: table-row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_MessageTile_content {
|
module.exports = {
|
||||||
display: table-cell;
|
propTypes: {
|
||||||
}
|
room: React.PropTypes.object.isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
.mx_MessageTile_sending {
|
getInitialState: function() {
|
||||||
color: #ddd;
|
return {
|
||||||
}
|
power_levels_changed: false
|
||||||
|
};
|
||||||
.mx_MessageTile_notSent {
|
}
|
||||||
color: #f11;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
.mx_MessageTile_highlight {
|
|
||||||
color: #00f;
|
|
||||||
}
|
|
@ -30,26 +30,28 @@ module.exports = {
|
|||||||
return {
|
return {
|
||||||
onHsUrlChanged: function() {},
|
onHsUrlChanged: function() {},
|
||||||
onIsUrlChanged: function() {},
|
onIsUrlChanged: function() {},
|
||||||
default_hs_url: 'https://matrix.org/',
|
defaultHsUrl: 'https://matrix.org/',
|
||||||
default_is_url: 'https://matrix.org/'
|
defaultIsUrl: 'https://matrix.org/'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
hs_url: this.props.default_hs_url,
|
hs_url: this.props.defaultHsUrl,
|
||||||
is_url: this.props.default_is_url,
|
is_url: this.props.defaultIsUrl,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
hsChanged: function(ev) {
|
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);
|
this.props.onHsUrlChanged(this.state.hs_url);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
isChanged: function(ev) {
|
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);
|
this.props.onIsUrlChanged(this.state.is_url);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getHsUrl: function() {
|
getHsUrl: function() {
|
||||||
|
@ -20,38 +20,26 @@ var React = require('react');
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
propTypes: {
|
propTypes: {
|
||||||
initially_selected: React.PropTypes.arrayOf(React.PropTypes.string),
|
onChange: React.PropTypes.func,
|
||||||
|
selected_users: React.PropTypes.arrayOf(React.PropTypes.string),
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
initially_selected: [],
|
onChange: function() {},
|
||||||
|
selected: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
selected_users: this.props.initially_selected,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
addUser: function(user_id) {
|
addUser: function(user_id) {
|
||||||
if (this.state.selected_users.indexOf(user_id == -1)) {
|
if (this.props.selected_users.indexOf(user_id == -1)) {
|
||||||
this.setState({
|
this.props.onChange(this.props.selected_users.concat([user_id]));
|
||||||
selected_users: this.state.selected_users.concat([user_id]),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
removeUser: function(user_id) {
|
removeUser: function(user_id) {
|
||||||
this.setState({
|
this.props.onChange(this.props.selected_users.filter(function(e) {
|
||||||
selected_users: this.state.selected_users.filter(function(e) {
|
return e != user_id;
|
||||||
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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_MNoticeTile {
|
module.exports = {
|
||||||
}
|
};
|
||||||
|
|
@ -18,6 +18,9 @@ limitations under the License.
|
|||||||
|
|
||||||
var React = require("react");
|
var React = require("react");
|
||||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||||
|
var PresetValues = require('../atoms/create_room/Presets').Presets;
|
||||||
|
var q = require('q');
|
||||||
|
var encryption = require("../../encryption");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
propTypes: {
|
propTypes: {
|
||||||
@ -41,25 +44,52 @@ module.exports = {
|
|||||||
return {
|
return {
|
||||||
phase: this.phases.CONFIG,
|
phase: this.phases.CONFIG,
|
||||||
error_string: "",
|
error_string: "",
|
||||||
|
is_private: true,
|
||||||
|
share_history: false,
|
||||||
|
default_preset: PresetValues.PrivateChat,
|
||||||
|
topic: '',
|
||||||
|
room_name: '',
|
||||||
|
invited_users: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
onCreateRoom: function() {
|
onCreateRoom: function() {
|
||||||
var options = {};
|
var options = {};
|
||||||
|
|
||||||
var room_name = this.getName();
|
if (this.state.room_name) {
|
||||||
if (room_name) {
|
options.name = this.state.room_name;
|
||||||
options.name = room_name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var preset = this.getPreset();
|
if (this.state.topic) {
|
||||||
if (preset) {
|
options.topic = this.state.topic;
|
||||||
options.preset = preset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var invited_users = this.getInvitedUsers();
|
if (this.state.preset) {
|
||||||
if (invited_users) {
|
if (this.state.preset != PresetValues.Custom) {
|
||||||
options.invite = invited_users;
|
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();
|
var cli = MatrixClientPeg.get();
|
||||||
@ -69,7 +99,20 @@ module.exports = {
|
|||||||
return;
|
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({
|
this.setState({
|
||||||
phase: this.phases.CREATING,
|
phase: this.phases.CREATING,
|
||||||
@ -77,11 +120,11 @@ module.exports = {
|
|||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
deferred.then(function () {
|
deferred.then(function (resp) {
|
||||||
self.setState({
|
self.setState({
|
||||||
phase: self.phases.CREATED,
|
phase: self.phases.CREATED,
|
||||||
});
|
});
|
||||||
self.props.onRoomCreated();
|
self.props.onRoomCreated(resp.room_id);
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
self.setState({
|
self.setState({
|
||||||
phase: self.phases.ERROR,
|
phase: self.phases.ERROR,
|
||||||
|
@ -14,22 +14,23 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
var React = require("react");
|
||||||
|
|
||||||
var React = require('react');
|
module.exports = {
|
||||||
|
propTypes: {
|
||||||
var RoomHeaderController = require("../../../../src/controllers/molecules/RoomHeader");
|
title: React.PropTypes.string,
|
||||||
|
description: React.PropTypes.string,
|
||||||
module.exports = React.createClass({
|
button: React.PropTypes.string,
|
||||||
displayName: 'RoomHeader',
|
focus: React.PropTypes.bool,
|
||||||
mixins: [RoomHeaderController],
|
onFinished: React.PropTypes.func.isRequired,
|
||||||
|
|
||||||
render: function() {
|
|
||||||
return (
|
|
||||||
<div className="mx_RoomHeader">
|
|
||||||
{this.props.room.name}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
|
||||||
|
|
||||||
|
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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_MTextTile {
|
var dis = require("../../dispatcher");
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var React = require("react");
|
|
||||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||||
|
var Modal = require("../../Modal");
|
||||||
|
var sdk = require('../../index');
|
||||||
|
|
||||||
var INITIAL_LOAD_NUM_MEMBERS = 50;
|
var INITIAL_LOAD_NUM_MEMBERS = 50;
|
||||||
|
|
||||||
@ -32,39 +31,137 @@ module.exports = {
|
|||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
var cli = MatrixClientPeg.get();
|
var cli = MatrixClientPeg.get();
|
||||||
cli.on("RoomState.members", this.onRoomStateMember);
|
cli.on("RoomState.members", this.onRoomStateMember);
|
||||||
|
cli.on("Room", this.onRoom); // invites
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
if (MatrixClientPeg.get()) {
|
if (MatrixClientPeg.get()) {
|
||||||
|
MatrixClientPeg.get().removeListener("Room", this.onRoom);
|
||||||
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
|
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
|
||||||
|
MatrixClientPeg.get().removeListener("User.presence", this.userPresenceFn);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
var that = this;
|
var self = this;
|
||||||
|
|
||||||
|
// Lazy-load in more than the first N members
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
if (!that.isMounted()) return;
|
if (!self.isMounted()) return;
|
||||||
that.setState({
|
self.setState({
|
||||||
memberDict: that.roomMembers()
|
memberDict: self.roomMembers()
|
||||||
});
|
});
|
||||||
}, 50);
|
}, 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
|
// Remember to set 'key' on a MemberList to the ID of the room it's for
|
||||||
/*componentWillReceiveProps: function(newProps) {
|
/*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) {
|
onRoomStateMember: function(ev, state, member) {
|
||||||
|
this._updateList();
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateList: function() {
|
||||||
var members = this.roomMembers();
|
var members = this.roomMembers();
|
||||||
this.setState({
|
this.setState({
|
||||||
memberDict: members
|
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) {
|
roomMembers: function(limit) {
|
||||||
|
if (!this.props.roomId) return {};
|
||||||
var cli = MatrixClientPeg.get();
|
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);
|
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 to_display = {};
|
||||||
var count = 0;
|
var count = 0;
|
||||||
for (var i = 0; i < all_user_ids.length && (limit === undefined || count < limit); ++i) {
|
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];
|
var m = all_members[user_id];
|
||||||
|
|
||||||
if (m.membership == 'join' || m.membership == 'invite') {
|
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;
|
to_display[user_id] = m;
|
||||||
++count;
|
++count;
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,21 @@ limitations under the License.
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||||
|
var dis = require("../../dispatcher");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dispatches:
|
||||||
|
* {
|
||||||
|
* action: "notifier_enabled",
|
||||||
|
* value: boolean
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
start: function() {
|
start: function() {
|
||||||
this.boundOnRoomTimeline = this.onRoomTimeline.bind(this);
|
this.boundOnRoomTimeline = this.onRoomTimeline.bind(this);
|
||||||
MatrixClientPeg.get().on('Room.timeline', this.boundOnRoomTimeline);
|
MatrixClientPeg.get().on('Room.timeline', this.boundOnRoomTimeline);
|
||||||
|
this.state = { 'toolbarHidden' : false };
|
||||||
},
|
},
|
||||||
|
|
||||||
stop: function() {
|
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) {
|
onRoomTimeline: function(ev, room, toStartOfTimeline) {
|
||||||
if (toStartOfTimeline) return;
|
if (toStartOfTimeline) return;
|
||||||
if (ev.sender && ev.sender.userId == MatrixClientPeg.get().credentials.userId) return;
|
if (ev.sender && ev.sender.userId == MatrixClientPeg.get().credentials.userId) return;
|
||||||
|
|
||||||
var enabled = global.localStorage.getItem('notifications_enabled');
|
if (!this.isEnabled()) {
|
||||||
if (enabled === 'false') return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
|
var actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
|
||||||
if (actions && actions.notify) {
|
if (actions && actions.notify) {
|
||||||
|
@ -14,22 +14,23 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
var React = require("react");
|
||||||
|
|
||||||
var React = require('react');
|
module.exports = {
|
||||||
|
propTypes: {
|
||||||
var MessageComposerController = require("../../../../src/controllers/molecules/MessageComposer");
|
title: React.PropTypes.string,
|
||||||
|
description: React.PropTypes.string,
|
||||||
module.exports = React.createClass({
|
button: React.PropTypes.string,
|
||||||
displayName: 'MessageComposer',
|
focus: React.PropTypes.bool,
|
||||||
mixins: [MessageComposerController],
|
onFinished: React.PropTypes.func.isRequired,
|
||||||
|
|
||||||
render: function() {
|
|
||||||
return (
|
|
||||||
<div className="mx_MessageComposer">
|
|
||||||
<textarea ref="textarea" onKeyDown={this.onKeyDown} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
|
||||||
|
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
title: "",
|
||||||
|
description: "",
|
||||||
|
button: "OK",
|
||||||
|
focus: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
@ -20,9 +20,7 @@ var React = require("react");
|
|||||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||||
var RoomListSorter = require("../../RoomListSorter");
|
var RoomListSorter = require("../../RoomListSorter");
|
||||||
|
|
||||||
var ComponentBroker = require('../../ComponentBroker');
|
var sdk = require('../../index');
|
||||||
|
|
||||||
var RoomTile = ComponentBroker.get("molecules/RoomTile");
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
@ -73,13 +71,11 @@ module.exports = {
|
|||||||
if (actions && actions.tweaks && actions.tweaks.highlight) {
|
if (actions && actions.tweaks && actions.tweaks.highlight) {
|
||||||
hl = 2;
|
hl = 2;
|
||||||
}
|
}
|
||||||
if (actions.notify) {
|
// obviously this won't deep copy but this shouldn't be necessary
|
||||||
// obviously this won't deep copy but this shouldn't be necessary
|
var amap = this.state.activityMap;
|
||||||
var amap = this.state.activityMap;
|
amap[room.roomId] = Math.max(amap[room.roomId] || 0, hl);
|
||||||
amap[room.roomId] = Math.max(amap[room.roomId] || 0, hl);
|
|
||||||
|
|
||||||
newState.activityMap = amap;
|
newState.activityMap = amap;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.setState(newState);
|
this.setState(newState);
|
||||||
},
|
},
|
||||||
@ -96,23 +92,28 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getRoomList: function() {
|
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() {
|
makeRoomTiles: function() {
|
||||||
var that = this;
|
var RoomTile = sdk.getComponent('molecules.RoomTile');
|
||||||
|
var self = this;
|
||||||
return this.state.roomList.map(function(room) {
|
return this.state.roomList.map(function(room) {
|
||||||
var selected = room.roomId == that.props.selectedRoom;
|
var selected = room.roomId == self.props.selectedRoom;
|
||||||
return (
|
return (
|
||||||
<RoomTile
|
<RoomTile
|
||||||
room={room}
|
room={room}
|
||||||
key={room.roomId}
|
key={room.roomId}
|
||||||
selected={selected}
|
selected={selected}
|
||||||
unread={that.state.activityMap[room.roomId] === 1}
|
unread={self.state.activityMap[room.roomId] === 1}
|
||||||
highlight={that.state.activityMap[room.roomId] === 2}
|
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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||||
var React = require("react");
|
var React = require("react");
|
||||||
var q = require("q");
|
var q = require("q");
|
||||||
var ContentMessages = require("../../ContentMessages");
|
var ContentMessages = require("../../ContentMessages");
|
||||||
|
var WhoIsTyping = require("../../WhoIsTyping");
|
||||||
|
var Modal = require("../../Modal");
|
||||||
|
var sdk = require('../../index');
|
||||||
|
|
||||||
var dis = require("../../dispatcher");
|
var dis = require("../../dispatcher");
|
||||||
|
|
||||||
var PAGINATE_SIZE = 20;
|
var PAGINATE_SIZE = 20;
|
||||||
var INITIAL_SIZE = 100;
|
var INITIAL_SIZE = 20;
|
||||||
|
|
||||||
var ComponentBroker = require('../../ComponentBroker');
|
|
||||||
|
|
||||||
var tileTypes = {
|
|
||||||
'm.room.message': ComponentBroker.get('molecules/MessageTile'),
|
|
||||||
'm.room.member': ComponentBroker.get('molecules/MRoomMemberTile')
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
room: this.props.roomId ? MatrixClientPeg.get().getRoom(this.props.roomId) : null,
|
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() {
|
componentWillMount: function() {
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
|
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
|
||||||
|
MatrixClientPeg.get().on("Room.name", this.onRoomName);
|
||||||
|
MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping);
|
||||||
this.atBottom = true;
|
this.atBottom = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -51,19 +51,40 @@ module.exports = {
|
|||||||
if (this.refs.messageWrapper) {
|
if (this.refs.messageWrapper) {
|
||||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
||||||
messageWrapper.removeEventListener('drop', this.onDrop);
|
messageWrapper.removeEventListener('drop', this.onDrop);
|
||||||
|
messageWrapper.removeEventListener('dragover', this.onDragOver);
|
||||||
|
messageWrapper.removeEventListener('dragleave', this.onDragLeaveOrEnd);
|
||||||
|
messageWrapper.removeEventListener('dragend', this.onDragLeaveOrEnd);
|
||||||
}
|
}
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
if (MatrixClientPeg.get()) {
|
if (MatrixClientPeg.get()) {
|
||||||
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
|
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
|
||||||
|
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
|
||||||
|
MatrixClientPeg.get().removeListener("RoomMember.typing", this.onRoomMemberTyping);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onAction: function(payload) {
|
onAction: function(payload) {
|
||||||
switch (payload.action) {
|
switch (payload.action) {
|
||||||
|
case 'message_send_failed':
|
||||||
case 'message_sent':
|
case 'message_sent':
|
||||||
this.setState({
|
this.setState({
|
||||||
room: MatrixClientPeg.get().getRoom(this.props.roomId)
|
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;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -90,10 +111,28 @@ module.exports = {
|
|||||||
|
|
||||||
if (this.refs.messageWrapper) {
|
if (this.refs.messageWrapper) {
|
||||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
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({
|
this.setState({
|
||||||
room: MatrixClientPeg.get().getRoom(this.props.roomId)
|
room: MatrixClientPeg.get().getRoom(this.props.roomId),
|
||||||
|
numUnreadMessages: currentUnread
|
||||||
});
|
});
|
||||||
|
|
||||||
if (toStartOfTimeline && !this.state.paginating) {
|
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() {
|
componentDidMount: function() {
|
||||||
if (this.refs.messageWrapper) {
|
if (this.refs.messageWrapper) {
|
||||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
||||||
|
|
||||||
messageWrapper.addEventListener('drop', this.onDrop);
|
messageWrapper.addEventListener('drop', this.onDrop);
|
||||||
messageWrapper.addEventListener('dragover', this.onDragOver);
|
messageWrapper.addEventListener('dragover', this.onDragOver);
|
||||||
|
messageWrapper.addEventListener('dragleave', this.onDragLeaveOrEnd);
|
||||||
|
messageWrapper.addEventListener('dragend', this.onDragLeaveOrEnd);
|
||||||
|
|
||||||
messageWrapper.scrollTop = messageWrapper.scrollHeight;
|
messageWrapper.scrollTop = messageWrapper.scrollHeight;
|
||||||
|
|
||||||
@ -128,10 +181,14 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
} else if (this.atBottom) {
|
} else if (this.atBottom) {
|
||||||
messageWrapper.scrollTop = messageWrapper.scrollHeight;
|
messageWrapper.scrollTop = messageWrapper.scrollHeight;
|
||||||
|
if (this.state.numUnreadMessages !== 0) {
|
||||||
|
this.setState({numUnreadMessages: 0});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
fillSpace: function() {
|
fillSpace: function() {
|
||||||
|
if (!this.refs.messageWrapper) return;
|
||||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
||||||
if (messageWrapper.scrollTop < messageWrapper.clientHeight && this.state.room.oldState.paginationToken) {
|
if (messageWrapper.scrollTop < messageWrapper.clientHeight && this.state.room.oldState.paginationToken) {
|
||||||
this.setState({paginating: true});
|
this.setState({paginating: true});
|
||||||
@ -146,12 +203,12 @@ module.exports = {
|
|||||||
this.waiting_for_paginate = true;
|
this.waiting_for_paginate = true;
|
||||||
var cap = this.state.messageCap + PAGINATE_SIZE;
|
var cap = this.state.messageCap + PAGINATE_SIZE;
|
||||||
this.setState({messageCap: cap, paginating: true});
|
this.setState({messageCap: cap, paginating: true});
|
||||||
var that = this;
|
var self = this;
|
||||||
MatrixClientPeg.get().scrollback(this.state.room, PAGINATE_SIZE).finally(function() {
|
MatrixClientPeg.get().scrollback(this.state.room, PAGINATE_SIZE).finally(function() {
|
||||||
that.waiting_for_paginate = false;
|
self.waiting_for_paginate = false;
|
||||||
if (that.isMounted()) {
|
if (self.isMounted()) {
|
||||||
that.setState({
|
self.setState({
|
||||||
room: MatrixClientPeg.get().getRoom(that.props.roomId)
|
room: MatrixClientPeg.get().getRoom(self.props.roomId)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// wait and set paginating to false when the component updates
|
// wait and set paginating to false when the component updates
|
||||||
@ -164,14 +221,14 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
onJoinButtonClicked: function(ev) {
|
onJoinButtonClicked: function(ev) {
|
||||||
var that = this;
|
var self = this;
|
||||||
MatrixClientPeg.get().joinRoom(this.props.roomId).then(function() {
|
MatrixClientPeg.get().joinRoom(this.props.roomId).then(function() {
|
||||||
that.setState({
|
self.setState({
|
||||||
joining: false,
|
joining: false,
|
||||||
room: MatrixClientPeg.get().getRoom(that.props.roomId)
|
room: MatrixClientPeg.get().getRoom(self.props.roomId)
|
||||||
});
|
});
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
that.setState({
|
self.setState({
|
||||||
joining: false,
|
joining: false,
|
||||||
joinError: error
|
joinError: error
|
||||||
});
|
});
|
||||||
@ -184,7 +241,11 @@ module.exports = {
|
|||||||
onMessageListScroll: function(ev) {
|
onMessageListScroll: function(ev) {
|
||||||
if (this.refs.messageWrapper) {
|
if (this.refs.messageWrapper) {
|
||||||
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
var messageWrapper = this.refs.messageWrapper.getDOMNode();
|
||||||
|
var wasAtBottom = this.atBottom;
|
||||||
this.atBottom = messageWrapper.scrollHeight - messageWrapper.scrollTop <= messageWrapper.clientHeight;
|
this.atBottom = messageWrapper.scrollHeight - messageWrapper.scrollTop <= messageWrapper.clientHeight;
|
||||||
|
if (this.atBottom && !wasAtBottom) {
|
||||||
|
this.forceUpdate(); // remove unread msg count
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!this.state.paginating) this.fillSpace();
|
if (!this.state.paginating) this.fillSpace();
|
||||||
},
|
},
|
||||||
@ -198,6 +259,7 @@ module.exports = {
|
|||||||
var items = ev.dataTransfer.items;
|
var items = ev.dataTransfer.items;
|
||||||
if (items.length == 1) {
|
if (items.length == 1) {
|
||||||
if (items[0].kind == 'file') {
|
if (items[0].kind == 'file') {
|
||||||
|
this.setState({ draggingFile : true });
|
||||||
ev.dataTransfer.dropEffect = 'copy';
|
ev.dataTransfer.dropEffect = 'copy';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -206,33 +268,178 @@ module.exports = {
|
|||||||
onDrop: function(ev) {
|
onDrop: function(ev) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
this.setState({ draggingFile : false });
|
||||||
var files = ev.dataTransfer.files;
|
var files = ev.dataTransfer.files;
|
||||||
|
|
||||||
if (files.length == 1) {
|
if (files.length == 1) {
|
||||||
ContentMessages.sendContentToRoom(
|
this.uploadFile(files[0]);
|
||||||
files[0], this.props.roomId, MatrixClientPeg.get()
|
|
||||||
).progress(function(ev) {
|
|
||||||
//console.log("Upload: "+ev.loaded+" / "+ev.total);
|
|
||||||
}).done(undefined, function() {
|
|
||||||
// display error message
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
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() {
|
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 ret = [];
|
||||||
var count = 0;
|
var count = 0;
|
||||||
|
|
||||||
for (var i = this.state.room.timeline.length-1; i >= 0 && count < this.state.messageCap; --i) {
|
for (var i = this.state.room.timeline.length-1; i >= 0 && count < this.state.messageCap; --i) {
|
||||||
var mxEv = this.state.room.timeline[i];
|
var mxEv = this.state.room.timeline[i];
|
||||||
var TileType = tileTypes[mxEv.getType()];
|
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;
|
if (!TileType) continue;
|
||||||
ret.unshift(
|
ret.unshift(
|
||||||
<TileType key={mxEv.getId()} mxEvent={mxEv} />
|
<li key={mxEv.getId()}><TileType mxEvent={mxEv} continuation={continuation} last={last}/></li>
|
||||||
);
|
);
|
||||||
++count;
|
++count;
|
||||||
}
|
}
|
||||||
return ret;
|
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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// should be atomised
|
|
||||||
var Loader = require("react-loader");
|
|
||||||
|
|
||||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||||
var RoomListSorter = require("../../RoomListSorter");
|
var RoomListSorter = require("../../RoomListSorter");
|
||||||
|
var Presence = require("../../Presence");
|
||||||
var dis = require("../../dispatcher");
|
var dis = require("../../dispatcher");
|
||||||
|
var q = require("q");
|
||||||
|
|
||||||
var ComponentBroker = require('../../ComponentBroker');
|
var sdk = require('../../index');
|
||||||
|
var MatrixTools = require('../../MatrixTools');
|
||||||
var Notifier = ComponentBroker.get('organisms/Notifier');
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
PageTypes: {
|
||||||
|
RoomView: "room_view",
|
||||||
|
UserSettings: "user_settings",
|
||||||
|
CreateRoom: "create_room",
|
||||||
|
RoomDirectory: "room_directory",
|
||||||
|
},
|
||||||
|
|
||||||
|
AuxPanel: {
|
||||||
|
RoomSettings: "room_settings",
|
||||||
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
var s = {
|
||||||
logged_in: !!(MatrixClientPeg.get() && MatrixClientPeg.get().credentials),
|
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() {
|
componentDidMount: function() {
|
||||||
@ -54,6 +68,7 @@ module.exports = {
|
|||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
document.removeEventListener("keydown", this.onKeyDown);
|
document.removeEventListener("keydown", this.onKeyDown);
|
||||||
|
window.removeEventListener("focus", this.onFocus);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidUpdate: function() {
|
componentDidUpdate: function() {
|
||||||
@ -65,6 +80,7 @@ module.exports = {
|
|||||||
|
|
||||||
onAction: function(payload) {
|
onAction: function(payload) {
|
||||||
var roomIndexDelta = 1;
|
var roomIndexDelta = 1;
|
||||||
|
var Notifier = sdk.getComponent('organisms.Notifier');
|
||||||
|
|
||||||
switch (payload.action) {
|
switch (payload.action) {
|
||||||
case 'logout':
|
case 'logout':
|
||||||
@ -76,8 +92,11 @@ module.exports = {
|
|||||||
window.localStorage.clear();
|
window.localStorage.clear();
|
||||||
}
|
}
|
||||||
Notifier.stop();
|
Notifier.stop();
|
||||||
|
Presence.stop();
|
||||||
|
MatrixClientPeg.get().stopClient();
|
||||||
MatrixClientPeg.get().removeAllListeners();
|
MatrixClientPeg.get().removeAllListeners();
|
||||||
MatrixClientPeg.replace(null);
|
MatrixClientPeg.unset();
|
||||||
|
this.notifyNewScreen('');
|
||||||
break;
|
break;
|
||||||
case 'start_registration':
|
case 'start_registration':
|
||||||
if (this.state.logged_in) return;
|
if (this.state.logged_in) return;
|
||||||
@ -110,8 +129,23 @@ module.exports = {
|
|||||||
case 'view_room':
|
case 'view_room':
|
||||||
this.focusComposer = true;
|
this.focusComposer = true;
|
||||||
this.setState({
|
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;
|
break;
|
||||||
case 'view_prev_room':
|
case 'view_prev_room':
|
||||||
roomIndexDelta = -1;
|
roomIndexDelta = -1;
|
||||||
@ -127,9 +161,43 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
roomIndex = (roomIndex + roomIndexDelta) % allRooms.length;
|
roomIndex = (roomIndex + roomIndexDelta) % allRooms.length;
|
||||||
|
if (roomIndex < 0) roomIndex = allRooms.length - 1;
|
||||||
|
this.focusComposer = true;
|
||||||
this.setState({
|
this.setState({
|
||||||
currentRoom: allRooms[roomIndex].roomId
|
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;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -144,32 +212,83 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
startMatrixClient: function() {
|
startMatrixClient: function() {
|
||||||
|
var Notifier = sdk.getComponent('organisms.Notifier');
|
||||||
var cli = MatrixClientPeg.get();
|
var cli = MatrixClientPeg.get();
|
||||||
var that = this;
|
var self = this;
|
||||||
cli.on('syncComplete', function() {
|
cli.on('syncComplete', function() {
|
||||||
var firstRoom = null;
|
self.sdkReady = true;
|
||||||
if (cli.getRooms() && cli.getRooms().length) {
|
|
||||||
firstRoom = RoomListSorter.mostRecentActivityFirst(
|
var defer = q.defer();
|
||||||
cli.getRooms()
|
if (self.starting_room_alias) {
|
||||||
)[0].roomId;
|
MatrixClientPeg.get().getRoomIdForAlias(self.starting_room_alias).done(function(result) {
|
||||||
|
self.setState({currentRoom: result.room_id});
|
||||||
|
defer.resolve();
|
||||||
|
}, function(error) {
|
||||||
|
defer.resolve();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
defer.resolve();
|
||||||
}
|
}
|
||||||
that.setState({ready: true, currentRoom: firstRoom});
|
|
||||||
dis.dispatch({action: 'focus_composer'});
|
defer.promise.done(function() {
|
||||||
|
if (!self.state.currentRoom) {
|
||||||
|
var firstRoom = null;
|
||||||
|
if (cli.getRooms() && cli.getRooms().length) {
|
||||||
|
firstRoom = RoomListSorter.mostRecentActivityFirst(
|
||||||
|
cli.getRooms()
|
||||||
|
)[0].roomId;
|
||||||
|
self.setState({ready: true, currentRoom: firstRoom, page_type: self.PageTypes.RoomView});
|
||||||
|
} else {
|
||||||
|
self.setState({ready: true, page_type: self.PageTypes.RoomDirectory});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.setState({ready: true, page_type: self.PageTypes.RoomView});
|
||||||
|
}
|
||||||
|
|
||||||
|
// we notifyNewScreen now because now the room will actually be displayed,
|
||||||
|
// and (mostly) now we can get the correct alias.
|
||||||
|
var presentedId = self.state.currentRoom;
|
||||||
|
var room = MatrixClientPeg.get().getRoom(self.state.currentRoom);
|
||||||
|
if (room) {
|
||||||
|
var theAlias = MatrixTools.getCanonicalAliasForRoom(room);
|
||||||
|
if (theAlias) presentedId = theAlias;
|
||||||
|
}
|
||||||
|
self.notifyNewScreen('room/'+presentedId);
|
||||||
|
dis.dispatch({action: 'focus_composer'});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
cli.on('Call.incoming', function(call) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'incoming_call',
|
||||||
|
call: call
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Notifier.start();
|
Notifier.start();
|
||||||
|
Presence.start();
|
||||||
cli.startClient();
|
cli.startClient();
|
||||||
},
|
},
|
||||||
|
|
||||||
onKeyDown: function(ev) {
|
onKeyDown: function(ev) {
|
||||||
if (ev.altKey) {
|
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) {
|
switch (ev.keyCode) {
|
||||||
case 38:
|
case 38:
|
||||||
dis.dispatch({action: 'view_prev_room'});
|
dis.dispatch({action: 'view_prev_room'});
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
break;
|
break;
|
||||||
case 40:
|
case 40:
|
||||||
dis.dispatch({action: 'view_next_room'});
|
dis.dispatch({action: 'view_next_room'});
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,6 +309,16 @@ module.exports = {
|
|||||||
action: 'start_login',
|
action: 'start_login',
|
||||||
params: params
|
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';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
|
||||||
|
|
||||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||||
var Matrix = require("matrix-js-sdk");
|
|
||||||
var dis = require("../../dispatcher");
|
var dis = require("../../dispatcher");
|
||||||
|
|
||||||
var ComponentBroker = require("../../ComponentBroker");
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
@ -35,57 +30,73 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
setStep: function(step) {
|
setStep: function(step) {
|
||||||
this.setState({ step: step, errorText: '', busy: false });
|
this.setState({ step: step, busy: false });
|
||||||
},
|
},
|
||||||
|
|
||||||
onHSChosen: function(ev) {
|
onHSChosen: function() {
|
||||||
ev.preventDefault();
|
|
||||||
MatrixClientPeg.replaceUsingUrls(
|
MatrixClientPeg.replaceUsingUrls(
|
||||||
this.getHsUrl(),
|
this.getHsUrl(),
|
||||||
this.getIsUrl()
|
this.getIsUrl()
|
||||||
);
|
);
|
||||||
this.setState({
|
this.setState({
|
||||||
hs_url: this.getHsUrl(),
|
hs_url: this.getHsUrl(),
|
||||||
is_url: this.getIsUrl()
|
is_url: this.getIsUrl(),
|
||||||
});
|
});
|
||||||
this.setStep("fetch_stages");
|
this.setStep("fetch_stages");
|
||||||
var cli = MatrixClientPeg.get();
|
var cli = MatrixClientPeg.get();
|
||||||
this.setState({busy: true});
|
this.setState({
|
||||||
var that = this;
|
busy: true,
|
||||||
|
errorText: "",
|
||||||
|
});
|
||||||
|
var self = this;
|
||||||
cli.loginFlows().done(function(result) {
|
cli.loginFlows().done(function(result) {
|
||||||
that.setState({
|
self.setState({
|
||||||
flows: result.flows,
|
flows: result.flows,
|
||||||
currentStep: 1,
|
currentStep: 1,
|
||||||
totalSteps: result.flows.length+1
|
totalSteps: result.flows.length+1
|
||||||
});
|
});
|
||||||
that.setStep('stage_'+result.flows[0].type);
|
self.setStep('stage_'+result.flows[0].type);
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
that.setStep("choose_hs");
|
self.setStep("choose_hs");
|
||||||
that.setState({errorText: 'Unable to contact the given Home Server'});
|
self.setState({errorText: 'Unable to contact the given Home Server'});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onUserPassEntered: function(ev) {
|
onUserPassEntered: function(ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
this.setState({busy: true});
|
this.setState({
|
||||||
var that = this;
|
busy: true,
|
||||||
|
errorText: "",
|
||||||
|
});
|
||||||
|
var self = this;
|
||||||
|
|
||||||
var formVals = this.getFormVals();
|
var formVals = this.getFormVals();
|
||||||
|
|
||||||
MatrixClientPeg.get().login('m.login.password', {
|
var loginParams = {
|
||||||
'user': formVals.username,
|
password: formVals.password
|
||||||
'password': formVals.password
|
};
|
||||||
}).done(function(data) {
|
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(
|
MatrixClientPeg.replaceUsingAccessToken(
|
||||||
that.state.hs_url, that.state.is_url,
|
self.state.hs_url, self.state.is_url,
|
||||||
data.user_id, data.access_token
|
data.user_id, data.access_token
|
||||||
);
|
);
|
||||||
if (that.props.onLoggedIn) {
|
if (self.props.onLoggedIn) {
|
||||||
that.props.onLoggedIn();
|
self.props.onLoggedIn();
|
||||||
}
|
}
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
that.setStep("stage_m.login.password");
|
self.setStep("stage_m.login.password");
|
||||||
that.setState({errorText: 'Login failed.'});
|
if (error.httpStatus == 400 && loginParams.medium) {
|
||||||
|
self.setState({errorText: 'This Home Server does not support login using email address.'});
|
||||||
|
} else {
|
||||||
|
self.setState({errorText: 'Login failed.'});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user