forked from matrix/element-web
Merge branch 'develop' into dbkr/email_notifs
This commit is contained in:
commit
3cb092051e
|
@ -1,8 +1,9 @@
|
||||||
node_modules
|
/cert.pem
|
||||||
vector/bundle.*
|
/.DS_Store
|
||||||
lib
|
/karma-reports
|
||||||
.DS_Store
|
/key.pem
|
||||||
key.pem
|
/lib
|
||||||
cert.pem
|
/node_modules
|
||||||
vector/components.css
|
/packages/
|
||||||
packages/
|
/vector/bundle.*
|
||||||
|
/vector/components.css
|
||||||
|
|
|
@ -13,8 +13,11 @@ npm install
|
||||||
# we may be using a dev branch of react-sdk, in which case we need to build it
|
# we may be using a dev branch of react-sdk, in which case we need to build it
|
||||||
(cd node_modules/matrix-react-sdk && npm run build)
|
(cd node_modules/matrix-react-sdk && npm run build)
|
||||||
|
|
||||||
|
# run the mocha tests
|
||||||
|
npm run test
|
||||||
|
|
||||||
# build our artifacts; dumps them in ./vector
|
# build our artifacts; dumps them in ./vector
|
||||||
npm run build
|
npm run build:dev
|
||||||
|
|
||||||
# gzip up ./vector
|
# gzip up ./vector
|
||||||
rm vector-*.tar.gz || true # rm previous artifacts without failing if it doesn't exist
|
rm vector-*.tar.gz || true # rm previous artifacts without failing if it doesn't exist
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
// karma.conf.js - the config file for karma, which runs our tests.
|
||||||
|
|
||||||
|
var path = require('path');
|
||||||
|
var webpack = require('webpack');
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We use webpack to build our tests. It's a pain to have to wait for webpack
|
||||||
|
* to build everything; however it's the easiest way to load our dependencies
|
||||||
|
* from node_modules.
|
||||||
|
*
|
||||||
|
* If you run karma in multi-run mode (with `npm run test:multi`), it will watch
|
||||||
|
* the tests for changes, and webpack will rebuild using a cache. This is much quicker
|
||||||
|
* than a clean rebuild.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// the name of the test file. By default, a special file which runs all tests.
|
||||||
|
var testFile = process.env.KARMA_TEST_FILE || 'test/all-tests.js';
|
||||||
|
|
||||||
|
process.env.PHANTOMJS_BIN = 'node_modules/.bin/phantomjs';
|
||||||
|
process.env.Q_DEBUG = 1;
|
||||||
|
|
||||||
|
module.exports = function (config) {
|
||||||
|
config.set({
|
||||||
|
// frameworks to use
|
||||||
|
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
||||||
|
frameworks: ['mocha'],
|
||||||
|
|
||||||
|
// list of files / patterns to load in the browser
|
||||||
|
files: [
|
||||||
|
testFile,
|
||||||
|
{pattern: 'vector/img/*', watched: false, included: false, served: true, nocache: false},
|
||||||
|
],
|
||||||
|
|
||||||
|
// redirect img links to the karma server
|
||||||
|
proxies: {
|
||||||
|
"/img/": "/base/vector/img/",
|
||||||
|
},
|
||||||
|
|
||||||
|
// preprocess matching files before serving them to the browser
|
||||||
|
// available preprocessors:
|
||||||
|
// https://npmjs.org/browse/keyword/karma-preprocessor
|
||||||
|
preprocessors: {
|
||||||
|
'test/**/*.js': ['webpack', 'sourcemap']
|
||||||
|
},
|
||||||
|
|
||||||
|
// test results reporter to use
|
||||||
|
// possible values: 'dots', 'progress'
|
||||||
|
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
||||||
|
reporters: ['progress', 'junit'],
|
||||||
|
|
||||||
|
// web server port
|
||||||
|
port: 9876,
|
||||||
|
|
||||||
|
// enable / disable colors in the output (reporters and logs)
|
||||||
|
colors: true,
|
||||||
|
|
||||||
|
// level of logging
|
||||||
|
// possible values: config.LOG_DISABLE || config.LOG_ERROR ||
|
||||||
|
// config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
||||||
|
logLevel: config.LOG_INFO,
|
||||||
|
|
||||||
|
// enable / disable watching file and executing tests whenever any file
|
||||||
|
// changes
|
||||||
|
autoWatch: true,
|
||||||
|
|
||||||
|
// start these browsers
|
||||||
|
// available browser launchers:
|
||||||
|
// https://npmjs.org/browse/keyword/karma-launcher
|
||||||
|
browsers: [
|
||||||
|
'Chrome',
|
||||||
|
//'PhantomJS',
|
||||||
|
],
|
||||||
|
|
||||||
|
// Continuous Integration mode
|
||||||
|
// if true, Karma captures browsers, runs the tests and exits
|
||||||
|
// singleRun: false,
|
||||||
|
|
||||||
|
// Concurrency level
|
||||||
|
// how many browser should be started simultaneous
|
||||||
|
concurrency: Infinity,
|
||||||
|
|
||||||
|
junitReporter: {
|
||||||
|
outputDir: 'karma-reports',
|
||||||
|
},
|
||||||
|
|
||||||
|
webpack: {
|
||||||
|
module: {
|
||||||
|
loaders: [
|
||||||
|
{ test: /\.json$/, loader: "json" },
|
||||||
|
{
|
||||||
|
test: /\.js$/, loader: "babel",
|
||||||
|
include: [path.resolve('./src'),
|
||||||
|
path.resolve('./test'),
|
||||||
|
],
|
||||||
|
query: {
|
||||||
|
// we're using babel 5, for consistency with
|
||||||
|
// the release build, which doesn't use the
|
||||||
|
// presets.
|
||||||
|
// presets: ['react', 'es2015'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
noParse: [
|
||||||
|
// don't parse the languages within highlight.js. They
|
||||||
|
// cause stack overflows
|
||||||
|
// (https://github.com/webpack/webpack/issues/1721), and
|
||||||
|
// there is no need for webpack to parse them - they can
|
||||||
|
// just be included as-is.
|
||||||
|
/highlight\.js\/lib\/languages/,
|
||||||
|
|
||||||
|
// also disable parsing for sinon, because it
|
||||||
|
// tries to do voodoo with 'require' which upsets
|
||||||
|
// webpack (https://github.com/webpack/webpack/issues/304)
|
||||||
|
/sinon\/pkg\/sinon\.js$/,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
// alias any requires to the react module to the one in our path, otherwise
|
||||||
|
// we tend to get the react source included twice when using npm link.
|
||||||
|
react: path.resolve('./node_modules/react'),
|
||||||
|
|
||||||
|
// same goes for js-sdk
|
||||||
|
"matrix-js-sdk": path.resolve('./node_modules/matrix-js-sdk'),
|
||||||
|
|
||||||
|
sinon: 'sinon/pkg/sinon.js',
|
||||||
|
},
|
||||||
|
root: [
|
||||||
|
path.resolve('./src'),
|
||||||
|
path.resolve('./test'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
devtool: 'inline-source-map',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
22
package.json
22
package.json
|
@ -16,7 +16,9 @@
|
||||||
"build:css": "catw \"src/skins/vector/css/**/*.css\" -o vector/components.css --no-watch",
|
"build:css": "catw \"src/skins/vector/css/**/*.css\" -o vector/components.css --no-watch",
|
||||||
"build:compile": "babel --source-maps -d lib src",
|
"build:compile": "babel --source-maps -d lib src",
|
||||||
"build:bundle": "NODE_ENV=production webpack -p lib/vector/index.js vector/bundle.js",
|
"build:bundle": "NODE_ENV=production webpack -p lib/vector/index.js vector/bundle.js",
|
||||||
|
"build:bundle:dev": "webpack --optimize-occurence-order lib/vector/index.js vector/bundle.js",
|
||||||
"build": "npm run build:css && npm run build:compile && npm run build:bundle",
|
"build": "npm run build:css && npm run build:compile && npm run build:bundle",
|
||||||
|
"build:dev": "npm run build:css && npm run build:compile && npm run build:bundle:dev",
|
||||||
"package": "scripts/package.sh",
|
"package": "scripts/package.sh",
|
||||||
"start:js": "webpack -w src/vector/index.js vector/bundle.js",
|
"start:js": "webpack -w src/vector/index.js vector/bundle.js",
|
||||||
"start:js:prod": "NODE_ENV=production webpack -w src/vector/index.js vector/bundle.js",
|
"start:js:prod": "NODE_ENV=production webpack -w src/vector/index.js vector/bundle.js",
|
||||||
|
@ -25,7 +27,9 @@
|
||||||
"start": "parallelshell \"npm run start:js\" \"npm run start:skins:css\" \"http-server -c 1 vector\"",
|
"start": "parallelshell \"npm run start:js\" \"npm run start:skins:css\" \"http-server -c 1 vector\"",
|
||||||
"start:prod": "parallelshell \"npm run start:js:prod\" \"npm run start:skins:css\" \"http-server -c 1 vector\"",
|
"start:prod": "parallelshell \"npm run start:js:prod\" \"npm run start:skins:css\" \"http-server -c 1 vector\"",
|
||||||
"clean": "rimraf lib vector/bundle.css vector/bundle.js vector/bundle.js.map vector/webpack.css*",
|
"clean": "rimraf lib vector/bundle.css vector/bundle.js vector/bundle.js.map vector/webpack.css*",
|
||||||
"prepublish": "npm run build:css && npm run build:compile"
|
"prepublish": "npm run build:css && npm run build:compile",
|
||||||
|
"test": "karma start --single-run=true --autoWatch=false --browsers PhantomJS --colors=false",
|
||||||
|
"test:multi": "karma start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"babel-polyfill": "^6.5.0",
|
"babel-polyfill": "^6.5.0",
|
||||||
|
@ -41,7 +45,7 @@
|
||||||
"matrix-react-sdk": "matrix-org/matrix-react-sdk#develop",
|
"matrix-react-sdk": "matrix-org/matrix-react-sdk#develop",
|
||||||
"modernizr": "^3.1.0",
|
"modernizr": "^3.1.0",
|
||||||
"q": "^1.4.1",
|
"q": "^1.4.1",
|
||||||
"react": "^0.14.2",
|
"react": "^0.14.8",
|
||||||
"react-dnd": "^2.0.2",
|
"react-dnd": "^2.0.2",
|
||||||
"react-dnd-html5-backend": "^2.0.0",
|
"react-dnd-html5-backend": "^2.0.0",
|
||||||
"react-dom": "^0.14.2",
|
"react-dom": "^0.14.2",
|
||||||
|
@ -54,11 +58,23 @@
|
||||||
"babel-loader": "^5.3.2",
|
"babel-loader": "^5.3.2",
|
||||||
"catw": "^1.0.1",
|
"catw": "^1.0.1",
|
||||||
"css-raw-loader": "^0.1.1",
|
"css-raw-loader": "^0.1.1",
|
||||||
|
"expect": "^1.16.0",
|
||||||
"http-server": "^0.8.4",
|
"http-server": "^0.8.4",
|
||||||
"json-loader": "^0.5.3",
|
"json-loader": "^0.5.3",
|
||||||
|
"karma": "^0.13.22",
|
||||||
|
"karma-chrome-launcher": "^0.2.3",
|
||||||
|
"karma-cli": "^0.1.2",
|
||||||
|
"karma-junit-reporter": "^0.4.1",
|
||||||
|
"karma-mocha": "^0.2.2",
|
||||||
|
"karma-phantomjs-launcher": "^1.0.0",
|
||||||
|
"karma-sourcemap-loader": "^0.3.7",
|
||||||
|
"karma-webpack": "^1.7.0",
|
||||||
|
"mocha": "^2.4.5",
|
||||||
"parallelshell": "^1.2.0",
|
"parallelshell": "^1.2.0",
|
||||||
|
"phantomjs-prebuilt": "^2.1.7",
|
||||||
|
"react-addons-test-utils": "^0.14.8",
|
||||||
"rimraf": "^2.4.3",
|
"rimraf": "^2.4.3",
|
||||||
"source-map-loader": "^0.1.5",
|
"source-map-loader": "^0.1.5",
|
||||||
"webpack": "^1.12.13"
|
"webpack": "^1.12.14"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,7 @@ var LeftPanel = React.createClass({
|
||||||
var BottomLeftMenu = sdk.getComponent('structures.BottomLeftMenu');
|
var BottomLeftMenu = sdk.getComponent('structures.BottomLeftMenu');
|
||||||
|
|
||||||
var collapseButton;
|
var collapseButton;
|
||||||
var classes = "mx_LeftPanel";
|
var classes = "mx_LeftPanel mx_fadable";
|
||||||
if (this.props.collapsed) {
|
if (this.props.collapsed) {
|
||||||
classes += " collapsed";
|
classes += " collapsed";
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,7 @@ var LeftPanel = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className={classes}>
|
<aside className={classes} style={{ opacity: this.props.opacity }}>
|
||||||
{ collapseButton }
|
{ collapseButton }
|
||||||
{ callPreview }
|
{ callPreview }
|
||||||
<RoomList
|
<RoomList
|
||||||
|
|
|
@ -158,13 +158,13 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var classes = "mx_RightPanel";
|
var classes = "mx_RightPanel mx_fadable";
|
||||||
if (this.props.collapsed) {
|
if (this.props.collapsed) {
|
||||||
classes += " collapsed";
|
classes += " collapsed";
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className={classes}>
|
<aside className={classes} style={{ opacity: this.props.opacity }}>
|
||||||
<div className="mx_RightPanel_header">
|
<div className="mx_RightPanel_header">
|
||||||
{ buttonGroup }
|
{ buttonGroup }
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -79,6 +79,17 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
var oob_data = {};
|
var oob_data = {};
|
||||||
if (room) {
|
if (room) {
|
||||||
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
|
if (!room.world_readable && !room.guest_can_join) {
|
||||||
|
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||||
|
Modal.createDialog(NeedToRegisterDialog, {
|
||||||
|
title: "Failed to join the room",
|
||||||
|
description: "This room is inaccessible to guests. You may be able to join if you register."
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
oob_data = {
|
oob_data = {
|
||||||
avatarUrl: room.avatar_url,
|
avatarUrl: room.avatar_url,
|
||||||
// XXX: This logic is duplicated from the JS SDK which
|
// XXX: This logic is duplicated from the JS SDK which
|
||||||
|
|
|
@ -22,174 +22,14 @@ var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
||||||
var UserSettingsStore = require('matrix-react-sdk/lib/UserSettingsStore');
|
var UserSettingsStore = require('matrix-react-sdk/lib/UserSettingsStore');
|
||||||
var Modal = require('matrix-react-sdk/lib/Modal');
|
var Modal = require('matrix-react-sdk/lib/Modal');
|
||||||
|
|
||||||
/**
|
var notifications = require('../../../notifications');
|
||||||
* Enum for state of a push rule as defined by the Vector UI.
|
|
||||||
* @readonly
|
|
||||||
* @enum {string}
|
|
||||||
*/
|
|
||||||
var PushRuleVectorState = {
|
|
||||||
/** The push rule is disabled */
|
|
||||||
OFF: "off",
|
|
||||||
/** The user will receive push notification for this rule */
|
|
||||||
ON: "on",
|
|
||||||
/** The user will receive push notification for this rule with sound and
|
|
||||||
highlight if this is legitimate */
|
|
||||||
LOUD: "loud",
|
|
||||||
};
|
|
||||||
|
|
||||||
// Encodes a dictionary of {
|
// TODO: this "view" component still has far to much application logic in it,
|
||||||
// "notify": true/false,
|
// which should be factored out to other files.
|
||||||
// "sound": string or undefined,
|
|
||||||
// "highlight: true/false,
|
|
||||||
// }
|
|
||||||
// to a list of push actions.
|
|
||||||
function encodeActions(action) {
|
|
||||||
var notify = action.notify;
|
|
||||||
var sound = action.sound;
|
|
||||||
var highlight = action.highlight;
|
|
||||||
if (notify) {
|
|
||||||
var actions = ["notify"];
|
|
||||||
if (sound) {
|
|
||||||
actions.push({"set_tweak": "sound", "value": sound});
|
|
||||||
}
|
|
||||||
if (highlight) {
|
|
||||||
actions.push({"set_tweak": "highlight"});
|
|
||||||
} else {
|
|
||||||
actions.push({"set_tweak": "highlight", "value": false});
|
|
||||||
}
|
|
||||||
return actions;
|
|
||||||
} else {
|
|
||||||
return ["dont_notify"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode a list of actions to a dictionary of {
|
var NotificationUtils = notifications.NotificationUtils;
|
||||||
// "notify": true/false,
|
var VectorPushRulesDefinitions = notifications.VectorPushRulesDefinitions;
|
||||||
// "sound": string or undefined,
|
var PushRuleVectorState = notifications.PushRuleVectorState;
|
||||||
// "highlight: true/false,
|
|
||||||
// }
|
|
||||||
// If the actions couldn't be decoded then returns null.
|
|
||||||
function decodeActions(actions) {
|
|
||||||
var notify = false;
|
|
||||||
var sound = null;
|
|
||||||
var highlight = false;
|
|
||||||
|
|
||||||
for (var i = 0; i < actions.length; ++i) {
|
|
||||||
var action = actions[i];
|
|
||||||
if (action === "notify") {
|
|
||||||
notify = true;
|
|
||||||
} else if (action === "dont_notify") {
|
|
||||||
notify = false;
|
|
||||||
} else if (typeof action === 'object') {
|
|
||||||
if (action.set_tweak === "sound") {
|
|
||||||
sound = action.value
|
|
||||||
} else if (action.set_tweak === "highlight") {
|
|
||||||
highlight = action.value;
|
|
||||||
} else {
|
|
||||||
// We don't understand this kind of tweak, so give up.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// We don't understand this kind of action, so give up.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (highlight === undefined) {
|
|
||||||
// If a highlight tweak is missing a value then it defaults to true.
|
|
||||||
highlight = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = {notify: notify, highlight: highlight};
|
|
||||||
if (sound !== null) {
|
|
||||||
result.sound = sound;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ACTION_NOTIFY = encodeActions({notify: true});
|
|
||||||
var ACTION_NOTIFY_DEFAULT_SOUND = encodeActions({notify: true, sound: "default"});
|
|
||||||
var ACTION_NOTIFY_RING_SOUND = encodeActions({notify: true, sound: "ring"});
|
|
||||||
var ACTION_HIGHLIGHT_DEFAULT_SOUND = encodeActions({notify: true, sound: "default", highlight: true});
|
|
||||||
var ACTION_DONT_NOTIFY = encodeActions({notify: false});
|
|
||||||
var ACTION_DISABLED = null;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The descriptions of rules managed by the Vector UI.
|
|
||||||
*/
|
|
||||||
var VectorPushRulesDefinitions = {
|
|
||||||
|
|
||||||
// Messages containing user's display name
|
|
||||||
// (skip contains_user_name which is too geeky)
|
|
||||||
".m.rule.contains_display_name": {
|
|
||||||
kind: "underride",
|
|
||||||
description: "Messages containing my name",
|
|
||||||
vectorStateToActions: { // The actions for each vector state, or null to disable the rule.
|
|
||||||
on: ACTION_NOTIFY,
|
|
||||||
loud: ACTION_HIGHLIGHT_DEFAULT_SOUND,
|
|
||||||
off: ACTION_DISABLED
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Messages just sent to the user in a 1:1 room
|
|
||||||
".m.rule.room_one_to_one": {
|
|
||||||
kind: "underride",
|
|
||||||
description: "Messages in one-to-one chats",
|
|
||||||
vectorStateToActions: {
|
|
||||||
on: ACTION_NOTIFY,
|
|
||||||
loud: ACTION_NOTIFY_DEFAULT_SOUND,
|
|
||||||
off: ACTION_DONT_NOTIFY
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Messages just sent to a group chat room
|
|
||||||
// 1:1 room messages are catched by the .m.rule.room_one_to_one rule if any defined
|
|
||||||
// By opposition, all other room messages are from group chat rooms.
|
|
||||||
".m.rule.message": {
|
|
||||||
kind: "underride",
|
|
||||||
description: "Messages in group chats",
|
|
||||||
vectorStateToActions: {
|
|
||||||
on: ACTION_NOTIFY,
|
|
||||||
loud: ACTION_NOTIFY_DEFAULT_SOUND,
|
|
||||||
off: ACTION_DONT_NOTIFY
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Invitation for the user
|
|
||||||
".m.rule.invite_for_me": {
|
|
||||||
kind: "underride",
|
|
||||||
description: "When I'm invited to a room",
|
|
||||||
vectorStateToActions: {
|
|
||||||
on: ACTION_NOTIFY,
|
|
||||||
loud: ACTION_NOTIFY_DEFAULT_SOUND,
|
|
||||||
off: ACTION_DISABLED
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Incoming call
|
|
||||||
".m.rule.call": {
|
|
||||||
kind: "underride",
|
|
||||||
description: "Call invitation",
|
|
||||||
vectorStateToActions: {
|
|
||||||
on: ACTION_NOTIFY,
|
|
||||||
loud: ACTION_NOTIFY_RING_SOUND,
|
|
||||||
off: ACTION_DISABLED
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Notifications from bots
|
|
||||||
".m.rule.suppress_notices": {
|
|
||||||
kind: "override",
|
|
||||||
description: "Messages sent by bot",
|
|
||||||
vectorStateToActions: {
|
|
||||||
// .m.rule.suppress_notices is a "negative" rule, we have to invert its enabled value for vector UI
|
|
||||||
on: ACTION_DISABLED,
|
|
||||||
loud: ACTION_NOTIFY_DEFAULT_SOUND,
|
|
||||||
off: ACTION_DONT_NOTIFY,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rules that Vector used to set in order to override the actions of default rules.
|
* Rules that Vector used to set in order to override the actions of default rules.
|
||||||
|
@ -206,9 +46,9 @@ var LEGACY_RULES = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function portLegacyActions(actions) {
|
function portLegacyActions(actions) {
|
||||||
var decoded = decodeActions(actions);
|
var decoded = NotificationUtils.decodeActions(actions);
|
||||||
if (decoded !== null) {
|
if (decoded !== null) {
|
||||||
return encodeActions(decoded);
|
return NotificationUtils.encodeActions(decoded);
|
||||||
} else {
|
} else {
|
||||||
// We don't recognise one of the actions here, so we don't try to
|
// We don't recognise one of the actions here, so we don't try to
|
||||||
// canonicalise them.
|
// canonicalise them.
|
||||||
|
@ -216,7 +56,6 @@ function portLegacyActions(actions) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'Notififications',
|
displayName: 'Notififications',
|
||||||
|
|
||||||
|
@ -335,40 +174,6 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_actionsFor: function(pushRuleVectorState) {
|
|
||||||
if (pushRuleVectorState === PushRuleVectorState.ON) {
|
|
||||||
return ACTION_NOTIFY;
|
|
||||||
}
|
|
||||||
else if (pushRuleVectorState === PushRuleVectorState.LOUD) {
|
|
||||||
return ACTION_HIGHLIGHT_DEFAULT_SOUND;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Determine whether a content rule is in the PushRuleVectorState.ON category or in PushRuleVectorState.LOUD
|
|
||||||
// regardless of its enabled state. Returns undefined if it does not match these categories.
|
|
||||||
_contentRuleVectorStateKind: function(rule) {
|
|
||||||
var stateKind;
|
|
||||||
|
|
||||||
// Count tweaks to determine if it is a ON or LOUD rule
|
|
||||||
var tweaks = 0;
|
|
||||||
for (var j in rule.actions) {
|
|
||||||
var action = rule.actions[j];
|
|
||||||
if (action.set_tweak === 'sound' ||
|
|
||||||
(action.set_tweak === 'highlight' && action.value)) {
|
|
||||||
tweaks++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch (tweaks) {
|
|
||||||
case 0:
|
|
||||||
stateKind = PushRuleVectorState.ON;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
stateKind = PushRuleVectorState.LOUD;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return stateKind;
|
|
||||||
},
|
|
||||||
|
|
||||||
_setPushRuleVectorState: function(rule, newPushRuleVectorState) {
|
_setPushRuleVectorState: function(rule, newPushRuleVectorState) {
|
||||||
if (rule && rule.vectorState !== newPushRuleVectorState) {
|
if (rule && rule.vectorState !== newPushRuleVectorState) {
|
||||||
|
|
||||||
|
@ -384,7 +189,7 @@ module.exports = React.createClass({
|
||||||
if (rule.rule) {
|
if (rule.rule) {
|
||||||
var actions = ruleDefinition.vectorStateToActions[newPushRuleVectorState];
|
var actions = ruleDefinition.vectorStateToActions[newPushRuleVectorState];
|
||||||
|
|
||||||
if (actions === ACTION_DISABLED) {
|
if (!actions) {
|
||||||
// The new state corresponds to disabling the rule.
|
// The new state corresponds to disabling the rule.
|
||||||
deferreds.push(cli.setPushRuleEnabled('global', rule.rule.kind, rule.rule.rule_id, false));
|
deferreds.push(cli.setPushRuleEnabled('global', rule.rule.kind, rule.rule.rule_id, false));
|
||||||
}
|
}
|
||||||
|
@ -430,7 +235,7 @@ module.exports = React.createClass({
|
||||||
switch (newPushRuleVectorState) {
|
switch (newPushRuleVectorState) {
|
||||||
case PushRuleVectorState.ON:
|
case PushRuleVectorState.ON:
|
||||||
if (rule.actions.length !== 1) {
|
if (rule.actions.length !== 1) {
|
||||||
actions = this._actionsFor(PushRuleVectorState.ON);
|
actions = PushRuleVectorState.actionsFor(PushRuleVectorState.ON);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.vectorContentRules.vectorState === PushRuleVectorState.OFF) {
|
if (this.state.vectorContentRules.vectorState === PushRuleVectorState.OFF) {
|
||||||
|
@ -440,7 +245,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
case PushRuleVectorState.LOUD:
|
case PushRuleVectorState.LOUD:
|
||||||
if (rule.actions.length !== 3) {
|
if (rule.actions.length !== 3) {
|
||||||
actions = this._actionsFor(PushRuleVectorState.LOUD);
|
actions = PushRuleVectorState.actionsFor(PushRuleVectorState.LOUD);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.vectorContentRules.vectorState === PushRuleVectorState.OFF) {
|
if (this.state.vectorContentRules.vectorState === PushRuleVectorState.OFF) {
|
||||||
|
@ -526,7 +331,7 @@ module.exports = React.createClass({
|
||||||
// when creating the new rule.
|
// when creating the new rule.
|
||||||
// Thus, this new rule will join the 'vectorContentRules' set.
|
// Thus, this new rule will join the 'vectorContentRules' set.
|
||||||
if (self.state.vectorContentRules.rules.length) {
|
if (self.state.vectorContentRules.rules.length) {
|
||||||
pushRuleVectorStateKind = self._contentRuleVectorStateKind(self.state.vectorContentRules.rules[0]);
|
pushRuleVectorStateKind = PushRuleVectorState.contentRuleVectorStateKind(self.state.vectorContentRules.rules[0]);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// ON is default
|
// ON is default
|
||||||
|
@ -541,13 +346,13 @@ module.exports = React.createClass({
|
||||||
if (self.state.vectorContentRules.vectorState !== PushRuleVectorState.OFF) {
|
if (self.state.vectorContentRules.vectorState !== PushRuleVectorState.OFF) {
|
||||||
deferreds.push(cli.addPushRule
|
deferreds.push(cli.addPushRule
|
||||||
('global', 'content', keyword, {
|
('global', 'content', keyword, {
|
||||||
actions: self._actionsFor(pushRuleVectorStateKind),
|
actions: PushRuleVectorState.actionsFor(pushRuleVectorStateKind),
|
||||||
pattern: keyword
|
pattern: keyword
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
deferreds.push(self._addDisabledPushRule('global', 'content', keyword, {
|
deferreds.push(self._addDisabledPushRule('global', 'content', keyword, {
|
||||||
actions: self._actionsFor(pushRuleVectorStateKind),
|
actions: PushRuleVectorState.actionsFor(pushRuleVectorStateKind),
|
||||||
pattern: keyword
|
pattern: keyword
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -650,7 +455,7 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (kind === 'content') {
|
else if (kind === 'content') {
|
||||||
switch (self._contentRuleVectorStateKind(r)) {
|
switch (PushRuleVectorState.contentRuleVectorStateKind(r)) {
|
||||||
case PushRuleVectorState.ON:
|
case PushRuleVectorState.ON:
|
||||||
if (r.enabled) {
|
if (r.enabled) {
|
||||||
contentRules.on.push(r);
|
contentRules.on.push(r);
|
||||||
|
@ -762,7 +567,7 @@ module.exports = React.createClass({
|
||||||
var state = PushRuleVectorState[stateKey];
|
var state = PushRuleVectorState[stateKey];
|
||||||
var vectorStateToActions = ruleDefinition.vectorStateToActions[state];
|
var vectorStateToActions = ruleDefinition.vectorStateToActions[state];
|
||||||
|
|
||||||
if (vectorStateToActions === ACTION_DISABLED) {
|
if (!vectorStateToActions) {
|
||||||
// No defined actions means that this vector state expects a disabled default hs rule
|
// No defined actions means that this vector state expects a disabled default hs rule
|
||||||
if (rule.enabled === false) {
|
if (rule.enabled === false) {
|
||||||
vectorState = state;
|
vectorState = state;
|
||||||
|
@ -1107,12 +912,11 @@ module.exports = React.createClass({
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>Devices</h3>
|
|
||||||
|
|
||||||
{ devicesSection }
|
|
||||||
|
|
||||||
{ advancedSettings }
|
{ advancedSettings }
|
||||||
|
|
||||||
|
<h3>Devices</h3>
|
||||||
|
{ devicesSection }
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 OpenMarket Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
// Encodes a dictionary of {
|
||||||
|
// "notify": true/false,
|
||||||
|
// "sound": string or undefined,
|
||||||
|
// "highlight: true/false,
|
||||||
|
// }
|
||||||
|
// to a list of push actions.
|
||||||
|
encodeActions: function(action) {
|
||||||
|
var notify = action.notify;
|
||||||
|
var sound = action.sound;
|
||||||
|
var highlight = action.highlight;
|
||||||
|
if (notify) {
|
||||||
|
var actions = ["notify"];
|
||||||
|
if (sound) {
|
||||||
|
actions.push({"set_tweak": "sound", "value": sound});
|
||||||
|
}
|
||||||
|
if (highlight) {
|
||||||
|
actions.push({"set_tweak": "highlight"});
|
||||||
|
} else {
|
||||||
|
actions.push({"set_tweak": "highlight", "value": false});
|
||||||
|
}
|
||||||
|
return actions;
|
||||||
|
} else {
|
||||||
|
return ["dont_notify"];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Decode a list of actions to a dictionary of {
|
||||||
|
// "notify": true/false,
|
||||||
|
// "sound": string or undefined,
|
||||||
|
// "highlight: true/false,
|
||||||
|
// }
|
||||||
|
// If the actions couldn't be decoded then returns null.
|
||||||
|
decodeActions: function(actions) {
|
||||||
|
var notify = false;
|
||||||
|
var sound = null;
|
||||||
|
var highlight = false;
|
||||||
|
|
||||||
|
for (var i = 0; i < actions.length; ++i) {
|
||||||
|
var action = actions[i];
|
||||||
|
if (action === "notify") {
|
||||||
|
notify = true;
|
||||||
|
} else if (action === "dont_notify") {
|
||||||
|
notify = false;
|
||||||
|
} else if (typeof action === 'object') {
|
||||||
|
if (action.set_tweak === "sound") {
|
||||||
|
sound = action.value
|
||||||
|
} else if (action.set_tweak === "highlight") {
|
||||||
|
highlight = action.value;
|
||||||
|
} else {
|
||||||
|
// We don't understand this kind of tweak, so give up.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We don't understand this kind of action, so give up.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (highlight === undefined) {
|
||||||
|
// If a highlight tweak is missing a value then it defaults to true.
|
||||||
|
highlight = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = {notify: notify, highlight: highlight};
|
||||||
|
if (sound !== null) {
|
||||||
|
result.sound = sound;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 OpenMarket Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum for state of a push rule as defined by the Vector UI.
|
||||||
|
* @readonly
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
/** The push rule is disabled */
|
||||||
|
OFF: "off",
|
||||||
|
|
||||||
|
/** The user will receive push notification for this rule */
|
||||||
|
ON: "on",
|
||||||
|
|
||||||
|
/** The user will receive push notification for this rule with sound and
|
||||||
|
highlight if this is legitimate */
|
||||||
|
LOUD: "loud",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a PushRuleVectorState to a list of actions
|
||||||
|
*
|
||||||
|
* @return [object] list of push-rule actions
|
||||||
|
*/
|
||||||
|
actionsFor: function(pushRuleVectorState) {
|
||||||
|
if (pushRuleVectorState === this.ON) {
|
||||||
|
return ACTION_NOTIFY;
|
||||||
|
}
|
||||||
|
else if (pushRuleVectorState === this.LOUD) {
|
||||||
|
return ACTION_HIGHLIGHT_DEFAULT_SOUND;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a pushrule's actions to a PushRuleVectorState.
|
||||||
|
*
|
||||||
|
* Determines whether a content rule is in the PushRuleVectorState.ON
|
||||||
|
* category or in PushRuleVectorState.LOUD, regardless of its enabled
|
||||||
|
* state. Returns undefined if it does not match these categories.
|
||||||
|
*/
|
||||||
|
contentRuleVectorStateKind: function(rule) {
|
||||||
|
var stateKind;
|
||||||
|
|
||||||
|
// Count tweaks to determine if it is a ON or LOUD rule
|
||||||
|
var tweaks = 0;
|
||||||
|
for (var j in rule.actions) {
|
||||||
|
var action = rule.actions[j];
|
||||||
|
if (action.set_tweak === 'sound' ||
|
||||||
|
(action.set_tweak === 'highlight' && action.value)) {
|
||||||
|
tweaks++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch (tweaks) {
|
||||||
|
case 0:
|
||||||
|
stateKind = this.ON;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
stateKind = this.LOUD;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return stateKind;
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 OpenMarket Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var NotificationUtils = require('./NotificationUtils');
|
||||||
|
|
||||||
|
var encodeActions = NotificationUtils.encodeActions;
|
||||||
|
var decodeActions = NotificationUtils.decodeActions;
|
||||||
|
|
||||||
|
const ACTION_NOTIFY = encodeActions({notify: true});
|
||||||
|
const ACTION_NOTIFY_DEFAULT_SOUND = encodeActions({notify: true, sound: "default"});
|
||||||
|
const ACTION_NOTIFY_RING_SOUND = encodeActions({notify: true, sound: "ring"});
|
||||||
|
const ACTION_HIGHLIGHT_DEFAULT_SOUND = encodeActions({notify: true, sound: "default", highlight: true});
|
||||||
|
const ACTION_DONT_NOTIFY = encodeActions({notify: false});
|
||||||
|
const ACTION_DISABLED = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The descriptions of rules managed by the Vector UI.
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
// Messages containing user's display name
|
||||||
|
// (skip contains_user_name which is too geeky)
|
||||||
|
".m.rule.contains_display_name": {
|
||||||
|
kind: "underride",
|
||||||
|
description: "Messages containing my name",
|
||||||
|
vectorStateToActions: { // The actions for each vector state, or null to disable the rule.
|
||||||
|
on: ACTION_NOTIFY,
|
||||||
|
loud: ACTION_HIGHLIGHT_DEFAULT_SOUND,
|
||||||
|
off: ACTION_DISABLED
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Messages just sent to the user in a 1:1 room
|
||||||
|
".m.rule.room_one_to_one": {
|
||||||
|
kind: "underride",
|
||||||
|
description: "Messages in one-to-one chats",
|
||||||
|
vectorStateToActions: {
|
||||||
|
on: ACTION_NOTIFY,
|
||||||
|
loud: ACTION_NOTIFY_DEFAULT_SOUND,
|
||||||
|
off: ACTION_DONT_NOTIFY
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Messages just sent to a group chat room
|
||||||
|
// 1:1 room messages are catched by the .m.rule.room_one_to_one rule if any defined
|
||||||
|
// By opposition, all other room messages are from group chat rooms.
|
||||||
|
".m.rule.message": {
|
||||||
|
kind: "underride",
|
||||||
|
description: "Messages in group chats",
|
||||||
|
vectorStateToActions: {
|
||||||
|
on: ACTION_NOTIFY,
|
||||||
|
loud: ACTION_NOTIFY_DEFAULT_SOUND,
|
||||||
|
off: ACTION_DONT_NOTIFY
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Invitation for the user
|
||||||
|
".m.rule.invite_for_me": {
|
||||||
|
kind: "underride",
|
||||||
|
description: "When I'm invited to a room",
|
||||||
|
vectorStateToActions: {
|
||||||
|
on: ACTION_NOTIFY,
|
||||||
|
loud: ACTION_NOTIFY_DEFAULT_SOUND,
|
||||||
|
off: ACTION_DISABLED
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Incoming call
|
||||||
|
".m.rule.call": {
|
||||||
|
kind: "underride",
|
||||||
|
description: "Call invitation",
|
||||||
|
vectorStateToActions: {
|
||||||
|
on: ACTION_NOTIFY,
|
||||||
|
loud: ACTION_NOTIFY_RING_SOUND,
|
||||||
|
off: ACTION_DISABLED
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Notifications from bots
|
||||||
|
".m.rule.suppress_notices": {
|
||||||
|
kind: "override",
|
||||||
|
description: "Messages sent by bot",
|
||||||
|
vectorStateToActions: {
|
||||||
|
// .m.rule.suppress_notices is a "negative" rule, we have to invert its enabled value for vector UI
|
||||||
|
on: ACTION_DISABLED,
|
||||||
|
loud: ACTION_NOTIFY_DEFAULT_SOUND,
|
||||||
|
off: ACTION_DONT_NOTIFY,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 OpenMarket Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
NotificationUtils: require('./NotificationUtils'),
|
||||||
|
PushRuleVectorState: require('./PushRuleVectorState'),
|
||||||
|
VectorPushRulesDefinitions: require('./VectorPushRulesDefinitions'),
|
||||||
|
};
|
|
@ -58,6 +58,15 @@ input[type=text]:focus, textarea:focus {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* applied to side-panels and messagepanel when in RoomSettings */
|
||||||
|
.mx_fadable {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-transition: opacity 0.2s ease-in-out;
|
||||||
|
-moz-transition: opacity 0.2s ease-in-out;
|
||||||
|
-ms-transition: opacity 0.2s ease-in-out;
|
||||||
|
-o-transition: opacity 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
/* XXX: critical hack to GeminiScrollbar to allow them to work in FF 42 and Chrome 48.
|
/* XXX: critical hack to GeminiScrollbar to allow them to work in FF 42 and Chrome 48.
|
||||||
Stop the scrollbar view from pushing out the container's overall sizing, which causes
|
Stop the scrollbar view from pushing out the container's overall sizing, which causes
|
||||||
flexbox to adapt to the new size and cause the view to keep growing.
|
flexbox to adapt to the new size and cause the view to keep growing.
|
||||||
|
|
|
@ -89,7 +89,7 @@ limitations under the License.
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid #ccc;
|
||||||
|
|
||||||
-webkit-flex: 0 0 auto;
|
-webkit-flex: 0 0 auto;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|
|
@ -166,6 +166,7 @@ limitations under the License.
|
||||||
display: block;
|
display: block;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_last .mx_MessageTimestamp {
|
.mx_EventTile_last .mx_MessageTimestamp {
|
||||||
|
|
|
@ -29,6 +29,7 @@ limitations under the License.
|
||||||
flex: 0 0 100px;
|
flex: 0 0 100px;
|
||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_LinkPreviewWidget_caption {
|
.mx_LinkPreviewWidget_caption {
|
||||||
|
|
|
@ -53,6 +53,19 @@ limitations under the License.
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_RoomHeader_spinner {
|
||||||
|
height: 36px;
|
||||||
|
|
||||||
|
-webkit-box-ordinal-group: 2;
|
||||||
|
-moz-box-ordinal-group: 2;
|
||||||
|
-ms-flex-order: 2;
|
||||||
|
-webkit-order: 2;
|
||||||
|
order: 2;
|
||||||
|
|
||||||
|
padding-left: 12px;
|
||||||
|
padding-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_RoomHeader_textButton {
|
.mx_RoomHeader_textButton {
|
||||||
height: 36px;
|
height: 36px;
|
||||||
background-color: #76cfa6;
|
background-color: #76cfa6;
|
||||||
|
|
|
@ -104,6 +104,7 @@ function parseQs(location) {
|
||||||
// Here, we do some crude URL analysis to allow
|
// Here, we do some crude URL analysis to allow
|
||||||
// deep-linking.
|
// deep-linking.
|
||||||
function routeUrl(location) {
|
function routeUrl(location) {
|
||||||
|
console.log("Routing URL "+window.location);
|
||||||
var params = parseQs(location);
|
var params = parseQs(location);
|
||||||
var loginToken = params.loginToken;
|
var loginToken = params.loginToken;
|
||||||
if (loginToken) {
|
if (loginToken) {
|
||||||
|
@ -134,6 +135,7 @@ var lastLoadedScreen = null;
|
||||||
// This will be called whenever the SDK changes screens,
|
// This will be called whenever the SDK changes screens,
|
||||||
// so a web page can update the URL bar appropriately.
|
// so a web page can update the URL bar appropriately.
|
||||||
var onNewScreen = function(screen) {
|
var onNewScreen = function(screen) {
|
||||||
|
console.log("newscreen "+screen);
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
lastLoadedScreen = screen;
|
lastLoadedScreen = screen;
|
||||||
} else {
|
} else {
|
||||||
|
@ -158,6 +160,7 @@ var makeRegistrationUrl = function() {
|
||||||
|
|
||||||
window.addEventListener('hashchange', onHashChange);
|
window.addEventListener('hashchange', onHashChange);
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
|
console.log("window.onload");
|
||||||
if (!validBrowser) {
|
if (!validBrowser) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -172,6 +175,7 @@ window.onload = function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadApp() {
|
function loadApp() {
|
||||||
|
console.log("Vector starting at "+window.location);
|
||||||
if (validBrowser) {
|
if (validBrowser) {
|
||||||
var MatrixChat = sdk.getComponent('structures.MatrixChat');
|
var MatrixChat = sdk.getComponent('structures.MatrixChat');
|
||||||
var fragParts = parseQsFromFragment(window.location);
|
var fragParts = parseQsFromFragment(window.location);
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
// all-tests.js
|
||||||
|
//
|
||||||
|
// Our master test file: uses the webpack require API to find our test files
|
||||||
|
// and run them
|
||||||
|
|
||||||
|
// ideally these unit tests could be run under nodejs rather than in a browser
|
||||||
|
// via karma, but having two separate test frameworks in the same project
|
||||||
|
// seems confusing
|
||||||
|
var unit_tests = require.context('./unit-tests', true, /\.js$/);
|
||||||
|
unit_tests.keys().forEach(unit_tests);
|
||||||
|
|
||||||
|
var app_tests = require.context('./app-tests', true, /\.jsx?$/);
|
||||||
|
app_tests.keys().forEach(app_tests);
|
|
@ -0,0 +1,164 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 OpenMarket Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* joining.js: tests for the various paths when joining a room */
|
||||||
|
|
||||||
|
require('skin-sdk');
|
||||||
|
|
||||||
|
var jssdk = require('matrix-js-sdk');
|
||||||
|
|
||||||
|
var sdk = require('matrix-react-sdk');
|
||||||
|
var peg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
||||||
|
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||||
|
var MatrixChat = sdk.getComponent('structures.MatrixChat');
|
||||||
|
var RoomDirectory = sdk.getComponent('structures.RoomDirectory');
|
||||||
|
var RoomPreviewBar = sdk.getComponent('rooms.RoomPreviewBar');
|
||||||
|
var RoomView = sdk.getComponent('structures.RoomView');
|
||||||
|
|
||||||
|
var React = require('react');
|
||||||
|
var ReactDOM = require('react-dom');
|
||||||
|
var ReactTestUtils = require('react-addons-test-utils');
|
||||||
|
var expect = require('expect');
|
||||||
|
var q = require('q');
|
||||||
|
|
||||||
|
var test_utils = require('../test-utils');
|
||||||
|
var MockHttpBackend = require('../mock-request');
|
||||||
|
|
||||||
|
var HS_URL='http://localhost';
|
||||||
|
var IS_URL='http://localhost';
|
||||||
|
var USER_ID='@me:localhost';
|
||||||
|
var ACCESS_TOKEN='access_token';
|
||||||
|
|
||||||
|
describe('joining a room', function () {
|
||||||
|
describe('over federation', function () {
|
||||||
|
var parentDiv;
|
||||||
|
var httpBackend;
|
||||||
|
var matrixChat;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
test_utils.beforeEach(this);
|
||||||
|
httpBackend = new MockHttpBackend();
|
||||||
|
jssdk.request(httpBackend.requestFn);
|
||||||
|
parentDiv = document.createElement('div');
|
||||||
|
|
||||||
|
// uncomment this to actually add the div to the UI, to help with
|
||||||
|
// debugging (but slow things down)
|
||||||
|
// document.body.appendChild(parentDiv);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
if (parentDiv) {
|
||||||
|
ReactDOM.unmountComponentAtNode(parentDiv);
|
||||||
|
parentDiv.remove();
|
||||||
|
parentDiv = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not get stuck at a spinner', function(done) {
|
||||||
|
var ROOM_ALIAS = '#alias:localhost';
|
||||||
|
var ROOM_ID = '!id:localhost';
|
||||||
|
|
||||||
|
httpBackend.when('PUT', '/presence/'+encodeURIComponent(USER_ID)+'/status')
|
||||||
|
.respond(200, {});
|
||||||
|
httpBackend.when('GET', '/pushrules').respond(200, {});
|
||||||
|
httpBackend.when('POST', '/filter').respond(200, { filter_id: 'fid' });
|
||||||
|
httpBackend.when('GET', '/sync').respond(200, {});
|
||||||
|
httpBackend.when('GET', '/publicRooms').respond(200, {chunk: []});
|
||||||
|
|
||||||
|
// start with a logged-in client
|
||||||
|
peg.replaceUsingAccessToken(HS_URL, IS_URL, USER_ID, ACCESS_TOKEN);
|
||||||
|
|
||||||
|
var mc = <MatrixChat config={{}}/>;
|
||||||
|
matrixChat = ReactDOM.render(mc, parentDiv);
|
||||||
|
|
||||||
|
// switch to the Directory
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_room_directory',
|
||||||
|
});
|
||||||
|
|
||||||
|
var roomView;
|
||||||
|
httpBackend.flush().then(() => {
|
||||||
|
var roomDir = ReactTestUtils.findRenderedComponentWithType(
|
||||||
|
matrixChat, RoomDirectory);
|
||||||
|
|
||||||
|
// enter an alias in the input, and simulate enter
|
||||||
|
var input = ReactTestUtils.findRenderedDOMComponentWithTag(
|
||||||
|
roomDir, 'input');
|
||||||
|
input.value = ROOM_ALIAS;
|
||||||
|
ReactTestUtils.Simulate.keyUp(input, {key: 'Enter'});
|
||||||
|
|
||||||
|
// that should create a roomview which will start a peek; wait
|
||||||
|
// for the peek.
|
||||||
|
httpBackend.when('GET', '/rooms/'+encodeURIComponent(ROOM_ALIAS)+"/initialSync")
|
||||||
|
.respond(401, {errcode: 'M_GUEST_ACCESS_FORBIDDEN'});
|
||||||
|
return httpBackend.flush();
|
||||||
|
}).then(() => {
|
||||||
|
httpBackend.verifyNoOutstandingExpectation();
|
||||||
|
|
||||||
|
// we should now have a roomview, with a preview bar
|
||||||
|
roomView = ReactTestUtils.findRenderedComponentWithType(
|
||||||
|
matrixChat, RoomView);
|
||||||
|
|
||||||
|
var previewBar = ReactTestUtils.findRenderedComponentWithType(
|
||||||
|
roomView, RoomPreviewBar);
|
||||||
|
|
||||||
|
var joinLink = ReactTestUtils.findRenderedDOMComponentWithTag(
|
||||||
|
previewBar, 'a');
|
||||||
|
|
||||||
|
ReactTestUtils.Simulate.click(joinLink);
|
||||||
|
|
||||||
|
// that will fire off a request to check our displayname, followed by a
|
||||||
|
// join request
|
||||||
|
httpBackend.when('GET', '/profile/'+encodeURIComponent(USER_ID))
|
||||||
|
.respond(200, {displayname: 'boris'});
|
||||||
|
httpBackend.when('POST', '/join/'+encodeURIComponent(ROOM_ALIAS))
|
||||||
|
.respond(200, {room_id: ROOM_ID});
|
||||||
|
return httpBackend.flush();
|
||||||
|
}).then(() => {
|
||||||
|
httpBackend.verifyNoOutstandingExpectation();
|
||||||
|
|
||||||
|
// the roomview should now be loading
|
||||||
|
expect(roomView.state.room).toBe(null);
|
||||||
|
expect(roomView.state.joining).toBe(true);
|
||||||
|
|
||||||
|
// there should be a spinner
|
||||||
|
ReactTestUtils.findRenderedDOMComponentWithClass(
|
||||||
|
roomView, "mx_Spinner");
|
||||||
|
|
||||||
|
// now send the room down the /sync pipe
|
||||||
|
httpBackend.when('GET', '/sync').
|
||||||
|
respond(200, {
|
||||||
|
rooms: {
|
||||||
|
join: {
|
||||||
|
[ROOM_ID]: {
|
||||||
|
state: {},
|
||||||
|
timeline: {
|
||||||
|
events: [],
|
||||||
|
limited: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return httpBackend.flush();
|
||||||
|
}).then(() => {
|
||||||
|
// now the room should have loaded
|
||||||
|
expect(roomView.state.room).toExist();
|
||||||
|
expect(roomView.state.joining).toBe(false);
|
||||||
|
}).done(done, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,228 @@
|
||||||
|
"use strict";
|
||||||
|
var q = require("q");
|
||||||
|
var expect = require('expect');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a mock HTTP backend, heavily inspired by Angular.js.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function HttpBackend() {
|
||||||
|
this.requests = [];
|
||||||
|
this.expectedRequests = [];
|
||||||
|
var self = this;
|
||||||
|
// the request function dependency that the SDK needs.
|
||||||
|
this.requestFn = function(opts, callback) {
|
||||||
|
var realReq = new Request(opts.method, opts.uri, opts.body, opts.qs);
|
||||||
|
realReq.callback = callback;
|
||||||
|
console.log("HTTP backend received request: %s %s", opts.method, opts.uri);
|
||||||
|
self.requests.push(realReq);
|
||||||
|
|
||||||
|
var abort = function() {
|
||||||
|
var idx = self.requests.indexOf(realReq);
|
||||||
|
if (idx >= 0) {
|
||||||
|
console.log("Aborting HTTP request: %s %s", opts.method, opts.uri);
|
||||||
|
self.requests.splice(idx, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
abort: abort
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
HttpBackend.prototype = {
|
||||||
|
/**
|
||||||
|
* Respond to all of the requests (flush the queue).
|
||||||
|
* @param {string} path The path to flush (optional) default: all.
|
||||||
|
* @param {integer} numToFlush The number of things to flush (optional), default: all.
|
||||||
|
* @return {Promise} resolved when there is nothing left to flush.
|
||||||
|
*/
|
||||||
|
flush: function(path, numToFlush) {
|
||||||
|
var defer = q.defer();
|
||||||
|
var self = this;
|
||||||
|
var flushed = 0;
|
||||||
|
var triedWaiting = false;
|
||||||
|
console.log(
|
||||||
|
"HTTP backend flushing... (path=%s numToFlush=%s)", path, numToFlush
|
||||||
|
);
|
||||||
|
var tryFlush = function() {
|
||||||
|
// if there's more real requests and more expected requests, flush 'em.
|
||||||
|
console.log(
|
||||||
|
" trying to flush queue => reqs=%s expected=%s [%s]",
|
||||||
|
self.requests.length, self.expectedRequests.length, path
|
||||||
|
);
|
||||||
|
if (self._takeFromQueue(path)) {
|
||||||
|
// try again on the next tick.
|
||||||
|
console.log(" flushed. Trying for more. [%s]", path);
|
||||||
|
flushed += 1;
|
||||||
|
if (numToFlush && flushed === numToFlush) {
|
||||||
|
console.log(" [%s] Flushed assigned amount: %s", path, numToFlush);
|
||||||
|
defer.resolve();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setTimeout(tryFlush, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (flushed === 0 && !triedWaiting) {
|
||||||
|
// we may not have made the request yet, wait a generous amount of
|
||||||
|
// time before giving up.
|
||||||
|
setTimeout(tryFlush, 5);
|
||||||
|
triedWaiting = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(" no more flushes. [%s]", path);
|
||||||
|
defer.resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setTimeout(tryFlush, 0);
|
||||||
|
|
||||||
|
return defer.promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to resolve requests/expected requests.
|
||||||
|
* @param {string} path The path to flush (optional) default: all.
|
||||||
|
* @return {boolean} true if something was resolved.
|
||||||
|
*/
|
||||||
|
_takeFromQueue: function(path) {
|
||||||
|
var req = null;
|
||||||
|
var i, j;
|
||||||
|
var matchingReq, expectedReq, testResponse = null;
|
||||||
|
for (i = 0; i < this.requests.length; i++) {
|
||||||
|
req = this.requests[i];
|
||||||
|
for (j = 0; j < this.expectedRequests.length; j++) {
|
||||||
|
expectedReq = this.expectedRequests[j];
|
||||||
|
if (path && path !== expectedReq.path) { continue; }
|
||||||
|
if (expectedReq.method === req.method &&
|
||||||
|
req.path.indexOf(expectedReq.path) !== -1) {
|
||||||
|
if (!expectedReq.data || (JSON.stringify(expectedReq.data) ===
|
||||||
|
JSON.stringify(req.data))) {
|
||||||
|
matchingReq = expectedReq;
|
||||||
|
this.expectedRequests.splice(j, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchingReq) {
|
||||||
|
// remove from request queue
|
||||||
|
this.requests.splice(i, 1);
|
||||||
|
i--;
|
||||||
|
|
||||||
|
for (j = 0; j < matchingReq.checks.length; j++) {
|
||||||
|
matchingReq.checks[j](req);
|
||||||
|
}
|
||||||
|
testResponse = matchingReq.response;
|
||||||
|
console.log(" responding to %s", matchingReq.path);
|
||||||
|
var body = testResponse.body;
|
||||||
|
if (Object.prototype.toString.call(body) == "[object Function]") {
|
||||||
|
body = body(req.path, req.data);
|
||||||
|
}
|
||||||
|
req.callback(
|
||||||
|
testResponse.err, testResponse.response, body
|
||||||
|
);
|
||||||
|
matchingReq = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (testResponse) { // flushed something
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes sure that the SDK hasn't sent any more requests to the backend.
|
||||||
|
*/
|
||||||
|
verifyNoOutstandingRequests: function() {
|
||||||
|
var firstOutstandingReq = this.requests[0] || {};
|
||||||
|
expect(this.requests.length).toEqual(0,
|
||||||
|
"Expected no more HTTP requests but received request to " +
|
||||||
|
firstOutstandingReq.path
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes sure that the test doesn't have any unresolved requests.
|
||||||
|
*/
|
||||||
|
verifyNoOutstandingExpectation: function() {
|
||||||
|
var firstOutstandingExpectation = this.expectedRequests[0] || {};
|
||||||
|
expect(this.expectedRequests.length).toEqual(
|
||||||
|
0,
|
||||||
|
"Expected to see HTTP request for "
|
||||||
|
+ firstOutstandingExpectation.method
|
||||||
|
+ " " + firstOutstandingExpectation.path
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an expected request.
|
||||||
|
* @param {string} method The HTTP method
|
||||||
|
* @param {string} path The path (which can be partial)
|
||||||
|
* @param {Object} data The expected data.
|
||||||
|
* @return {Request} An expected request.
|
||||||
|
*/
|
||||||
|
when: function(method, path, data) {
|
||||||
|
var pendingReq = new Request(method, path, data);
|
||||||
|
this.expectedRequests.push(pendingReq);
|
||||||
|
return pendingReq;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function Request(method, path, data, queryParams) {
|
||||||
|
this.method = method;
|
||||||
|
this.path = path;
|
||||||
|
this.data = data;
|
||||||
|
this.queryParams = queryParams;
|
||||||
|
this.callback = null;
|
||||||
|
this.response = null;
|
||||||
|
this.checks = [];
|
||||||
|
}
|
||||||
|
Request.prototype = {
|
||||||
|
/**
|
||||||
|
* Execute a check when this request has been satisfied.
|
||||||
|
* @param {Function} fn The function to execute.
|
||||||
|
* @return {Request} for chaining calls.
|
||||||
|
*/
|
||||||
|
check: function(fn) {
|
||||||
|
this.checks.push(fn);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Respond with the given data when this request is satisfied.
|
||||||
|
* @param {Number} code The HTTP status code.
|
||||||
|
* @param {Object|Function} data The HTTP JSON body. If this is a function,
|
||||||
|
* it will be invoked when the JSON body is required (which should be returned).
|
||||||
|
*/
|
||||||
|
respond: function(code, data) {
|
||||||
|
this.response = {
|
||||||
|
response: {
|
||||||
|
statusCode: code,
|
||||||
|
headers: {}
|
||||||
|
},
|
||||||
|
body: data,
|
||||||
|
err: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fail with an Error when this request is satisfied.
|
||||||
|
* @param {Number} code The HTTP status code.
|
||||||
|
* @param {Error} err The error to throw (e.g. Network Error)
|
||||||
|
*/
|
||||||
|
fail: function(code, err) {
|
||||||
|
this.response = {
|
||||||
|
response: {
|
||||||
|
statusCode: code,
|
||||||
|
headers: {}
|
||||||
|
},
|
||||||
|
body: null,
|
||||||
|
err: err
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The HttpBackend class.
|
||||||
|
*/
|
||||||
|
module.exports = HttpBackend;
|
|
@ -0,0 +1,8 @@
|
||||||
|
/*
|
||||||
|
* skin-sdk.js
|
||||||
|
*
|
||||||
|
* Skins the react-sdk with the vector components
|
||||||
|
*/
|
||||||
|
|
||||||
|
var sdk = require('matrix-react-sdk');
|
||||||
|
sdk.loadSkin(require('component-index'));
|
|
@ -0,0 +1,24 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var q = require('q');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform common actions before each test case, e.g. printing the test case
|
||||||
|
* name to stdout.
|
||||||
|
* @param {Mocha.Context} context The test context
|
||||||
|
*/
|
||||||
|
module.exports.beforeEach = function(context) {
|
||||||
|
var desc = context.currentTest.fullTitle();
|
||||||
|
console.log();
|
||||||
|
console.log(desc);
|
||||||
|
console.log(new Array(1 + desc.length).join("="));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns true if the current environment supports webrtc
|
||||||
|
*/
|
||||||
|
module.exports.browserSupportsWebRTC = function() {
|
||||||
|
var n = global.window.navigator;
|
||||||
|
return n.getUserMedia || n.webkitGetUserMedia ||
|
||||||
|
n.mozGetUserMedia;
|
||||||
|
};
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 OpenMarket Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var notifications = require('notifications');
|
||||||
|
|
||||||
|
var prvs = notifications.PushRuleVectorState;
|
||||||
|
|
||||||
|
var expect = require('expect');
|
||||||
|
|
||||||
|
describe("PushRuleVectorState", function() {
|
||||||
|
describe("contentRuleVectorStateKind", function() {
|
||||||
|
it("should understand normal notifications", function () {
|
||||||
|
expect(prvs.contentRuleVectorStateKind(["notify"])).
|
||||||
|
toEqual(prvs.ON);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -42,6 +42,9 @@ module.exports = {
|
||||||
// we tend to get the react source included twice when using npm link.
|
// we tend to get the react source included twice when using npm link.
|
||||||
react: path.resolve('./node_modules/react'),
|
react: path.resolve('./node_modules/react'),
|
||||||
|
|
||||||
|
// same goes for js-sdk
|
||||||
|
"matrix-js-sdk": path.resolve('./node_modules/matrix-js-sdk'),
|
||||||
|
|
||||||
// matrix-js-sdk will use olm if it is available,
|
// matrix-js-sdk will use olm if it is available,
|
||||||
// but does not explicitly depend on it. Pull it
|
// but does not explicitly depend on it. Pull it
|
||||||
// in from node_modules if it's there.
|
// in from node_modules if it's there.
|
||||||
|
|
Loading…
Reference in New Issue