Merge branch 'develop' of github.com:vector-im/riot-web into set_default_federate_by_settings

This commit is contained in:
Michael Telatynski 2017-08-02 12:24:15 +01:00
commit bf09c14e44
No known key found for this signature in database
GPG Key ID: 0435A1D4BBD34D64
68 changed files with 1426 additions and 787 deletions

View File

@ -1,4 +1,4 @@
{ {
"presets": ["react", "es2015", "es2016"], "presets": ["react", "es2015", "es2016"],
"plugins": ["transform-class-properties", "transform-object-rest-spread", "transform-async-to-generator", "transform-runtime", "add-module-exports"] "plugins": ["transform-class-properties", "transform-object-rest-spread", "transform-async-to-bluebird", "transform-runtime", "add-module-exports"]
} }

View File

@ -1,3 +1,10 @@
# we need trusty for the chrome addon
dist: trusty
# we don't need sudo, so can run in a container, which makes startup much
# quicker.
sudo: false
language: node_js language: node_js
node_js: node_js:
# make sure we work with a range of node versions. # make sure we work with a range of node versions.
@ -5,8 +12,9 @@ node_js:
# - 4.x is still in LTS (until April 2018), but some of our deps (notably # - 4.x is still in LTS (until April 2018), but some of our deps (notably
# extract-zip) don't work with it # extract-zip) don't work with it
# - 5.x has been EOLed for nearly a year. # - 5.x has been EOLed for nearly a year.
# - 6.x is the current 'LTS' version # - 6.x is the active 'LTS' version
# - 7.x is the current 'current' version (until October 2017) # - 7.x is no longer supported
# - 8.x is the current 'current' version (until October 2017)
# #
# see: https://github.com/nodejs/LTS/ # see: https://github.com/nodejs/LTS/
# #
@ -16,6 +24,8 @@ node_js:
- 6.3 - 6.3
- 6 - 6
- 7 - 7
addons:
chrome: stable
install: install:
# clone the deps with depth 1: we know we will only ever need that one # clone the deps with depth 1: we know we will only ever need that one
# commit. # commit.

View File

@ -5,7 +5,7 @@ Changes in [0.11.4](https://github.com/vector-im/riot-web/releases/tag/v0.11.4)
* Update matrix-js-sdk and react-sdk to fix a regression where the * Update matrix-js-sdk and react-sdk to fix a regression where the
background indexedb worker was disabled, failures to open indexeddb background indexedb worker was disabled, failures to open indexeddb
causing the app to fail to start, a race when starting that could break causing the app to fail to start, a race when starting that could break
switching to rooms, and the inability to to invite user with mixed case switching to rooms, and the inability to invite users with mixed case
usernames. usernames.
Changes in [0.11.3](https://github.com/vector-im/riot-web/releases/tag/v0.11.3) (2017-06-20) Changes in [0.11.3](https://github.com/vector-im/riot-web/releases/tag/v0.11.3) (2017-06-20)

View File

@ -81,7 +81,7 @@ to build.
npm run build npm run build
``` ```
However, we recommend setting up a proper development environment (see "Setting However, we recommend setting up a proper development environment (see "Setting
up a development environment" below) if you want to run your own copy of the up a dev environment" below) if you want to run your own copy of the
`develop` branch, as it makes it much easier to keep these dependencies `develop` branch, as it makes it much easier to keep these dependencies
up-to-date. Or just use https://riot.im/develop - the continuous integration up-to-date. Or just use https://riot.im/develop - the continuous integration
release of the develop branch. release of the develop branch.
@ -253,7 +253,6 @@ Finally, build and start Riot itself:
1. `rm -r node_modules/matrix-react-sdk; ln -s ../../matrix-react-sdk node_modules/` 1. `rm -r node_modules/matrix-react-sdk; ln -s ../../matrix-react-sdk node_modules/`
1. `npm start` 1. `npm start`
1. Wait a few seconds for the initial build to finish; you should see something like: 1. Wait a few seconds for the initial build to finish; you should see something like:
``` ```
Hash: b0af76309dd56d7275c8 Hash: b0af76309dd56d7275c8
Version: webpack 1.12.14 Version: webpack 1.12.14
@ -282,19 +281,34 @@ If any of these steps error with, `file table overflow`, you are probably on a m
which has a very low limit on max open files. Run `ulimit -Sn 1024` and try again. which has a very low limit on max open files. Run `ulimit -Sn 1024` and try again.
You'll need to do this in each new terminal you open before building Riot. You'll need to do this in each new terminal you open before building Riot.
How to add a new translation? Running the tests
============================= -----------------
There are a number of application-level tests in the `tests` directory; these
are designed to run in a browser instance under the control of
[karma](https://karma-runner.github.io). To run them:
* Make sure you have Chrome installed (a recent version, like 59)
* Make sure you have `matrix-js-sdk` and `matrix-react-sdk` installed and
built, as above
* `npm run test`
The above will run the tests under Chrome in a `headless` mode.
You can also tell karma to run the tests in a loop (every time the source
changes), in an instance of Chrome on your desktop, with `npm run
test-multi`. This also gives you the option of running the tests in 'debug'
mode, which is useful for stepping through the tests in the developer tools.
Translations
============
To add a new translation, head to the [translating doc](docs/translating.md).
For a developer guide, see the [translating dev doc](docs/translating-dev.md).
[<img src="https://translate.riot.im/widgets/riot-web/-/multi-auto.svg" alt="translationsstatus" width="340">](https://translate.riot.im/engage/riot-web/?utm_source=widget) [<img src="https://translate.riot.im/widgets/riot-web/-/multi-auto.svg" alt="translationsstatus" width="340">](https://translate.riot.im/engage/riot-web/?utm_source=widget)
Head to the [translating doc](docs/translating.md)
Adding Strings to the translations (Developer Guide)
====================================================
Head to the [translating dev doc](docs/translating-dev.md)
Triaging issues Triaging issues
=============== ===============

View File

@ -29,6 +29,7 @@ const AutoLaunch = require('auto-launch');
const tray = require('./tray'); const tray = require('./tray');
const vectorMenu = require('./vectormenu'); const vectorMenu = require('./vectormenu');
const webContentsHandler = require('./webcontents-handler'); const webContentsHandler = require('./webcontents-handler');
const updater = require('./updater');
const windowStateKeeper = require('electron-window-state'); const windowStateKeeper = require('electron-window-state');
@ -46,69 +47,9 @@ try {
// Continue with the defaults (ie. an empty config) // Continue with the defaults (ie. an empty config)
} }
const UPDATE_POLL_INTERVAL_MS = 60 * 60 * 1000;
const INITIAL_UPDATE_DELAY_MS = 30 * 1000;
let mainWindow = null; let mainWindow = null;
let appQuitting = false; global.appQuitting = false;
function installUpdate() {
// for some reason, quitAndInstall does not fire the
// before-quit event, so we need to set the flag here.
appQuitting = true;
electron.autoUpdater.quitAndInstall();
}
function pollForUpdates() {
try {
electron.autoUpdater.checkForUpdates();
} catch (e) {
console.log('Couldn\'t check for update', e);
}
}
function startAutoUpdate(updateBaseUrl) {
if (updateBaseUrl.slice(-1) !== '/') {
updateBaseUrl = updateBaseUrl + '/';
}
try {
// For reasons best known to Squirrel, the way it checks for updates
// is completely different between macOS and windows. On macOS, it
// hits a URL that either gives it a 200 with some json or
// 204 No Content. On windows it takes a base path and looks for
// files under that path.
if (process.platform === 'darwin') {
// include the current version in the URL we hit. Electron doesn't add
// it anywhere (apart from the User-Agent) so it's up to us. We could
// (and previously did) just use the User-Agent, but this doesn't
// rely on NSURLConnection setting the User-Agent to what we expect,
// and also acts as a convenient cache-buster to ensure that when the
// app updates it always gets a fresh value to avoid update-looping.
electron.autoUpdater.setFeedURL(
`${updateBaseUrl}macos/?localVersion=${encodeURIComponent(electron.app.getVersion())}`);
} else if (process.platform === 'win32') {
electron.autoUpdater.setFeedURL(`${updateBaseUrl}win32/${process.arch}/`);
} else {
// Squirrel / electron only supports auto-update on these two platforms.
// I'm not even going to try to guess which feed style they'd use if they
// implemented it on Linux, or if it would be different again.
console.log('Auto update not supported on this platform');
}
// We check for updates ourselves rather than using 'updater' because we need to
// do it in the main process (and we don't really need to check every 10 minutes:
// every hour should be just fine for a desktop app)
// However, we still let the main window listen for the update events.
// We also wait a short time before checking for updates the first time because
// of squirrel on windows and it taking a small amount of time to release a
// lock file.
setTimeout(pollForUpdates, INITIAL_UPDATE_DELAY_MS);
setInterval(pollForUpdates, UPDATE_POLL_INTERVAL_MS);
} catch (err) {
// will fail if running in debug mode
console.log('Couldn\'t enable update checking', err);
}
}
// handle uncaught errors otherwise it displays // handle uncaught errors otherwise it displays
// stack traces in popup dialogs, which is terrible (which // stack traces in popup dialogs, which is terrible (which
@ -120,8 +61,6 @@ process.on('uncaughtException', function(error) {
console.log('Unhandled exception', error); console.log('Unhandled exception', error);
}); });
electron.ipcMain.on('install_update', installUpdate);
let focusHandlerAttached = false; let focusHandlerAttached = false;
electron.ipcMain.on('setBadgeCount', function(ev, count) { electron.ipcMain.on('setBadgeCount', function(ev, count) {
electron.app.setBadgeCount(count); electron.app.setBadgeCount(count);
@ -233,7 +172,7 @@ electron.app.on('ready', () => {
if (vectorConfig.update_base_url) { if (vectorConfig.update_base_url) {
console.log(`Starting auto update with base URL: ${vectorConfig.update_base_url}`); console.log(`Starting auto update with base URL: ${vectorConfig.update_base_url}`);
startAutoUpdate(vectorConfig.update_base_url); updater.start(vectorConfig.update_base_url);
} else { } else {
console.log('No update_base_url is defined: auto update is disabled'); console.log('No update_base_url is defined: auto update is disabled');
} }
@ -246,7 +185,7 @@ electron.app.on('ready', () => {
defaultHeight: 768, defaultHeight: 768,
}); });
mainWindow = new electron.BrowserWindow({ mainWindow = global.mainWindow = new electron.BrowserWindow({
icon: iconPath, icon: iconPath,
show: false, show: false,
autoHideMenuBar: true, autoHideMenuBar: true,
@ -264,7 +203,7 @@ electron.app.on('ready', () => {
mainWindow.hide(); mainWindow.hide();
// Create trayIcon icon // Create trayIcon icon
tray.create(mainWindow, { tray.create({
icon_path: iconPath, icon_path: iconPath,
brand: vectorConfig.brand || 'Riot', brand: vectorConfig.brand || 'Riot',
}); });
@ -276,10 +215,10 @@ electron.app.on('ready', () => {
} }
mainWindow.on('closed', () => { mainWindow.on('closed', () => {
mainWindow = null; mainWindow = global.mainWindow = null;
}); });
mainWindow.on('close', (e) => { mainWindow.on('close', (e) => {
if (!appQuitting && (tray.hasTray() || process.platform === 'darwin')) { if (!global.appQuitting && (tray.hasTray() || process.platform === 'darwin')) {
// On Mac, closing the window just hides it // On Mac, closing the window just hides it
// (this is generally how single-window Mac apps // (this is generally how single-window Mac apps
// behave, eg. Mail.app) // behave, eg. Mail.app)
@ -302,7 +241,10 @@ electron.app.on('activate', () => {
}); });
electron.app.on('before-quit', () => { electron.app.on('before-quit', () => {
appQuitting = true; global.appQuitting = true;
if (mainWindow) {
mainWindow.webContents.send('before-quit');
}
}); });
// Set the App User Model ID to match what the squirrel // Set the App User Model ID to match what the squirrel

View File

@ -26,17 +26,17 @@ exports.hasTray = function hasTray() {
return (trayIcon !== null); return (trayIcon !== null);
}; };
exports.create = function(win, config) { exports.create = function(config) {
// no trays on darwin // no trays on darwin
if (process.platform === 'darwin' || trayIcon) return; if (process.platform === 'darwin' || trayIcon) return;
const toggleWin = function() { const toggleWin = function() {
if (win.isVisible() && !win.isMinimized()) { if (global.mainWindow.isVisible() && !global.mainWindow.isMinimized()) {
win.hide(); global.mainWindow.hide();
} else { } else {
if (win.isMinimized()) win.restore(); if (global.mainWindow.isMinimized()) global.mainWindow.restore();
if (!win.isVisible()) win.show(); if (!global.mainWindow.isVisible()) global.mainWindow.show();
win.focus(); global.mainWindow.focus();
} }
}; };
@ -54,41 +54,46 @@ exports.create = function(win, config) {
}, },
]); ]);
trayIcon = new Tray(config.icon_path); const defaultIcon = nativeImage.createFromPath(config.icon_path);
trayIcon = new Tray(defaultIcon);
trayIcon.setToolTip(config.brand); trayIcon.setToolTip(config.brand);
trayIcon.setContextMenu(contextMenu); trayIcon.setContextMenu(contextMenu);
trayIcon.on('click', toggleWin); trayIcon.on('click', toggleWin);
let lastFavicon = null; let lastFavicon = null;
win.webContents.on('page-favicon-updated', async function(ev, favicons) { global.mainWindow.webContents.on('page-favicon-updated', async function(ev, favicons) {
let newFavicon = config.icon_path; if (!favicons || favicons.length <= 0 || !favicons[0].startsWith('data:')) {
if (favicons && favicons.length > 0 && favicons[0].startsWith('data:')) { if (lastFavicon !== null) {
newFavicon = favicons[0]; win.setIcon(defaultIcon);
trayIcon.setImage(defaultIcon);
lastFavicon = null;
}
return;
} }
// No need to change, shortcut // No need to change, shortcut
if (newFavicon === lastFavicon) return; if (favicons[0] === lastFavicon) return;
lastFavicon = newFavicon; lastFavicon = favicons[0];
// if its not default we have to construct into nativeImage let newFavicon = nativeImage.createFromDataURL(favicons[0]);
if (newFavicon !== config.icon_path) {
newFavicon = nativeImage.createFromDataURL(favicons[0]);
// Windows likes ico's too much.
if (process.platform === 'win32') { if (process.platform === 'win32') {
try { try {
const icoPath = path.join(app.getPath('temp'), 'win32_riot_icon.ico') const icoPath = path.join(app.getPath('temp'), 'win32_riot_icon.ico');
const icoBuf = await pngToIco(newFavicon.toPNG()); fs.writeFileSync(icoPath, await pngToIco(newFavicon.toPNG()));
fs.writeFileSync(icoPath, icoBuf); newFavicon = nativeImage.createFromPath(icoPath);
newFavicon = icoPath; } catch (e) {
} catch (e) {console.error(e);} console.error("Failed to make win32 ico", e);
} }
} }
trayIcon.setImage(newFavicon); trayIcon.setImage(newFavicon);
win.setIcon(newFavicon); global.mainWindow.setIcon(newFavicon);
}); });
win.webContents.on('page-title-updated', function(ev, title) { global.mainWindow.webContents.on('page-title-updated', function(ev, title) {
trayIcon.setToolTip(title); trayIcon.setToolTip(title);
}); });
}; };

View File

@ -0,0 +1,84 @@
const { app, autoUpdater, ipcMain } = require('electron');
const UPDATE_POLL_INTERVAL_MS = 60 * 60 * 1000;
const INITIAL_UPDATE_DELAY_MS = 30 * 1000;
function installUpdate() {
// for some reason, quitAndInstall does not fire the
// before-quit event, so we need to set the flag here.
global.appQuitting = true;
autoUpdater.quitAndInstall();
}
function pollForUpdates() {
try {
autoUpdater.checkForUpdates();
} catch (e) {
console.log('Couldn\'t check for update', e);
}
}
module.exports = {};
module.exports.start = function startAutoUpdate(updateBaseUrl) {
if (updateBaseUrl.slice(-1) !== '/') {
updateBaseUrl = updateBaseUrl + '/';
}
try {
let url;
// For reasons best known to Squirrel, the way it checks for updates
// is completely different between macOS and windows. On macOS, it
// hits a URL that either gives it a 200 with some json or
// 204 No Content. On windows it takes a base path and looks for
// files under that path.
if (process.platform === 'darwin') {
// include the current version in the URL we hit. Electron doesn't add
// it anywhere (apart from the User-Agent) so it's up to us. We could
// (and previously did) just use the User-Agent, but this doesn't
// rely on NSURLConnection setting the User-Agent to what we expect,
// and also acts as a convenient cache-buster to ensure that when the
// app updates it always gets a fresh value to avoid update-looping.
url = `${updateBaseUrl}macos/?localVersion=${encodeURIComponent(app.getVersion())}`;
} else if (process.platform === 'win32') {
url = `${updateBaseUrl}win32/${process.arch}/`;
} else {
// Squirrel / electron only supports auto-update on these two platforms.
// I'm not even going to try to guess which feed style they'd use if they
// implemented it on Linux, or if it would be different again.
console.log('Auto update not supported on this platform');
}
if (url) {
autoUpdater.setFeedURL(url);
// We check for updates ourselves rather than using 'updater' because we need to
// do it in the main process (and we don't really need to check every 10 minutes:
// every hour should be just fine for a desktop app)
// However, we still let the main window listen for the update events.
// We also wait a short time before checking for updates the first time because
// of squirrel on windows and it taking a small amount of time to release a
// lock file.
setTimeout(pollForUpdates, INITIAL_UPDATE_DELAY_MS);
setInterval(pollForUpdates, UPDATE_POLL_INTERVAL_MS);
}
} catch (err) {
// will fail if running in debug mode
console.log('Couldn\'t enable update checking', err);
}
}
ipcMain.on('install_update', installUpdate);
ipcMain.on('check_updates', pollForUpdates);
function ipcChannelSendUpdateStatus(status) {
if (global.mainWindow) {
global.mainWindow.webContents.send('check_updates', status);
}
}
autoUpdater.on('update-available', function() {
ipcChannelSendUpdateStatus(true);
}).on('update-not-available', function() {
ipcChannelSendUpdateStatus(false);
}).on('error', function(error) {
ipcChannelSendUpdateStatus(error.message);
});

View File

@ -113,8 +113,23 @@ module.exports = function (config) {
browsers: [ browsers: [
'Chrome', 'Chrome',
//'PhantomJS', //'PhantomJS',
//'ChromeHeadless'
], ],
customLaunchers: {
'ChromeHeadless': {
base: 'Chrome',
flags: [
// '--no-sandbox',
// See https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md
'--headless',
'--disable-gpu',
// Without a remote debugging port, Google Chrome exits immediately.
'--remote-debugging-port=9222',
],
}
},
// Continuous Integration mode // Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits // if true, Karma captures browsers, runs the tests and exits
// singleRun: false, // singleRun: false,

View File

@ -48,12 +48,13 @@
"lintall": "eslint src/ test/", "lintall": "eslint src/ test/",
"clean": "rimraf lib webapp electron_app/dist", "clean": "rimraf lib webapp electron_app/dist",
"prepublish": "npm run build:compile", "prepublish": "npm run build:compile",
"test": "karma start --single-run=true --autoWatch=false --browsers PhantomJS --colors=false", "test": "karma start --single-run=true --autoWatch=false --browsers ChromeHeadless",
"test-multi": "karma start" "test-multi": "karma start"
}, },
"dependencies": { "dependencies": {
"babel-polyfill": "^6.5.0", "babel-polyfill": "^6.5.0",
"babel-runtime": "^6.11.6", "babel-runtime": "^6.11.6",
"bluebird": "^3.5.0",
"browser-request": "^0.3.3", "browser-request": "^0.3.3",
"classnames": "^2.1.2", "classnames": "^2.1.2",
"draft-js": "^0.8.1", "draft-js": "^0.8.1",
@ -69,11 +70,10 @@
"matrix-react-sdk": "0.9.7", "matrix-react-sdk": "0.9.7",
"modernizr": "^3.1.0", "modernizr": "^3.1.0",
"pako": "^1.0.5", "pako": "^1.0.5",
"q": "^1.4.1", "react": "^15.6.0",
"react": "^15.4.0",
"react-dnd": "^2.1.4", "react-dnd": "^2.1.4",
"react-dnd-html5-backend": "^2.1.2", "react-dnd-html5-backend": "^2.1.2",
"react-dom": "^15.4.0", "react-dom": "^15.6.0",
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#5e97aef", "react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#5e97aef",
"sanitize-html": "^1.11.1", "sanitize-html": "^1.11.1",
"text-encoding-utf-8": "^1.0.1", "text-encoding-utf-8": "^1.0.1",
@ -88,7 +88,7 @@
"babel-eslint": "^6.1.0", "babel-eslint": "^6.1.0",
"babel-loader": "^6.2.5", "babel-loader": "^6.2.5",
"babel-plugin-add-module-exports": "^0.2.1", "babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-transform-async-to-generator": "^6.16.0", "babel-plugin-transform-async-to-bluebird": "^1.1.1",
"babel-plugin-transform-class-properties": "^6.16.0", "babel-plugin-transform-class-properties": "^6.16.0",
"babel-plugin-transform-object-rest-spread": "^6.16.0", "babel-plugin-transform-object-rest-spread": "^6.16.0",
"babel-plugin-transform-runtime": "^6.15.0", "babel-plugin-transform-runtime": "^6.15.0",
@ -119,13 +119,13 @@
"karma-cli": "^0.1.2", "karma-cli": "^0.1.2",
"karma-junit-reporter": "^0.4.1", "karma-junit-reporter": "^0.4.1",
"karma-mocha": "^0.2.2", "karma-mocha": "^0.2.2",
"karma-phantomjs-launcher": "^1.0.0",
"karma-webpack": "^1.7.0", "karma-webpack": "^1.7.0",
"matrix-mock-request": "^1.2.0",
"matrix-react-test-utils": "^0.2.0",
"minimist": "^1.2.0", "minimist": "^1.2.0",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"mocha": "^2.4.5", "mocha": "^2.4.5",
"parallelshell": "^1.2.0", "parallelshell": "^1.2.0",
"phantomjs-prebuilt": "^2.1.7",
"postcss-extend": "^1.0.5", "postcss-extend": "^1.0.5",
"postcss-import": "^9.0.0", "postcss-import": "^9.0.0",
"postcss-loader": "^1.2.2", "postcss-loader": "^1.2.2",
@ -135,7 +135,7 @@
"postcss-simple-vars": "^3.0.0", "postcss-simple-vars": "^3.0.0",
"postcss-strip-inline-comments": "^0.1.5", "postcss-strip-inline-comments": "^0.1.5",
"react-addons-perf": "^15.4.0", "react-addons-perf": "^15.4.0",
"react-addons-test-utils": "^15.4.0", "react-addons-test-utils": "^15.6.0",
"rimraf": "^2.4.3", "rimraf": "^2.4.3",
"source-map-loader": "^0.1.5", "source-map-loader": "^0.1.5",
"webpack": "^1.12.14", "webpack": "^1.12.14",

View File

@ -63,7 +63,8 @@ class Deployer:
self.packages_path = "." self.packages_path = "."
self.bundles_path = None self.bundles_path = None
self.should_clean = False self.should_clean = False
self.config_location = None # filename -> symlink path e.g 'config.localhost.json' => '../localhost/config.json'
self.config_locations = {}
self.verify_signature = True self.verify_signature = True
def deploy(self, tarball, extract_path): def deploy(self, tarball, extract_path):
@ -95,10 +96,11 @@ class Deployer:
print ("Extracted into: %s" % extracted_dir) print ("Extracted into: %s" % extracted_dir)
if self.config_location: if self.config_locations:
for config_filename, config_loc in self.config_locations.iteritems():
create_relative_symlink( create_relative_symlink(
target=self.config_location, target=config_loc,
linkname=os.path.join(extracted_dir, 'config.json') linkname=os.path.join(extracted_dir, config_filename)
) )
if self.bundles_path: if self.bundles_path:
@ -178,6 +180,8 @@ if __name__ == "__main__":
deployer.packages_path = args.packages_dir deployer.packages_path = args.packages_dir
deployer.bundles_path = args.bundles_dir deployer.bundles_path = args.bundles_dir
deployer.should_clean = args.clean deployer.should_clean = args.clean
deployer.config_location = args.config deployer.config_locations = {
"config.json": args.config,
}
deployer.deploy(args.tarball, args.extract_path) deployer.deploy(args.tarball, args.extract_path)

View File

@ -13,6 +13,7 @@
from __future__ import print_function from __future__ import print_function
import json, requests, tarfile, argparse, os, errno import json, requests, tarfile, argparse, os, errno
import time import time
import traceback
from urlparse import urljoin from urlparse import urljoin
from flask import Flask, jsonify, request, abort from flask import Flask, jsonify, request, abort
@ -124,6 +125,7 @@ def fetch_jenkins_build(job_name, build_num):
try: try:
extracted_dir = deploy_tarball(tar_gz_url, build_dir) extracted_dir = deploy_tarball(tar_gz_url, build_dir)
except DeployException as e: except DeployException as e:
traceback.print_exc()
abort(400, e.message) abort(400, e.message)
create_symlink(source=extracted_dir, linkname=arg_symlink) create_symlink(source=extracted_dir, linkname=arg_symlink)
@ -185,10 +187,16 @@ if __name__ == "__main__":
to the /vector directory INSIDE the tarball." to the /vector directory INSIDE the tarball."
) )
) )
def _raise(ex):
raise ex
# --config config.json=../../config.json --config config.localhost.json=./localhost.json
parser.add_argument( parser.add_argument(
"--config", dest="config", help=( "--config", action="append", dest="configs",
"Write a symlink to config.json in the extracted tarball. \ type=lambda kv: kv.split("=", 1) if "=" in kv else _raise(Exception("Missing =")), help=(
To this location." "A list of configs to symlink into the extracted tarball. \
For example, --config config.json=../config.json config2.json=../test/config.json"
) )
) )
parser.add_argument( parser.add_argument(
@ -212,7 +220,8 @@ if __name__ == "__main__":
deployer = Deployer() deployer = Deployer()
deployer.bundles_path = args.bundles_dir deployer.bundles_path = args.bundles_dir
deployer.should_clean = args.clean deployer.should_clean = args.clean
deployer.config_location = args.config deployer.config_locations = dict(args.configs) if args.configs else {}
# we don't pgp-sign jenkins artifacts; instead we rely on HTTPS access to # we don't pgp-sign jenkins artifacts; instead we rely on HTTPS access to
# the jenkins server (and the jenkins server not being compromised and/or # the jenkins server (and the jenkins server not being compromised and/or
@ -225,13 +234,13 @@ if __name__ == "__main__":
deploy_tarball(args.tarball_uri, build_dir) deploy_tarball(args.tarball_uri, build_dir)
else: else:
print( print(
"Listening on port %s. Extracting to %s%s. Symlinking to %s. Jenkins URL: %s. Config location: %s" % "Listening on port %s. Extracting to %s%s. Symlinking to %s. Jenkins URL: %s. Config locations: %s" %
(args.port, (args.port,
arg_extract_path, arg_extract_path,
" (clean after)" if deployer.should_clean else "", " (clean after)" if deployer.should_clean else "",
arg_symlink, arg_symlink,
arg_jenkins_url, arg_jenkins_url,
deployer.config_location, deployer.config_locations,
) )
) )
app.run(host="0.0.0.0", port=args.port, debug=True) app.run(host="0.0.0.0", port=args.port, debug=True)

View File

@ -16,7 +16,7 @@ limitations under the License.
"use strict"; "use strict";
var q = require("q"); import Promise from 'bluebird';
var Matrix = require("matrix-js-sdk"); var Matrix = require("matrix-js-sdk");
var Room = Matrix.Room; var Room = Matrix.Room;
var CallHandler = require('matrix-react-sdk/lib/CallHandler'); var CallHandler = require('matrix-react-sdk/lib/CallHandler');
@ -53,11 +53,11 @@ ConferenceCall.prototype._joinConferenceUser = function() {
// Make sure the conference user is in the group chat room // Make sure the conference user is in the group chat room
var groupRoom = this.client.getRoom(this.groupRoomId); var groupRoom = this.client.getRoom(this.groupRoomId);
if (!groupRoom) { if (!groupRoom) {
return q.reject("Bad group room ID"); return Promise.reject("Bad group room ID");
} }
var member = groupRoom.getMember(this.confUserId); var member = groupRoom.getMember(this.confUserId);
if (member && member.membership === "join") { if (member && member.membership === "join") {
return q(); return Promise.resolve();
} }
return this.client.invite(this.groupRoomId, this.confUserId); return this.client.invite(this.groupRoomId, this.confUserId);
}; };
@ -75,7 +75,7 @@ ConferenceCall.prototype._getConferenceUserRoom = function() {
} }
} }
if (confRoom) { if (confRoom) {
return q(confRoom); return Promise.resolve(confRoom);
} }
return this.client.createRoom({ return this.client.createRoom({
preset: "private_chat", preset: "private_chat",

View File

@ -52,6 +52,8 @@ module.exports = React.createClass({
}, },
componentWillMount: function() { componentWillMount: function() {
this._unmounted = false;
if (this.props.teamToken && this.props.teamServerUrl) { if (this.props.teamToken && this.props.teamServerUrl) {
this.setState({ this.setState({
iframeSrc: `${this.props.teamServerUrl}/static/${this.props.teamToken}/home.html` iframeSrc: `${this.props.teamServerUrl}/static/${this.props.teamToken}/home.html`
@ -67,9 +69,14 @@ module.exports = React.createClass({
request( request(
{ method: "GET", url: src }, { method: "GET", url: src },
(err, response, body) => { (err, response, body) => {
if (this._unmounted) {
return;
}
if (err || response.status < 200 || response.status >= 300) { if (err || response.status < 200 || response.status >= 300) {
console.log(err); console.warn(`Error loading home page: ${err}`);
this.setState({ page: "Couldn't load home page" }); this.setState({ page: _t("Couldn't load home page") });
return;
} }
body = body.replace(/_t\(['"]([\s\S]*?)['"]\)/mg, (match, g1)=>this.translate(g1)); body = body.replace(/_t\(['"]([\s\S]*?)['"]\)/mg, (match, g1)=>this.translate(g1));
@ -79,6 +86,10 @@ module.exports = React.createClass({
} }
}, },
componentWillUnmount: function() {
this._unmounted = true;
},
render: function() { render: function() {
if (this.state.iframeSrc) { if (this.state.iframeSrc) {
return ( return (

View File

@ -28,7 +28,7 @@ var linkify = require('linkifyjs');
var linkifyString = require('linkifyjs/string'); var linkifyString = require('linkifyjs/string');
var linkifyMatrix = require('matrix-react-sdk/lib/linkify-matrix'); var linkifyMatrix = require('matrix-react-sdk/lib/linkify-matrix');
var sanitizeHtml = require('sanitize-html'); var sanitizeHtml = require('sanitize-html');
var q = require('q'); import Promise from 'bluebird';
import { _t } from 'matrix-react-sdk/lib/languageHandler'; import { _t } from 'matrix-react-sdk/lib/languageHandler';
@ -73,6 +73,7 @@ module.exports = React.createClass({
this.protocols = response; this.protocols = response;
this.setState({protocolsLoading: false}); this.setState({protocolsLoading: false});
}, (err) => { }, (err) => {
console.warn(`error loading thirdparty protocols: ${err}`);
this.setState({protocolsLoading: false}); this.setState({protocolsLoading: false});
if (MatrixClientPeg.get().isGuest()) { if (MatrixClientPeg.get().isGuest()) {
// Guests currently aren't allowed to use this API, so // Guests currently aren't allowed to use this API, so
@ -116,7 +117,7 @@ module.exports = React.createClass({
}, },
getMoreRooms: function() { getMoreRooms: function() {
if (!MatrixClientPeg.get()) return q(); if (!MatrixClientPeg.get()) return Promise.resolve();
const my_filter_string = this.state.filterString; const my_filter_string = this.state.filterString;
const my_server = this.state.roomServer; const my_server = this.state.roomServer;
@ -265,7 +266,7 @@ module.exports = React.createClass({
}, },
onFillRequest: function(backwards) { onFillRequest: function(backwards) {
if (backwards || !this.nextBatch) return q(false); if (backwards || !this.nextBatch) return Promise.resolve(false);
return this.getMoreRooms(); return this.getMoreRooms();
}, },

View File

@ -30,6 +30,7 @@ var RoomNotifs = require('matrix-react-sdk/lib/RoomNotifs');
var FormattingUtils = require('matrix-react-sdk/lib/utils/FormattingUtils'); var FormattingUtils = require('matrix-react-sdk/lib/utils/FormattingUtils');
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton'); var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
import Modal from 'matrix-react-sdk/lib/Modal'; import Modal from 'matrix-react-sdk/lib/Modal';
import KeyCode from 'matrix-react-sdk/lib/KeyCode';
// turn this on for drop & drag console debugging galore // turn this on for drop & drag console debugging galore
var debug = false; var debug = false;
@ -151,10 +152,11 @@ var RoomSubList = React.createClass({
} }
}, },
onRoomTileClick(roomId) { onRoomTileClick(roomId, ev) {
dis.dispatch({ dis.dispatch({
action: 'view_room', action: 'view_room',
room_id: roomId, room_id: roomId,
clear_search: (ev && (ev.keyCode == KeyCode.ENTER || ev.keyCode == KeyCode.SPACE)),
}); });
}, },
@ -509,7 +511,7 @@ var RoomSubList = React.createClass({
if (list[i].tags[self.props.tagName] && list[i].tags[self.props.tagName].order === undefined) { if (list[i].tags[self.props.tagName] && list[i].tags[self.props.tagName].order === undefined) {
MatrixClientPeg.get().setRoomTag(list[i].roomId, self.props.tagName, {order: (order + 1.0) / 2.0}).finally(function() { MatrixClientPeg.get().setRoomTag(list[i].roomId, self.props.tagName, {order: (order + 1.0) / 2.0}).finally(function() {
// Do any final stuff here // Do any final stuff here
}).fail(function(err) { }).catch(function(err) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to add tag " + self.props.tagName + " to room" + err); console.error("Failed to add tag " + self.props.tagName + " to room" + err);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {

View File

@ -16,12 +16,13 @@ limitations under the License.
'use strict'; 'use strict';
var React = require('react'); import React from 'react';
import { _t } from 'matrix-react-sdk/lib/languageHandler'; import { _t } from 'matrix-react-sdk/lib/languageHandler';
var sdk = require('matrix-react-sdk') import KeyCode from 'matrix-react-sdk/lib/KeyCode';
var dis = require('matrix-react-sdk/lib/dispatcher'); import sdk from 'matrix-react-sdk';
var rate_limited_func = require('matrix-react-sdk/lib/ratelimitedfunc'); import dis from 'matrix-react-sdk/lib/dispatcher';
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton'); import rate_limited_func from 'matrix-react-sdk/lib/ratelimitedfunc';
import AccessibleButton from 'matrix-react-sdk/lib/components/views/elements/AccessibleButton';
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'SearchBox', displayName: 'SearchBox',
@ -46,18 +47,19 @@ module.exports = React.createClass({
}, },
onAction: function(payload) { onAction: function(payload) {
// Disabling this as I find it really really annoying, and was used to the
// previous behaviour - see https://github.com/vector-im/riot-web/issues/3348
/*
switch (payload.action) { switch (payload.action) {
// Clear up the text field when a room is selected.
case 'view_room': case 'view_room':
if (this.refs.search) { if (this.refs.search && payload.clear_search) {
this._clearSearch(); this._clearSearch();
} }
break; break;
case 'focus_room_filter':
if (this.refs.search) {
this.refs.search.focus();
this.refs.search.select();
}
break;
} }
*/
}, },
onChange: function() { onChange: function() {
@ -86,6 +88,15 @@ module.exports = React.createClass({
} }
}, },
_onKeyDown: function(ev) {
switch (ev.keyCode) {
case KeyCode.ESCAPE:
this._clearSearch();
dis.dispatch({action: 'focus_composer'});
break;
}
},
_clearSearch: function() { _clearSearch: function() {
this.refs.search.value = ""; this.refs.search.value = "";
this.onChange(); this.onChange();
@ -135,6 +146,7 @@ module.exports = React.createClass({
className="mx_SearchBox_search" className="mx_SearchBox_search"
value={ this.state.searchTerm } value={ this.state.searchTerm }
onChange={ this.onChange } onChange={ this.onChange }
onKeyDown={ this._onKeyDown }
placeholder={ _t('Filter room names') } placeholder={ _t('Filter room names') }
/> />
]; ];

View File

@ -67,7 +67,7 @@ module.exports = React.createClass({
onResendClick: function() { onResendClick: function() {
Resend.resend(this.props.mxEvent); Resend.resend(this.props.mxEvent);
if (this.props.onFinished) this.props.onFinished(); this.closeMenu();
}, },
onViewSourceClick: function() { onViewSourceClick: function() {
@ -75,7 +75,7 @@ module.exports = React.createClass({
Modal.createDialog(ViewSource, { Modal.createDialog(ViewSource, {
content: this.props.mxEvent.event, content: this.props.mxEvent.event,
}, 'mx_Dialog_viewsource'); }, 'mx_Dialog_viewsource');
if (this.props.onFinished) this.props.onFinished(); this.closeMenu();
}, },
onViewClearSourceClick: function() { onViewClearSourceClick: function() {
@ -84,7 +84,7 @@ module.exports = React.createClass({
// FIXME: _clearEvent is private // FIXME: _clearEvent is private
content: this.props.mxEvent._clearEvent, content: this.props.mxEvent._clearEvent,
}, 'mx_Dialog_viewsource'); }, 'mx_Dialog_viewsource');
if (this.props.onFinished) this.props.onFinished(); this.closeMenu();
}, },
onRedactClick: function() { onRedactClick: function() {
@ -106,12 +106,12 @@ module.exports = React.createClass({
}).done(); }).done();
}, },
}, 'mx_Dialog_confirmredact'); }, 'mx_Dialog_confirmredact');
if (this.props.onFinished) this.props.onFinished(); this.closeMenu();
}, },
onCancelSendClick: function() { onCancelSendClick: function() {
Resend.removeFromQueue(this.props.mxEvent); Resend.removeFromQueue(this.props.mxEvent);
if (this.props.onFinished) this.props.onFinished(); this.closeMenu();
}, },
onForwardClick: function() { onForwardClick: function() {
@ -130,7 +130,7 @@ module.exports = React.createClass({
if (this.props.eventTileOps) { if (this.props.eventTileOps) {
this.props.eventTileOps.unhideWidget(); this.props.eventTileOps.unhideWidget();
} }
if (this.props.onFinished) this.props.onFinished(); this.closeMenu();
}, },
onQuoteClick: function() { onQuoteClick: function() {
@ -139,6 +139,7 @@ module.exports = React.createClass({
action: 'quote', action: 'quote',
event: this.props.mxEvent, event: this.props.mxEvent,
}); });
this.closeMenu();
}, },
render: function() { render: function() {
@ -247,7 +248,7 @@ module.exports = React.createClass({
{viewClearSourceButton} {viewClearSourceButton}
{unhidePreviewButton} {unhidePreviewButton}
{permalinkButton} {permalinkButton}
{UserSettingsStore.isFeatureEnabled('rich_text_editor') ? quoteButton : null} {quoteButton}
{externalURLButton} {externalURLButton}
</div> </div>
); );

View File

@ -17,7 +17,7 @@ limitations under the License.
'use strict'; 'use strict';
import q from 'q'; import Promise from 'bluebird';
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import sdk from 'matrix-react-sdk'; import sdk from 'matrix-react-sdk';
@ -61,14 +61,14 @@ module.exports = React.createClass({
const roomId = this.props.room.roomId; const roomId = this.props.room.roomId;
var cli = MatrixClientPeg.get(); var cli = MatrixClientPeg.get();
if (!cli.isGuest()) { if (!cli.isGuest()) {
q.delay(500).then(function() { Promise.delay(500).then(function() {
if (tagNameOff !== null && tagNameOff !== undefined) { if (tagNameOff !== null && tagNameOff !== undefined) {
cli.deleteRoomTag(roomId, tagNameOff).finally(function() { cli.deleteRoomTag(roomId, tagNameOff).finally(function() {
// Close the context menu // Close the context menu
if (self.props.onFinished) { if (self.props.onFinished) {
self.props.onFinished(); self.props.onFinished();
}; };
}).fail(function(err) { }).catch(function(err) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Failed to remove tag %(tagName)s from room', {tagName: tagNameOff}), title: _t('Failed to remove tag %(tagName)s from room', {tagName: tagNameOff}),
@ -85,7 +85,7 @@ module.exports = React.createClass({
if (self.props.onFinished) { if (self.props.onFinished) {
self.props.onFinished(); self.props.onFinished();
}; };
}).fail(function(err) { }).catch(function(err) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: _t('Failed to remove tag %(tagName)s from room', {tagName: tagNameOn}), title: _t('Failed to remove tag %(tagName)s from room', {tagName: tagNameOn}),
@ -212,7 +212,7 @@ module.exports = React.createClass({
RoomNotifs.setRoomNotifsState(this.props.room.roomId, newState).done(() => { RoomNotifs.setRoomNotifsState(this.props.room.roomId, newState).done(() => {
// delay slightly so that the user can see their state change // delay slightly so that the user can see their state change
// before closing the menu // before closing the menu
return q.delay(500).then(() => { return Promise.delay(500).then(() => {
if (this._unmounted) return; if (this._unmounted) return;
// Close the context menu // Close the context menu
if (this.props.onFinished) { if (this.props.onFinished) {

View File

@ -158,7 +158,7 @@ module.exports = React.createClass({
var eventRedact; var eventRedact;
if(showEventMeta) { if(showEventMeta) {
eventRedact = (<div className="mx_ImageView_button" onClick={this.onRedactClick}> eventRedact = (<div className="mx_ImageView_button" onClick={this.onRedactClick}>
{ _t('Redact') } { _t('Remove') }
</div>); </div>);
} }

View File

@ -35,7 +35,7 @@ module.exports = React.createClass({
render: function() { render: function() {
return ( return (
<div className="mx_MatrixToolbar"> <div className="mx_MatrixToolbar">
<img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt="/!\"/> <img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt="Warning"/>
<div className="mx_MatrixToolbar_content"> <div className="mx_MatrixToolbar_content">
{ _t('You are not receiving desktop notifications') } <a className="mx_MatrixToolbar_link" onClick={ this.onClick }> { _t('Enable them now') }</a> { _t('You are not receiving desktop notifications') } <a className="mx_MatrixToolbar_link" onClick={ this.onClick }> { _t('Enable them now') }</a>
</div> </div>

View File

@ -96,7 +96,7 @@ export default React.createClass({
} }
return ( return (
<div className="mx_MatrixToolbar"> <div className="mx_MatrixToolbar">
<img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt="/!\"/> <img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt="Warning"/>
<div className="mx_MatrixToolbar_content"> <div className="mx_MatrixToolbar_content">
{_t("A new version of Riot is available.")} {_t("A new version of Riot is available.")}
</div> </div>

View File

@ -20,6 +20,7 @@ import React from 'react';
import sdk from 'matrix-react-sdk'; import sdk from 'matrix-react-sdk';
import Modal from 'matrix-react-sdk/lib/Modal'; import Modal from 'matrix-react-sdk/lib/Modal';
import dis from 'matrix-react-sdk/lib/dispatcher'; import dis from 'matrix-react-sdk/lib/dispatcher';
import { _t, _tJsx } from 'matrix-react-sdk/lib/languageHandler';
export default React.createClass({ export default React.createClass({
onUpdateClicked: function() { onUpdateClicked: function() {
@ -33,12 +34,11 @@ export default React.createClass({
dis.dispatch({ dis.dispatch({
action: 'password_changed', action: 'password_changed',
}); });
} },
}); });
}, },
render: function() { render: function() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const toolbarClasses = "mx_MatrixToolbar mx_MatrixToolbar_clickable"; const toolbarClasses = "mx_MatrixToolbar mx_MatrixToolbar_clickable";
return ( return (
<div className={toolbarClasses} onClick={this.onUpdateClicked}> <div className={toolbarClasses} onClick={this.onUpdateClicked}>
@ -49,12 +49,16 @@ export default React.createClass({
alt="Warning" alt="Warning"
/> />
<div className="mx_MatrixToolbar_content"> <div className="mx_MatrixToolbar_content">
To return to your account in future you need to <u>set a password</u> { _tJsx(
"To return to your account in future you need to <u>set a password</u>",
/<u>(.*?)<\/u>/,
(sub) => { return <u>{ sub }</u>; },
) }
</div> </div>
<button className="mx_MatrixToolbar_action"> <button className="mx_MatrixToolbar_action">
Set Password { _t("Set Password") }
</button> </button>
</div> </div>
); );
} },
}); });

View File

@ -0,0 +1,85 @@
/*
Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
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';
import React from 'react';
import { _t } from 'matrix-react-sdk/lib/languageHandler';
import PlatformPeg from 'matrix-react-sdk/lib/PlatformPeg';
import {updateCheckStatusEnum} from '../../../vector/platform/VectorBasePlatform';
import AccessibleButton from 'matrix-react-sdk/lib/components/views/elements/AccessibleButton';
const doneStatuses = [
updateCheckStatusEnum.ERROR,
updateCheckStatusEnum.NOTAVAILABLE,
];
export default React.createClass({
propTypes: {
status: React.PropTypes.oneOf(Object.values(updateCheckStatusEnum)).isRequired,
// Currently for error detail but will be usable for download progress
// once that is a thing that squirrel passes through electron.
detail: React.PropTypes.string,
},
getDefaultProps: function() {
return {
detail: '',
}
},
getStatusText: function() {
switch(this.props.status) {
case updateCheckStatusEnum.ERROR:
return _t('Error encountered (%(errorDetail)s).', { errorDetail: this.props.detail });
case updateCheckStatusEnum.CHECKING:
return _t('Checking for an update...');
case updateCheckStatusEnum.NOTAVAILABLE:
return _t('No update available.');
case updateCheckStatusEnum.DOWNLOADING:
return _t('Downloading update...');
}
}
,
hideToolbar: function() {
PlatformPeg.get().stopUpdateCheck();
},
render: function() {
const message = this.getStatusText();
const warning = _t('Warning');
let image;
if (doneStatuses.includes(this.props.status)) {
image = <img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt={warning}/>;
} else {
image = <img className="mx_MatrixToolbar_warning" src="img/spinner.gif" width="24" height="23" alt={message}/>;
}
return (
<div className="mx_MatrixToolbar">
{image}
<div className="mx_MatrixToolbar_content">
{message}
</div>
<AccessibleButton className="mx_MatrixToolbar_close" onClick={this.hideToolbar}>
<img src="img/cancel.svg" width="18" height="18" />
</AccessibleButton>
</div>
);
}
});

View File

@ -111,7 +111,7 @@ var roomTileSource = {
//component.state.set({ spinner: component.state.spinner ? component.state.spinner++ : 1 }); //component.state.set({ spinner: component.state.spinner ? component.state.spinner++ : 1 });
MatrixClientPeg.get().deleteRoomTag(item.room.roomId, prevTag).finally(function() { MatrixClientPeg.get().deleteRoomTag(item.room.roomId, prevTag).finally(function() {
//component.state.set({ spinner: component.state.spinner-- }); //component.state.set({ spinner: component.state.spinner-- });
}).fail(function(err) { }).catch(function(err) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to remove tag " + prevTag + " from room: " + err); console.error("Failed to remove tag " + prevTag + " from room: " + err);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
@ -130,10 +130,7 @@ var roomTileSource = {
if (newTag && newTag !== 'im.vector.fake.direct' && if (newTag && newTag !== 'im.vector.fake.direct' &&
(item.targetList !== item.originalList || newOrder) (item.targetList !== item.originalList || newOrder)
) { ) {
//component.state.set({ spinner: component.state.spinner ? component.state.spinner++ : 1 }); MatrixClientPeg.get().setRoomTag(item.room.roomId, newTag, newOrder).catch(function(err) {
MatrixClientPeg.get().setRoomTag(item.room.roomId, newTag, newOrder).finally(function() {
//component.state.set({ spinner: component.state.spinner-- });
}).fail(function(err) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to add tag " + newTag + " to room: " + err); console.error("Failed to add tag " + newTag + " to room: " + err);
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {

View File

@ -17,7 +17,7 @@ limitations under the License.
'use strict'; 'use strict';
var React = require('react'); var React = require('react');
import { _t, _tJsx } from 'matrix-react-sdk/lib/languageHandler'; import { _t, _tJsx } from 'matrix-react-sdk/lib/languageHandler';
var q = require("q"); import Promise from 'bluebird';
var sdk = require('matrix-react-sdk'); var sdk = require('matrix-react-sdk');
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var UserSettingsStore = require('matrix-react-sdk/lib/UserSettingsStore'); var UserSettingsStore = require('matrix-react-sdk/lib/UserSettingsStore');
@ -236,7 +236,7 @@ module.exports = React.createClass({
} }
} }
q.all(deferreds).done(function() { Promise.all(deferreds).done(function() {
self._refreshFromServer(); self._refreshFromServer();
}, function(error) { }, function(error) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
@ -306,7 +306,7 @@ module.exports = React.createClass({
} }
} }
q.all(deferreds).done(function(resps) { Promise.all(deferreds).done(function(resps) {
self._refreshFromServer(); self._refreshFromServer();
}, function(error) { }, function(error) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
@ -361,7 +361,7 @@ module.exports = React.createClass({
} }
// Then, add the new ones // Then, add the new ones
q.all(removeDeferreds).done(function(resps) { Promise.all(removeDeferreds).done(function(resps) {
var deferreds = []; var deferreds = [];
var pushRuleVectorStateKind = self.state.vectorContentRules.vectorState; var pushRuleVectorStateKind = self.state.vectorContentRules.vectorState;
@ -399,7 +399,7 @@ module.exports = React.createClass({
} }
} }
q.all(deferreds).done(function(resps) { Promise.all(deferreds).done(function(resps) {
self._refreshFromServer(); self._refreshFromServer();
}, onError); }, onError);
}, onError); }, onError);
@ -431,7 +431,9 @@ module.exports = React.createClass({
'global', kind, LEGACY_RULES[rule.rule_id], portLegacyActions(rule.actions) 'global', kind, LEGACY_RULES[rule.rule_id], portLegacyActions(rule.actions)
).then( function() { ).then( function() {
return cli.deletePushRule('global', kind, rule.rule_id); return cli.deletePushRule('global', kind, rule.rule_id);
}) }).catch( (e) => {
console.warn(`Error when porting legacy rule: ${e}`);
});
}(kind, rule)); }(kind, rule));
} }
} }
@ -440,7 +442,7 @@ module.exports = React.createClass({
if (needsUpdate.length > 0) { if (needsUpdate.length > 0) {
// If some of the rules need to be ported then wait for the porting // If some of the rules need to be ported then wait for the porting
// to happen and then fetch the rules again. // to happen and then fetch the rules again.
return q.allSettled(needsUpdate).then( function() { return Promise.all(needsUpdate).then( function() {
return cli.getPushRules(); return cli.getPushRules();
}); });
} else { } else {
@ -594,7 +596,7 @@ module.exports = React.createClass({
self.setState({pushers: resp.pushers}); self.setState({pushers: resp.pushers});
}); });
q.all([pushRulesPromise, pushersPromise]).then(function() { Promise.all([pushRulesPromise, pushersPromise]).then(function() {
self.setState({ self.setState({
phase: self.phases.DISPLAY phase: self.phases.DISPLAY
}); });

View File

@ -207,5 +207,7 @@
"General discussion about Matrix and Riot": "Allgemeine Diskussion über Matrix und Riot", "General discussion about Matrix and Riot": "Allgemeine Diskussion über Matrix und Riot",
"(HTTP status %(httpStatus)s)": "(HTTP-Status %(httpStatus)s)", "(HTTP status %(httpStatus)s)": "(HTTP-Status %(httpStatus)s)",
"You have successfully set a password and an email address!": "Du hast erfolgreich ein Passwort und eine E-Mail-Adresse gesetzt!", "You have successfully set a password and an email address!": "Du hast erfolgreich ein Passwort und eine E-Mail-Adresse gesetzt!",
"Remember, you can always set an email address in user settings if you change your mind.": "Denk daran, dass du in den Benutzereinstellungen jederzeit eine E-Mail-Adresse setzen kannst." "Remember, you can always set an email address in user settings if you change your mind.": "Denk daran, dass du in den Benutzereinstellungen jederzeit eine E-Mail-Adresse setzen kannst.",
"To return to your account in future you need to <u>set a password</u>": "Um in Zukunft zu deinem Konto zurückkehren zu können, musst du <u>ein Passwort setzen</u>",
"Set Password": "Setze ein Passwort"
} }

View File

@ -198,5 +198,7 @@
"Failed to set Direct Message status of room": "Δεν ήταν δυνατός ο ορισμός της κατάστασης Direct Message του δωματίου", "Failed to set Direct Message status of room": "Δεν ήταν δυνατός ο ορισμός της κατάστασης Direct Message του δωματίου",
"Support for those using, running and writing other bridges": "Υποστήριξη για τους χρήστες που χρησιμοποιούν ή αναπτύσσουν εφαρμογές ενσωμάτωσης για το Matrix", "Support for those using, running and writing other bridges": "Υποστήριξη για τους χρήστες που χρησιμοποιούν ή αναπτύσσουν εφαρμογές ενσωμάτωσης για το Matrix",
"You have successfully set a password and an email address!": "Ο κωδικός πρόσβασης και η διεύθυνση ηλεκτρονικής αλληλογραφίας ορίστηκαν επιτυχώς!", "You have successfully set a password and an email address!": "Ο κωδικός πρόσβασης και η διεύθυνση ηλεκτρονικής αλληλογραφίας ορίστηκαν επιτυχώς!",
"Remember, you can always set an email address in user settings if you change your mind.": "Να θυμάστε ότι μπορείτε πάντα να ορίσετε μια διεύθυνση ηλεκτρονικής αλληλογραφίας στις ρυθμίσεις χρήστη αν αλλάξετε γνώμη." "Remember, you can always set an email address in user settings if you change your mind.": "Να θυμάστε ότι μπορείτε πάντα να ορίσετε μια διεύθυνση ηλεκτρονικής αλληλογραφίας στις ρυθμίσεις χρήστη αν αλλάξετε γνώμη.",
"To return to your account in future you need to <u>set a password</u>": "Για να επιστρέψετε στον λογαριασμό σας μελλοντικα πρέπει να ορίσετε έναν <u>κωδικό πρόσβασης</u>",
"Set Password": "Ορισμός κωδικού πρόσβασης"
} }

View File

@ -98,7 +98,6 @@
"Please Register": "Please Register", "Please Register": "Please Register",
"powered by Matrix": "powered by Matrix", "powered by Matrix": "powered by Matrix",
"Quote": "Quote", "Quote": "Quote",
"Redact": "Redact",
"Reject": "Reject", "Reject": "Reject",
"Remove %(name)s from the directory?": "Remove %(name)s from the directory?", "Remove %(name)s from the directory?": "Remove %(name)s from the directory?",
"Remove": "Remove", "Remove": "Remove",
@ -160,6 +159,11 @@
"Today": "Today", "Today": "Today",
"Yesterday": "Yesterday", "Yesterday": "Yesterday",
"OK": "OK", "OK": "OK",
"Warning": "Warning",
"Checking for an update...": "Checking for an update...",
"Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).",
"No update available.": "No update available.",
"Downloading update...": "Downloading update...",
"You need to be using HTTPS to place a screen-sharing call.": "You need to be using HTTPS to place a screen-sharing call.", "You need to be using HTTPS to place a screen-sharing call.": "You need to be using HTTPS to place a screen-sharing call.",
"Welcome page": "Welcome page", "Welcome page": "Welcome page",
"With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!": "With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!", "With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!": "With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!",
@ -198,5 +202,8 @@
"Please set a password!": "Please set a password!", "Please set a password!": "Please set a password!",
"This will allow you to return to your account after signing out, and sign in on other devices.": "This will allow you to return to your account after signing out, and sign in on other devices.", "This will allow you to return to your account after signing out, and sign in on other devices.": "This will allow you to return to your account after signing out, and sign in on other devices.",
"You have successfully set a password and an email address!": "You have successfully set a password and an email address!", "You have successfully set a password and an email address!": "You have successfully set a password and an email address!",
"Remember, you can always set an email address in user settings if you change your mind.": "Remember, you can always set an email address in user settings if you change your mind." "Remember, you can always set an email address in user settings if you change your mind.": "Remember, you can always set an email address in user settings if you change your mind.",
"To return to your account in future you need to <u>set a password</u>": "To return to your account in future you need to <u>set a password</u>",
"Set Password": "Set Password",
"Couldn't load home page": "Couldn't load home page"
} }

View File

@ -96,7 +96,6 @@
"Please Register": "Please Register", "Please Register": "Please Register",
"powered by Matrix": "powered by Matrix", "powered by Matrix": "powered by Matrix",
"Quote": "Quote", "Quote": "Quote",
"Redact": "Redact",
"Reject": "Reject", "Reject": "Reject",
"Remove %(name)s from the directory?": "Remove %(name)s from the directory?", "Remove %(name)s from the directory?": "Remove %(name)s from the directory?",
"Remove": "Remove", "Remove": "Remove",

View File

@ -198,5 +198,11 @@
"Lots of rooms already exist in Matrix, linked to existing networks (Slack, IRC, Gitter etc) or independent. Check out the directory!": "Muchas salas ya están disponibles en Matrix, enlazadas a redes existentes (Slack, IRC, Gitter, etc) o independientes. ¡Revisa el directorio!", "Lots of rooms already exist in Matrix, linked to existing networks (Slack, IRC, Gitter etc) or independent. Check out the directory!": "Muchas salas ya están disponibles en Matrix, enlazadas a redes existentes (Slack, IRC, Gitter, etc) o independientes. ¡Revisa el directorio!",
"You can now return to your account after signing out, and sign in on other devices.": "Ahora puedes regresar a tu cuenta después de cerrar tu sesión, e iniciar sesión en otros dispositivos.", "You can now return to your account after signing out, and sign in on other devices.": "Ahora puedes regresar a tu cuenta después de cerrar tu sesión, e iniciar sesión en otros dispositivos.",
"Please set a password!": "¡Por favor establece una contraseña!", "Please set a password!": "¡Por favor establece una contraseña!",
"This will allow you to return to your account after signing out, and sign in on other devices.": "Esto le permitirá regresar a su cuenta después de cerrar sesión, así como iniciar sesión en otros dispositivos." "This will allow you to return to your account after signing out, and sign in on other devices.": "Esto le permitirá regresar a su cuenta después de cerrar sesión, así como iniciar sesión en otros dispositivos.",
"Warning": "Advertencia",
"Checking for an update...": "Comprobando actualizaciones...",
"No update available.": "No hay actualizaciones disponibles.",
"Downloading update...": "Descargando actualizaciones...",
"To return to your account in future you need to <u>set a password</u>": "Para regresar a su cuenta en el futuro Ud. debe <u>establecer una contraseña</u>",
"Set Password": "Establezca la contraseña"
} }

View File

@ -130,7 +130,7 @@
"Messages containing <span>keywords</span>": "Az üzenet <span>kulcsszavakat</span> tartalmaz", "Messages containing <span>keywords</span>": "Az üzenet <span>kulcsszavakat</span> tartalmaz",
"%(appName)s via %(browserName)s on %(osName)s": "%(appName)s alkalmazás %(browserName)s böngészőn %(osName)s rendszeren", "%(appName)s via %(browserName)s on %(osName)s": "%(appName)s alkalmazás %(browserName)s böngészőn %(osName)s rendszeren",
"A new version of Riot is available.": "Új verzió érhető el a Riot-ból.", "A new version of Riot is available.": "Új verzió érhető el a Riot-ból.",
"All Rooms": "Minden szoba", "All Rooms": "Minden szobában",
"Cancel": "Mégse", "Cancel": "Mégse",
"Changelog": "Változások", "Changelog": "Változások",
"Collecting app version information": "Alkalmazás verzió információk összegyűjtése", "Collecting app version information": "Alkalmazás verzió információk összegyűjtése",
@ -148,7 +148,7 @@
"Search…": "Keresés…", "Search…": "Keresés…",
"Send": "Küld", "Send": "Küld",
"Send logs": "Naplók elküldése", "Send logs": "Naplók elküldése",
"This Room": "Ez a szoba", "This Room": "Ebben a szobában",
"Unavailable": "Elérhetetlen", "Unavailable": "Elérhetetlen",
"Unknown device": "Ismeretlen eszköz", "Unknown device": "Ismeretlen eszköz",
"Update": "Frissítés", "Update": "Frissítés",
@ -198,5 +198,7 @@
"This will allow you to return to your account after signing out, and sign in on other devices.": "Ezzel visszatérhetsz kijelentkezés után a fiókodhoz és más eszközökkel is be tudsz jelentkezni.", "This will allow you to return to your account after signing out, and sign in on other devices.": "Ezzel visszatérhetsz kijelentkezés után a fiókodhoz és más eszközökkel is be tudsz jelentkezni.",
"(HTTP status %(httpStatus)s)": "(HTTP állapot %(httpStatus)s)", "(HTTP status %(httpStatus)s)": "(HTTP állapot %(httpStatus)s)",
"You have successfully set a password and an email address!": "Sikeresen beállítottad a jelszavad és e-mail címed!", "You have successfully set a password and an email address!": "Sikeresen beállítottad a jelszavad és e-mail címed!",
"Remember, you can always set an email address in user settings if you change your mind.": "Ha meggondolod magad, bármikor beállíthatod az e-mail címed a felhasználói beállításoknál." "Remember, you can always set an email address in user settings if you change your mind.": "Ha meggondolod magad, bármikor beállíthatod az e-mail címed a felhasználói beállításoknál.",
"To return to your account in future you need to <u>set a password</u>": "A fiókba való visszalépéshez <u>jelszót</u> kell beállítanod",
"Set Password": "Jelszó beállítása"
} }

View File

@ -25,7 +25,7 @@
"Directory": "목록", "Directory": "목록",
"Dismiss": "없애기", "Dismiss": "없애기",
"Download this file": "이 파일 받기", "Download this file": "이 파일 받기",
"Enable desktop notifications": "데스크탑에서 알림 받기", "Enable desktop notifications": "컴퓨터에서 알림 받기",
"Enable email notifications": "이메일로 알림 받기", "Enable email notifications": "이메일로 알림 받기",
"Enable notifications for this account": "이 계정의 알림 받기", "Enable notifications for this account": "이 계정의 알림 받기",
"Error": "오류", "Error": "오류",
@ -62,7 +62,7 @@
"Remove from Directory": "목록에서 지우기", "Remove from Directory": "목록에서 지우기",
"Report a bug": "오류 보고하기", "Report a bug": "오류 보고하기",
"Resend": "다시 보내기", "Resend": "다시 보내기",
"Riot Desktop on %(platformName)s": "%(platformName)s에서 라이엇 데스크탑", "Riot Desktop on %(platformName)s": "%(platformName)s에서 라이엇 컴퓨터판",
"Riot is not supported on mobile web. Install the app?": "라이엇은 모바일 사이트를 지원하지 않아요. 앱을 설치하시겠어요?", "Riot is not supported on mobile web. Install the app?": "라이엇은 모바일 사이트를 지원하지 않아요. 앱을 설치하시겠어요?",
"Room directory": "방 목록", "Room directory": "방 목록",
"Room not found": "방을 찾지 못했어요", "Room not found": "방을 찾지 못했어요",
@ -87,7 +87,7 @@
"Waiting for response from server": "서버에서 응답을 기다리는 중", "Waiting for response from server": "서버에서 응답을 기다리는 중",
"You cannot delete this image. (%(code)s)": "이 사진을 지우실 수 없어요. (%(code)s)", "You cannot delete this image. (%(code)s)": "이 사진을 지우실 수 없어요. (%(code)s)",
"You cannot delete this message. (%(code)s)": "이 메시지를 지우실 수 없어요. (%(code)s)", "You cannot delete this message. (%(code)s)": "이 메시지를 지우실 수 없어요. (%(code)s)",
"You are not receiving desktop notifications": "데스크탑 알림을 받지 않고 있어요", "You are not receiving desktop notifications": "컴퓨터 알림을 받지 않고 있어요",
"Sunday": "일요일", "Sunday": "일요일",
"Monday": "월요일", "Monday": "월요일",
"Tuesday": "화요일", "Tuesday": "화요일",
@ -171,7 +171,7 @@
"Get started with some tips from Riot Bot!": "라이엇 봇에게 조언을 받고 시작하세요!", "Get started with some tips from Riot Bot!": "라이엇 봇에게 조언을 받고 시작하세요!",
"General discussion about Matrix and Riot": "매트릭스와 라이엇에 대한 일반 논의", "General discussion about Matrix and Riot": "매트릭스와 라이엇에 대한 일반 논의",
"Discussion of all things Matrix!": "매트릭스의 모든 것에 대한 토론!", "Discussion of all things Matrix!": "매트릭스의 모든 것에 대한 토론!",
"Riot/Web &amp; Desktop chat": "라이엇/웹 &amp; 데스크탑 대화", "Riot/Web &amp; Desktop chat": "라이엇/웹 &amp; 컴퓨터 이야기",
"Riot/iOS &amp; matrix-ios-sdk chat": "라이엇/IOS &amp; matrix-ios-sdk 대화", "Riot/iOS &amp; matrix-ios-sdk chat": "라이엇/IOS &amp; matrix-ios-sdk 대화",
"Riot/Android &amp; matrix-android-sdk chat": "매트릭스/안드로이드 &amp; matrix-ios-sdk 대화", "Riot/Android &amp; matrix-android-sdk chat": "매트릭스/안드로이드 &amp; matrix-ios-sdk 대화",
"Matrix technical discussions": "매트릭스 기술 논의", "Matrix technical discussions": "매트릭스 기술 논의",
@ -198,5 +198,7 @@
"You have successfully set a password and an email address!": "비밀번호와 이메일 주소를 설정했어요!", "You have successfully set a password and an email address!": "비밀번호와 이메일 주소를 설정했어요!",
"Remember, you can always set an email address in user settings if you change your mind.": "잊지마세요, 마음이 바뀌면 언제라도 사용자 설정에서 이메일 주소를 바꾸실 수 있다는 걸요.", "Remember, you can always set an email address in user settings if you change your mind.": "잊지마세요, 마음이 바뀌면 언제라도 사용자 설정에서 이메일 주소를 바꾸실 수 있다는 걸요.",
"You are Rioting as a guest. <a>Register</a> or <a>sign in</a> to access more rooms and features!": "손님으로 라이엇에 들어오셨네요. <a>계정을 등록하거나</a> <a>로그인하시고</a> 더 많은 방과 기능을 즐기세요!", "You are Rioting as a guest. <a>Register</a> or <a>sign in</a> to access more rooms and features!": "손님으로 라이엇에 들어오셨네요. <a>계정을 등록하거나</a> <a>로그인하시고</a> 더 많은 방과 기능을 즐기세요!",
"You might have configured them in a client other than Riot. You cannot tune them in Riot but they still apply": "라이엇이 아닌 다른 클라이언트에서 구성하셨을 수도 있어요. 라이엇에서 조정할 수는 없지만 여전히 적용되있을 거에요" "You might have configured them in a client other than Riot. You cannot tune them in Riot but they still apply": "라이엇이 아닌 다른 클라이언트에서 구성하셨을 수도 있어요. 라이엇에서 조정할 수는 없지만 여전히 적용되있을 거에요",
"To return to your account in future you need to <u>set a password</u>": "나중에 계정으로 돌아가려면 <u>비밀번호 설정</u>을 해야만 해요",
"Set Password": "비밀번호 설정"
} }

View File

@ -198,5 +198,7 @@
"Please set a password!": "Proszę, ustaw hasło!", "Please set a password!": "Proszę, ustaw hasło!",
"This will allow you to return to your account after signing out, and sign in on other devices.": "To pozwoli Ci powrócić do Twojego konta po wylogowaniu i ponownym zalogowaniu się na innych urządzeniach.", "This will allow you to return to your account after signing out, and sign in on other devices.": "To pozwoli Ci powrócić do Twojego konta po wylogowaniu i ponownym zalogowaniu się na innych urządzeniach.",
"You have successfully set a password and an email address!": "Z powodzeniem ustawiono hasło i adres e-mail dla Twojego konta!", "You have successfully set a password and an email address!": "Z powodzeniem ustawiono hasło i adres e-mail dla Twojego konta!",
"Remember, you can always set an email address in user settings if you change your mind.": "Pamiętaj, że zawsze możesz zmienić swój e-mail lub hasło w panelu ustawień użytkownika." "Remember, you can always set an email address in user settings if you change your mind.": "Pamiętaj, że zawsze możesz zmienić swój e-mail lub hasło w panelu ustawień użytkownika.",
"To return to your account in future you need to <u>set a password</u>": "Aby wrócić do swojego konta w przyszłości musisz <u> ustawić hasło </u>",
"Set Password": "Ustaw hasło"
} }

View File

@ -199,5 +199,9 @@
"Please set a password!": "Por favor, defina uma senha!", "Please set a password!": "Por favor, defina uma senha!",
"This will allow you to return to your account after signing out, and sign in on other devices.": "Isso permitirá que você possa retornar à sua conta após fazer logout, e também fazer login em outros dispositivos.", "This will allow you to return to your account after signing out, and sign in on other devices.": "Isso permitirá que você possa retornar à sua conta após fazer logout, e também fazer login em outros dispositivos.",
"(HTTP status %(httpStatus)s)": "(Status HTTP %(httpStatus)s)", "(HTTP status %(httpStatus)s)": "(Status HTTP %(httpStatus)s)",
"Decentralised, encrypted chat &amp; collaboration powered by [matrix]": "Chat descentralizado, criptografado e colaborativo impulsionado por [matrix]" "Decentralised, encrypted chat &amp; collaboration powered by [matrix]": "Chat descentralizado, criptografado e colaborativo impulsionado por [matrix]",
"You have successfully set a password and an email address!": "Você definiu uma senha e um endereço de e-mail com sucesso!",
"Remember, you can always set an email address in user settings if you change your mind.": "Lembre-se: você pode sempre definir um endereço de e-mail nas configurações de usuário, se mudar de ideia.",
"To return to your account in future you need to <u>set a password</u>": "Para poder, futuramente, retornar à sua conta, você precisa <u>definir uma senha</u>",
"Set Password": "Definir senha"
} }

View File

@ -205,5 +205,7 @@
"Co-ordination for Riot/Web translators": "Координирование для переводчиков Riot / Web", "Co-ordination for Riot/Web translators": "Координирование для переводчиков Riot / Web",
"This will allow you to return to your account after signing out, and sign in on other devices.": "Это позволит Вам вернуться в свою учетную запись после выхода, и войти в систему на других устройствах.", "This will allow you to return to your account after signing out, and sign in on other devices.": "Это позволит Вам вернуться в свою учетную запись после выхода, и войти в систему на других устройствах.",
"You have successfully set a password and an email address!": "Пароль и адрес электронной почты успешно сохранены!", "You have successfully set a password and an email address!": "Пароль и адрес электронной почты успешно сохранены!",
"Remember, you can always set an email address in user settings if you change your mind.": "Помните, Вы всегда можете указать адрес электронной почты в пользовательских настройках, если передумаете." "Remember, you can always set an email address in user settings if you change your mind.": "Помните, Вы всегда можете указать адрес электронной почты в пользовательских настройках, если передумаете.",
"Set Password": "Задать пароль",
"To return to your account in future you need to <u>set a password</u>": "Чтобы в будущем вернутся к вашей учётной записи, вы должны <u>задать пароль</u>"
} }

View File

@ -171,7 +171,7 @@
"Riot/Web &amp; Desktop chat": "แชทเกี่ยวกับ Riot บนเว็บและเดสก์ทอป", "Riot/Web &amp; Desktop chat": "แชทเกี่ยวกับ Riot บนเว็บและเดสก์ทอป",
"Riot/iOS &amp; matrix-ios-sdk chat": "แชทเกี่ยวกับ Riot บน iOS และ matrix-ios-sdk", "Riot/iOS &amp; matrix-ios-sdk chat": "แชทเกี่ยวกับ Riot บน iOS และ matrix-ios-sdk",
"Riot/Android &amp; matrix-android-sdk chat": "แชทเกี่ยวกับ Riot บน Android และ matrix-android-sdk", "Riot/Android &amp; matrix-android-sdk chat": "แชทเกี่ยวกับ Riot บน Android และ matrix-android-sdk",
"Matrix technical discussions": "พูดคุยเรื่อง Matrix ทางเทคนิค", "Matrix technical discussions": "พูดคุยเรื่อง Matrix เชิงเทคนิค",
"Running Matrix services": "การติดตั้งบริการ Matrix", "Running Matrix services": "การติดตั้งบริการ Matrix",
"Community-run support for Synapse": "ฝ่ายสนับสนุน Synapse โดยชุมชนผู้ใช้", "Community-run support for Synapse": "ฝ่ายสนับสนุน Synapse โดยชุมชนผู้ใช้",
"Admin support for Dendrite": "ฝ่ายสนับสนุน Dendrite จากผู้ดูแล", "Admin support for Dendrite": "ฝ่ายสนับสนุน Dendrite จากผู้ดูแล",
@ -182,7 +182,7 @@
"Implementing VR services with Matrix": "การอิมพลีเมนต์บริการ VR ด้วย Matrix", "Implementing VR services with Matrix": "การอิมพลีเมนต์บริการ VR ด้วย Matrix",
"Implementing VoIP services with Matrix": "การอิมพลีเมนต์บริการ VoIP ด้วย Matrix", "Implementing VoIP services with Matrix": "การอิมพลีเมนต์บริการ VoIP ด้วย Matrix",
"Support for those using, running and writing other bridges": "ฝ่ายสนับสนุนสำหรับผู้ใช้หรือพัฒนาตัวเชื่อมอื่น ๆ", "Support for those using, running and writing other bridges": "ฝ่ายสนับสนุนสำหรับผู้ใช้หรือพัฒนาตัวเชื่อมอื่น ๆ",
"Contributing code to Matrix and Riot": "สมทบโค๊ดกับ Matrix และ Riot", "Contributing code to Matrix and Riot": "สมทบโค๊ดให้ Matrix และ Riot",
"Dev chat for the Riot/Web dev team": "แชทสำหรับทีมพัฒนา Riot บนเว็บ", "Dev chat for the Riot/Web dev team": "แชทสำหรับทีมพัฒนา Riot บนเว็บ",
"Dev chat for the Dendrite dev team": "แชทสำหรับทีมพัฒนา Dendrite", "Dev chat for the Dendrite dev team": "แชทสำหรับทีมพัฒนา Dendrite",
"Co-ordination for Riot/Web translators": "แชทสำหรับประสานงานการแปล Riot บนเว็บ", "Co-ordination for Riot/Web translators": "แชทสำหรับประสานงานการแปล Riot บนเว็บ",
@ -199,5 +199,12 @@
"General discussion about Matrix and Riot": "พูดคุยเรื่องทั่วไป ทั้ง Matrix และ Riot", "General discussion about Matrix and Riot": "พูดคุยเรื่องทั่วไป ทั้ง Matrix และ Riot",
"(HTTP status %(httpStatus)s)": "(สถานะ HTTP %(httpStatus)s)", "(HTTP status %(httpStatus)s)": "(สถานะ HTTP %(httpStatus)s)",
"Remember, you can always set an email address in user settings if you change your mind.": "อย่าลืม คุณสามารถตั้งที่อยู่อีเมลในการตั้งค่าผู้ใช้ได้ทุกเมื่อหากคุณเปลี่ยนใจ", "Remember, you can always set an email address in user settings if you change your mind.": "อย่าลืม คุณสามารถตั้งที่อยู่อีเมลในการตั้งค่าผู้ใช้ได้ทุกเมื่อหากคุณเปลี่ยนใจ",
"You have successfully set a password and an email address!": "ตั้งรหัสผ่านและที่อยู่อีเมลสำเร็จแล้ว!" "You have successfully set a password and an email address!": "ตั้งรหัสผ่านและที่อยู่อีเมลสำเร็จแล้ว!",
"Warning": "คำเตือน",
"Checking for an update...": "กำลังตรวจหาอัปเดต...",
"Error encountered (%(errorDetail)s).": "เกิดข้อผิดพลาด (%(errorDetail)s)",
"No update available.": "ไม่มีอัปเดตที่ใหม่กว่า",
"Downloading update...": "กำลังดาวน์โหลดอัปเดต...",
"To return to your account in future you need to <u>set a password</u>": "คุณต้อง<u>ตั้งรหัสผ่าน</u>เพื่อจะกลับมาที่บัญชีนี้ในอนาคต",
"Set Password": "ตั้งรหัสผ่าน"
} }

View File

@ -18,7 +18,7 @@
"Collapse panel": "Katlanır panel", "Collapse panel": "Katlanır panel",
"Collecting app version information": "Uygulama sürümü bilgileri toplanıyor", "Collecting app version information": "Uygulama sürümü bilgileri toplanıyor",
"Collecting logs": "Kayıtlar toplanıyor", "Collecting logs": "Kayıtlar toplanıyor",
"Create new room": "Yeni oda oluştur", "Create new room": "Yeni Oda Oluştur",
"Couldn't find a matching Matrix room": "Eşleşen bir Matrix odası bulunamadı", "Couldn't find a matching Matrix room": "Eşleşen bir Matrix odası bulunamadı",
"Custom Server Options": "Özel Sunucu Seçenekleri", "Custom Server Options": "Özel Sunucu Seçenekleri",
"customServer_text": "Farklı bir Ana Sunucu URL'si belirleyerek başka bir Matrix sunucusunda oturum açmak için Özel Sunucu Seçeneklerini kullanabilirsiniz. <br/> Bu , Riot'u mevcut Matrix hesabı ile farklı bir Ana Sunucuda kullanmanıza olanak tanır.<br/> <br/> Ayrıca Özel Kimlik Sunucu'da ayarlayabilirsiniz ama kullanıcıları e-posta adresleriyle veya kendi e-posta adresinizle davet edemezsiniz.", "customServer_text": "Farklı bir Ana Sunucu URL'si belirleyerek başka bir Matrix sunucusunda oturum açmak için Özel Sunucu Seçeneklerini kullanabilirsiniz. <br/> Bu , Riot'u mevcut Matrix hesabı ile farklı bir Ana Sunucuda kullanmanıza olanak tanır.<br/> <br/> Ayrıca Özel Kimlik Sunucu'da ayarlayabilirsiniz ama kullanıcıları e-posta adresleriyle veya kendi e-posta adresinizle davet edemezsiniz.",
@ -27,9 +27,9 @@
"Describe your problem here.": "Probleminizi burada açıklayın.", "Describe your problem here.": "Probleminizi burada açıklayın.",
"Direct Chat": "Doğrudan Sohbet", "Direct Chat": "Doğrudan Sohbet",
"Directory": "Dizin", "Directory": "Dizin",
"Dismiss": "Reddet", "Dismiss": "Uzaklaştır",
"Download this file": "Bu dosyayı indir", "Download this file": "Bu dosyayı indir",
"Drop here %(toAction)s": "Burayı terket %(toAction)s", "Drop here %(toAction)s": "%(toAction)s'ı buraya bırak",
"Enable audible notifications in web client": "Web istemcisinde sesli bildirimleri etkinleştir", "Enable audible notifications in web client": "Web istemcisinde sesli bildirimleri etkinleştir",
"Enable desktop notifications": "Masaüstü bildirimlerini etkinleştir", "Enable desktop notifications": "Masaüstü bildirimlerini etkinleştir",
"Enable email notifications": "E-posta bildirimlerini etkinleştir", "Enable email notifications": "E-posta bildirimlerini etkinleştir",
@ -43,11 +43,11 @@
"Failed to": "Başaramadı", "Failed to": "Başaramadı",
"Failed to add tag %(tagName)s to room": "%(tagName)s etiketi odaya eklenemedi", "Failed to add tag %(tagName)s to room": "%(tagName)s etiketi odaya eklenemedi",
"Failed to change settings": "Ayarlar değiştirilemedi", "Failed to change settings": "Ayarlar değiştirilemedi",
"Failed to forget room %(errCode)s": "Odayı unutma başarısız oldu %(errCode)s", "Failed to forget room %(errCode)s": "Oda unutulması başarısız oldu %(errCode)s",
"Failed to update keywords": "Anahtar kelimeler güncellenemedi", "Failed to update keywords": "Anahtar kelimeler güncellenemedi",
"Failed to get protocol list from Home Server": "Ana Sunucu'dan protokol listesi alınamadı", "Failed to get protocol list from Home Server": "Ana Sunucu'dan protokol listesi alınamadı",
"Failed to get public room list": "Genel odalar listesi alınamadı", "Failed to get public room list": "Genel odalar listesi alınamadı",
"Failed to join the room": "Odaya girilemedi", "Failed to join the room": "Odaya girme başarısız oldu",
"Failed to remove tag %(tagName)s from room": "Odadan %(tagName)s etiketi kaldırılamadı", "Failed to remove tag %(tagName)s from room": "Odadan %(tagName)s etiketi kaldırılamadı",
"Failed to send report: ": "Rapor gönderilemedi: ", "Failed to send report: ": "Rapor gönderilemedi: ",
"Failed to set direct chat tag": "Direkt sohbet etiketi ayarlanamadı", "Failed to set direct chat tag": "Direkt sohbet etiketi ayarlanamadı",
@ -90,7 +90,7 @@
"Notify me for anything else": "Başka herhangi bir şey için bana bildirim yap", "Notify me for anything else": "Başka herhangi bir şey için bana bildirim yap",
"Off": "Kapalı", "Off": "Kapalı",
"On": "Açık", "On": "Açık",
"Operation failed": "Operasyon başarısız", "Operation failed": "Operasyon başarısız oldu",
"Permalink": "Kalıcı Bağlantı(permalink)", "Permalink": "Kalıcı Bağlantı(permalink)",
"Please describe the bug. What did you do? What did you expect to happen? What actually happened?": "Lütfen hatayı tanımlayın. Ne yaptınız ? Ne gerçekleşmesini beklediniz ? Ne gerçekleşti ?", "Please describe the bug. What did you do? What did you expect to happen? What actually happened?": "Lütfen hatayı tanımlayın. Ne yaptınız ? Ne gerçekleşmesini beklediniz ? Ne gerçekleşti ?",
"Please describe the bug and/or send logs.": "Lütfen hatayı tanımlayın ve/veya kayıtları gönderin.", "Please describe the bug and/or send logs.": "Lütfen hatayı tanımlayın ve/veya kayıtları gönderin.",
@ -110,7 +110,7 @@
"Riot does not know how to join a room on this network": "Riot bu ağdaki bir odaya nasıl gireceğini bilmiyor", "Riot does not know how to join a room on this network": "Riot bu ağdaki bir odaya nasıl gireceğini bilmiyor",
"Riot is not supported on mobile web. Install the app?": "Riot mobil web'de desteklenmiyor . Uygulamayı yükle ?", "Riot is not supported on mobile web. Install the app?": "Riot mobil web'de desteklenmiyor . Uygulamayı yükle ?",
"Riot uses many advanced browser features, some of which are not available or experimental in your current browser.": "Riot geçerli tarayıcınızda mevcut olmayan veya denemelik olan birçok gelişmiş tarayıcı özelliği kullanıyor.", "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.": "Riot geçerli tarayıcınızda mevcut olmayan veya denemelik olan birçok gelişmiş tarayıcı özelliği kullanıyor.",
"Room directory": "Oda dizini", "Room directory": "Oda Rehberi",
"Room not found": "Oda bulunamadı", "Room not found": "Oda bulunamadı",
"Search": "Ara", "Search": "Ara",
"Search…": "Arama…", "Search…": "Arama…",
@ -120,7 +120,7 @@
"Settings": "Ayarlar", "Settings": "Ayarlar",
"Source URL": "Kaynak URL", "Source URL": "Kaynak URL",
"Sorry, your browser is <b>not</b> able to run Riot.": "Üzgünüz , tarayıcınız Riot'u <b> çalıştıramıyor </b>.", "Sorry, your browser is <b>not</b> able to run Riot.": "Üzgünüz , tarayıcınız Riot'u <b> çalıştıramıyor </b>.",
"Start chat": "Sohbet başlat", "Start chat": "Sohbet Başlat",
"The Home Server may be too old to support third party networks": "Ana Sunucu 3. parti ağları desteklemek için çok eski olabilir", "The Home Server may be too old to support third party networks": "Ana Sunucu 3. parti ağları desteklemek için çok eski olabilir",
"There are advanced notifications which are not shown here": "Burada gösterilmeyen gelişmiş bildirimler var", "There are advanced notifications which are not shown here": "Burada gösterilmeyen gelişmiş bildirimler var",
"The server may be unavailable or overloaded": "Sunucu kullanılamıyor veya aşırı yüklenmiş olabilir", "The server may be unavailable or overloaded": "Sunucu kullanılamıyor veya aşırı yüklenmiş olabilir",
@ -133,7 +133,7 @@
"Unavailable": "Kullanım dışı", "Unavailable": "Kullanım dışı",
"Unhide Preview": "Önizlemeyi Göster", "Unhide Preview": "Önizlemeyi Göster",
"Unknown device": "Bilinmeyen aygıt", "Unknown device": "Bilinmeyen aygıt",
"unknown error code": "Bilinmeyen hata kodu", "unknown error code": "bilinmeyen hata kodu",
"Unnamed room": "İsimsiz oda", "Unnamed room": "İsimsiz oda",
"Update": "Güncelleştirme", "Update": "Güncelleştirme",
"Uploaded on %(date)s by %(user)s": "%(user)s tarafında %(date)s e yüklendi", "Uploaded on %(date)s by %(user)s": "%(user)s tarafında %(date)s e yüklendi",
@ -191,10 +191,10 @@
"Dev chat for the Dendrite dev team": "Dendrite Geliştirici Takımı için Geliştirici sohbeti", "Dev chat for the Dendrite dev team": "Dendrite Geliştirici Takımı için Geliştirici sohbeti",
"Co-ordination for Riot/Web translators": "Riot/Web çevirmenleri için koordinasyon", "Co-ordination for Riot/Web translators": "Riot/Web çevirmenleri için koordinasyon",
"Lots of rooms already exist in Matrix, linked to existing networks (Slack, IRC, Gitter etc) or independent. Check out the directory!": "Matrix'te varolan ağlara (Slack , IRC , Gitter vb.) bağlı ya da bağımsız bir çok oda var . Dizini kontrol edin!", "Lots of rooms already exist in Matrix, linked to existing networks (Slack, IRC, Gitter etc) or independent. Check out the directory!": "Matrix'te varolan ağlara (Slack , IRC , Gitter vb.) bağlı ya da bağımsız bir çok oda var . Dizini kontrol edin!",
"Failed to change password. Is your password correct?": "Şifre değiştirme başarısız oldu . Şifreniz doğru mu ?", "Failed to change password. Is your password correct?": "Şifreniz değiştirilemedi . Şifreniz doğru mu ?",
"You have successfully set a password!": "Başarıyla bir şifre ayarladınız!", "You have successfully set a password!": "Başarıyla bir şifre ayarladınız!",
"You can now return to your account after signing out, and sign in on other devices.": "Şimdi oturumunuzu iptal ettikten sonra başka cihazda oturum açarak hesabınıza dönebilirsiniz.", "You can now return to your account after signing out, and sign in on other devices.": "Şimdi oturumunuzu iptal ettikten sonra başka cihazda oturum açarak hesabınıza dönebilirsiniz.",
"Continue": "Devam", "Continue": "Devam Et",
"Please set a password!": "Lütfen bir şifre ayarlayın !", "Please set a password!": "Lütfen bir şifre ayarlayın !",
"This will allow you to return to your account after signing out, and sign in on other devices.": "Bu oturumunuzu kapattıktan sonra hesabınıza dönmenizi ve diğer cihazlarda oturum açmanızı sağlar.", "This will allow you to return to your account after signing out, and sign in on other devices.": "Bu oturumunuzu kapattıktan sonra hesabınıza dönmenizi ve diğer cihazlarda oturum açmanızı sağlar.",
"You have successfully set a password and an email address!": "Başarıyla bir şifre ve e-posta adresi ayarladın !", "You have successfully set a password and an email address!": "Başarıyla bir şifre ve e-posta adresi ayarladın !",

View File

@ -194,5 +194,11 @@
"No rooms to show": "Кімнати відсутні", "No rooms to show": "Кімнати відсутні",
"Noisy": "Шумний", "Noisy": "Шумний",
"Unavailable": "Нема в наявності", "Unavailable": "Нема в наявності",
"Unhide Preview": "Відкрити попередній перегляд" "Unhide Preview": "Відкрити попередній перегляд",
"Failed to set Direct Message status of room": "Не вдалось встановити статус прямого спілкування в кімнаті",
"Messages containing my display name": "Повідомлення, вміщає моє ім'я",
"Running Matrix services": "Запуск служби Matrix",
"Set Password": "Задати пароль",
"Notifications on the following keywords follow rules which cant be displayed here:": "Сповіщення з наступних ключових слів дотримуються правил, що не можуть бути показані тут:",
"To return to your account in future you need to <u>set a password</u>": "Щоб мати змогу використовувати вашу обліковку у майбутньому, <u>зазначте пароль</u>"
} }

View File

@ -306,3 +306,24 @@ textarea {
.changelog_text { .changelog_text {
font-family: 'Open Sans', Arial, Helvetica, Sans-Serif; font-family: 'Open Sans', Arial, Helvetica, Sans-Serif;
} }
.mx_Beta {
color: red;
margin-right: 10px;
position: relative;
top: -3px;
background-color: white;
padding: 0 4px;
border-radius: 3px;
border: 1px solid darkred;
cursor: help;
transition-duration: 200ms;
font-size: smaller;
filter: opacity(0.5);
}
.mx_Beta:hover {
color: white;
border: 1px solid gray;
background-color: darkred;
}

View File

@ -3,8 +3,10 @@
@import "./matrix-react-sdk/structures/_ContextualMenu.scss"; @import "./matrix-react-sdk/structures/_ContextualMenu.scss";
@import "./matrix-react-sdk/structures/_CreateRoom.scss"; @import "./matrix-react-sdk/structures/_CreateRoom.scss";
@import "./matrix-react-sdk/structures/_FilePanel.scss"; @import "./matrix-react-sdk/structures/_FilePanel.scss";
@import "./matrix-react-sdk/structures/_GroupView.scss";
@import "./matrix-react-sdk/structures/_LoginBox.scss"; @import "./matrix-react-sdk/structures/_LoginBox.scss";
@import "./matrix-react-sdk/structures/_MatrixChat.scss"; @import "./matrix-react-sdk/structures/_MatrixChat.scss";
@import "./matrix-react-sdk/structures/_MyGroups.scss";
@import "./matrix-react-sdk/structures/_NotificationPanel.scss"; @import "./matrix-react-sdk/structures/_NotificationPanel.scss";
@import "./matrix-react-sdk/structures/_RoomStatusBar.scss"; @import "./matrix-react-sdk/structures/_RoomStatusBar.scss";
@import "./matrix-react-sdk/structures/_RoomView.scss"; @import "./matrix-react-sdk/structures/_RoomView.scss";
@ -17,6 +19,7 @@
@import "./matrix-react-sdk/views/dialogs/_ChatCreateOrReuseChatDialog.scss"; @import "./matrix-react-sdk/views/dialogs/_ChatCreateOrReuseChatDialog.scss";
@import "./matrix-react-sdk/views/dialogs/_ChatInviteDialog.scss"; @import "./matrix-react-sdk/views/dialogs/_ChatInviteDialog.scss";
@import "./matrix-react-sdk/views/dialogs/_ConfirmUserActionDialog.scss"; @import "./matrix-react-sdk/views/dialogs/_ConfirmUserActionDialog.scss";
@import "./matrix-react-sdk/views/dialogs/_CreateGroupDialog.scss";
@import "./matrix-react-sdk/views/dialogs/_EncryptedEventDialog.scss"; @import "./matrix-react-sdk/views/dialogs/_EncryptedEventDialog.scss";
@import "./matrix-react-sdk/views/dialogs/_SetMxIdDialog.scss"; @import "./matrix-react-sdk/views/dialogs/_SetMxIdDialog.scss";
@import "./matrix-react-sdk/views/dialogs/_UnknownDeviceDialog.scss"; @import "./matrix-react-sdk/views/dialogs/_UnknownDeviceDialog.scss";
@ -38,6 +41,7 @@
@import "./matrix-react-sdk/views/messages/_RoomAvatarEvent.scss"; @import "./matrix-react-sdk/views/messages/_RoomAvatarEvent.scss";
@import "./matrix-react-sdk/views/messages/_TextualEvent.scss"; @import "./matrix-react-sdk/views/messages/_TextualEvent.scss";
@import "./matrix-react-sdk/views/messages/_UnknownBody.scss"; @import "./matrix-react-sdk/views/messages/_UnknownBody.scss";
@import "./matrix-react-sdk/views/rooms/_AppsDrawer.scss";
@import "./matrix-react-sdk/views/rooms/_Autocomplete.scss"; @import "./matrix-react-sdk/views/rooms/_Autocomplete.scss";
@import "./matrix-react-sdk/views/rooms/_EntityTile.scss"; @import "./matrix-react-sdk/views/rooms/_EntityTile.scss";
@import "./matrix-react-sdk/views/rooms/_EventTile.scss"; @import "./matrix-react-sdk/views/rooms/_EventTile.scss";
@ -53,7 +57,6 @@
@import "./matrix-react-sdk/views/rooms/_RoomSettings.scss"; @import "./matrix-react-sdk/views/rooms/_RoomSettings.scss";
@import "./matrix-react-sdk/views/rooms/_RoomTile.scss"; @import "./matrix-react-sdk/views/rooms/_RoomTile.scss";
@import "./matrix-react-sdk/views/rooms/_SearchableEntityList.scss"; @import "./matrix-react-sdk/views/rooms/_SearchableEntityList.scss";
@import "./matrix-react-sdk/views/rooms/_TabCompleteBar.scss";
@import "./matrix-react-sdk/views/rooms/_TopUnreadMessagesBar.scss"; @import "./matrix-react-sdk/views/rooms/_TopUnreadMessagesBar.scss";
@import "./matrix-react-sdk/views/settings/_DevicesPanel.scss"; @import "./matrix-react-sdk/views/settings/_DevicesPanel.scss";
@import "./matrix-react-sdk/views/settings/_IntegrationsManager.scss"; @import "./matrix-react-sdk/views/settings/_IntegrationsManager.scss";

View File

@ -0,0 +1,157 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_GroupView {
max-width: 960px;
width: 100%;
}
.mx_GroupView_error {
margin: auto;
}
.mx_GroupView_header {
max-width: 960px;
margin: auto;
height: 70px;
align-items: center;
display: flex;
margin-bottom: 20px;
}
.mx_GroupView_header_view {
border-bottom: 1px solid #e5e5e5;
}
.mx_GroupView_header_avatar, .mx_GroupView_header_info {
display: table-cell;
vertical-align: middle;
}
.mx_GroupHeader_button {
margin-left: 12px;
cursor: pointer;
}
.mx_GroupHeader_button object {
// prevents clicks from being swallowed by svg in 'object' tag
pointer-events: none;
}
.mx_GroupView_avatarPicker {
position: relative;
}
.mx_GroupView_avatarPicker_edit {
position: absolute;
top: 50px;
left: 15px;
}
.mx_GroupView_avatarPicker .mx_Spinner {
width: 48px;
height: 48px ! important;
}
.mx_GroupView_header_leftCol {
flex: 1;
}
.mx_GroupView_saveButton, .mx_GroupView_cancelButton {
display: table-cell;
}
.mx_GroupView_header_groupid {
font-weight: normal;
font-size: initial;
padding-left: 10px;
}
.mx_GroupView_header_name {
vertical-align: middle;
width: 100%;
height: 31px;
overflow: hidden;
color: $primary-fg-color;
font-weight: bold;
font-size: 22px;
padding-left: 19px;
padding-right: 16px;
/* why isn't text-overflow working? */
text-overflow: ellipsis;
border-bottom: 1px solid transparent;
}
.mx_GroupView_header_name input, .mx_GroupView_header_shortDesc input {
width: 400px;
}
.mx_GroupView_header_shortDesc {
vertical-align: bottom;
float: left;
max-height: 42px;
color: $settings-grey-fg-color;
font-weight: 300;
font-size: 13px;
padding-left: 19px;
margin-right: 16px;
overflow: hidden;
text-overflow: ellipsis;
border-bottom: 1px solid transparent;
}
.mx_GroupView_avatarPicker_label {
cursor: pointer;
}
.mx_GroupView_cancelButton {
padding-left: 8px;
}
.mx_GroupView_cancelButton img {
position: relative;
top: 5px;
}
.mx_GroupView_featuredThings {
margin-top: 20px;
}
.mx_GroupView_featuredThings_header {
font-weight: bold;
font-size: 120%;
margin-bottom: 20px;
}
.mx_GroupView_featuredThings_category {
font-weight: bold;
font-size: 110%;
margin-top: 10px;
}
.mx_GroupView_featuredThing {
cursor: pointer;
display: table-cell;
}
.mx_GroupView_uploadInput {
display: none;
}
.mx_GroupView_editLongDesc {
width: 100%;
height: 150px;
}

View File

@ -0,0 +1,44 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_MyGroups_joinCreateBox {
display: table;
margin-bottom: 30px;
}
.mx_MyGroups_createBox, .mx_MyGroups_joinBox {
display: table-cell;
width: 40%;
}
.mx_MyGroups_joinCreateHeader {
font-weight: bold;
margin-bottom: 10px;
}
.mx_MyGroups_joinCreateButton {
float: left;
margin: 10px;
}
.mx_MyGroups_joinCreateButton object {
/* Otherwise the SVG object absorbs clicks and the button doesn't work */
pointer-events: none;
}
.mx_MyGroups_content {
clear: left;
}

View File

@ -140,11 +140,6 @@ limitations under the License.
cursor: pointer; cursor: pointer;
} }
.mx_RoomStatusBar_tabCompleteBar {
padding-top: 10px;
color: $primary-fg-color;
}
.mx_RoomStatusBar_typingBar { .mx_RoomStatusBar_typingBar {
height: 50px; height: 50px;
line-height: 50px; line-height: 50px;
@ -155,26 +150,6 @@ limitations under the License.
display: block; display: block;
} }
.mx_RoomStatusBar_tabCompleteWrapper {
display: flex;
height: 26px;
}
.mx_RoomStatusBar_tabCompleteWrapper .mx_TabCompleteBar {
flex: 1 1 auto;
}
.mx_RoomStatusBar_tabCompleteEol {
flex: 0 0 auto;
color: $accent-color;
}
.mx_RoomStatusBar_tabCompleteEol object {
vertical-align: middle;
margin-right: 8px;
margin-top: -2px;
}
.mx_MatrixChat_useCompactLayout { .mx_MatrixChat_useCompactLayout {
.mx_RoomStatusBar { .mx_RoomStatusBar {
min-height: 40px; min-height: 40px;

View File

@ -102,6 +102,13 @@ limitations under the License.
padding-right: 1em; padding-right: 1em;
} }
.mx_UserSettings_passwordWarning {
/* To move the "Sign out" button out of the way */
clear: both;
color: $warning-color;
margin-bottom: 5px;
}
.mx_UserSettings_importExportButtons { .mx_UserSettings_importExportButtons {
padding-top: 10px; padding-top: 10px;
padding-left: 40px; padding-left: 40px;

View File

@ -1,5 +1,5 @@
/* /*
Copyright 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -14,17 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import PlatformPeg from 'matrix-react-sdk/lib/PlatformPeg'; .mx_CreateGroupDialog_inputRow {
margin-top: 10px;
var POKE_RATE_MS = 10 * 60 * 1000; // 10 min margin-bottom: 10px;
module.exports = {
start: function() {
module.exports.poll();
setInterval(module.exports.poll, POKE_RATE_MS);
},
poll: function() {
PlatformPeg.get().pollForUpdate();
} }
};
.mx_CreateGroupDialog_label {
text-align: left;
padding-bottom: 12px;
}
.mx_CreateGroupDialog_input {
font-size: 15px;
border-radius: 3px;
border: 1px solid $input-border-color;
padding: 9px;
color: $primary-fg-color;
background-color: $primary-bg-color;
}

View File

@ -2,19 +2,42 @@
// naming scheme; it's completely unclear where or how they're being used // naming scheme; it's completely unclear where or how they're being used
// --Matthew // --Matthew
.mx_UserPill { .mx_UserPill,
color: white; .mx_RoomPill {
background-color: $accent-color;
padding: 2px 8px;
border-radius: 16px; border-radius: 16px;
display: inline-block;
height: 20px;
line-height: 20px;
} }
/* More specific to override `.markdown-body a` color */
.mx_EventTile_content .markdown-body a.mx_UserPill,
.mx_UserPill {
color: $primary-fg-color;
background-color: $other-user-pill-bg-color;
padding-right: 5px;
}
.mx_EventTile_highlight .mx_EventTile_content .markdown-body a.mx_UserPill_me {
color: $accent-fg-color;
background-color: $mention-user-pill-bg-color;
padding-right: 5px;
}
/* More specific to override `.markdown-body a` color */
.mx_EventTile_content .markdown-body a.mx_RoomPill,
.mx_RoomPill { .mx_RoomPill {
background-color: white; color: $accent-fg-color;
color: $accent-color; background-color: $rte-room-pill-color;
border: 1px solid $accent-color; padding-right: 5px;
padding: 2px 8px; }
border-radius: 16px;
.mx_UserPill .mx_BaseAvatar,
.mx_RoomPill .mx_BaseAvatar {
position: relative;
top: 2px;
margin-left: 2px;
margin-right: 2px;
} }
.mx_Markdown_BOLD { .mx_Markdown_BOLD {

View File

@ -14,6 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
.mx_MEmoteBody {
white-space: pre-wrap;
}
.mx_MEmoteBody_sender { .mx_MEmoteBody_sender {
cursor: pointer; cursor: pointer;
} }

View File

@ -0,0 +1,228 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_AppsDrawer {
}
.mx_AppsContainer {
display: flex;
flex-direction: row;
align-items: center;
}
.mx_AddWidget_button {
order: 2;
cursor: pointer;
padding-right: 12px;
padding: 0;
margin: 0 0 5px 0;
color: $accent-color;
font-size: 12px;
}
.mx_SetAppURLDialog_input {
border-radius: 3px;
border: 1px solid $input-border-color;
padding: 9px;
color: $primary-hairline-color;
background-color: $primary-bg-color;
font-size: 15px;
}
.mx_AppTile {
width: 50%;
margin: 0 5px 2px 0;
border: 1px solid $primary-hairline-color;
border-radius: 2px;
// height: 350px;
// display: inline-block;
}
.mx_AppTileFullWidth {
width: 100%;
margin: 0;
padding: 0;
border: 1px solid $primary-hairline-color;
border-radius: 2px;
// height: 350px;
// display: inline-block;
}
.mx_AppTileMenuBar {
// height: 15px;
margin: 0;
padding: 2px 10px;
// background-color: $e2e-verified-color;
border-bottom: 1px solid $primary-hairline-color;
font-size: 10px;
}
.mx_AppTileMenuBarWidgets {
float: right;
}
.mx_AppTileMenuBarWidget {
// pointer-events: none;
cursor: pointer;
width: 10px;
height: 10px;
padding: 1px;
transition-duration: 500ms;
border: 1px solid transparent;
}
.mx_AppTileMenuBarWidgetDelete {
filter: none;
}
.mx_AppTileMenuBarWidget:hover {
border: 1px solid $primary-hairline-color;
border-radius: 2px;
}
.mx_AppTileBody{
height: 350px;
overflow: hidden;
}
.mx_AppTileBody iframe {
width: 100%;
height: 350px;
overflow: hidden;
border: none;
padding: 0;
margin: 0;
display: block;
}
.mx_CloseAppWidget {
}
.mx_AppTileMenuBarWidgetPadding {
margin-right: 5px;
}
.mx_AppIconTile {
background-color: $lightbox-bg-color;
border: 1px solid rgba(0, 0, 0, 0);
width: 200px;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
transition: 0.3s;
border-radius: 3px;
margin: 5px;
display: inline-block;
}
.mx_AppIconTile.mx_AppIconTile_active {
color: $accent-color;
border-color: $accent-color;
}
.mx_AppIconTile:hover {
border: 1px solid $accent-color;
box-shadow: 0 0 10px 5px rgba(200,200,200,0.5);
}
.mx_AppIconTile_content {
padding: 2px 16px;
height: 60px;
overflow: hidden;
}
.mx_AppIconTile_content h4 {
margin-top: 5px;
margin-bottom: 2px;
}
.mx_AppIconTile_content p {
margin-top: 0;
margin-bottom: 5px;
font-size: smaller;
}
.mx_AppIconTile_image {
padding: 10px;
width: 75%;
max-width:100px;
max-height:100px;
width: auto;
height: auto;
}
.mx_AppIconTile_imageContainer {
text-align: center;
width: 100%;
background-color: white;
border-radius: 3px 3px 0 0;
height: 155px;
display: flex;
justify-content: center;
align-items: center;
}
form.mx_Custom_Widget_Form div {
margin-top: 10px;
margin-bottom: 10px;
}
.mx_AppPermissionWarning {
text-align: center;
padding: 30px 0;
background-color: $primary-bg-color;
}
.mx_AppPermissionWarningImage {
margin: 10px 0;
}
.mx_AppPermissionWarningImage img {
width: 100px;
}
.mx_AppPermissionWarningText {
max-width: 400px;
margin: 10px auto 10px auto;
color: $primary-fg-color;
}
.mx_AppPermissionWarningTextLabel {
font-weight: bold;
display: block;
}
.mx_AppPermissionWarningTextURL {
color: $accent-color;
}
.mx_AppPermissionButton {
padding: 5px;
border-radius: 5px;
color: $warning-color;
background-color: $primary-bg-color;
}
.mx_AppPermissionButton:hover {
background-color: $primary-fg-color;
cursor: pointer;
}
.mx_AppLoading {
min-height: 305px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-weight: bold;
}

View File

@ -38,6 +38,7 @@
.mx_Autocomplete_Completion_pill { .mx_Autocomplete_Completion_pill {
border-radius: 17px; border-radius: 17px;
height: 34px; height: 34px;
padding: 0px 5px;
display: flex; display: flex;
user-select: none; user-select: none;
cursor: pointer; cursor: pointer;
@ -45,10 +46,22 @@
color: $primary-fg-color; color: $primary-fg-color;
} }
.mx_Autocomplete_Completion_pill * { .mx_Autocomplete_Completion_pill > * {
margin: 0 3px; margin: 0 3px;
} }
.mx_Autocomplete_Completion_container_truncate {
.mx_Autocomplete_Completion_title,
.mx_Autocomplete_Completion_subtitle,
.mx_Autocomplete_Completion_description {
/* Ellipsis for long names/subtitles/descriptions*/
max-width: 150px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
/* container for pill-style completions */ /* container for pill-style completions */
.mx_Autocomplete_Completion_container_pill { .mx_Autocomplete_Completion_container_pill {
margin: 12px; margin: 12px;

View File

@ -128,6 +128,12 @@ limitations under the License.
color: $event-sending-color; color: $event-sending-color;
} }
.mx_EventTile_sending .mx_UserPill,
.mx_EventTile_sending .mx_RoomPill,
.mx_EventTile_sending .mx_emojione {
opacity: 0.5;
}
.mx_EventTile_notSent { .mx_EventTile_notSent {
color: $event-notsent-color; color: $event-notsent-color;
} }
@ -316,7 +322,7 @@ limitations under the License.
font-family: inherit ! important; font-family: inherit ! important;
white-space: normal ! important; white-space: normal ! important;
line-height: inherit ! important; line-height: inherit ! important;
color: inherit; color: inherit; // inherit the colour from the dark or light theme by default (but not for code blocks)
font-size: 14px; font-size: 14px;
} }
@ -330,8 +336,11 @@ limitations under the License.
} }
.mx_EventTile_content .markdown-body code { .mx_EventTile_content .markdown-body code {
// deliberate constants as we're behind an invert filter
background-color: #f8f8f8; background-color: #f8f8f8;
color: #333;
} }
.mx_EventTile_copyButton { .mx_EventTile_copyButton {
position: absolute; position: absolute;
display: inline-block; display: inline-block;
@ -343,14 +352,17 @@ limitations under the License.
height: 19px; height: 19px;
background-image: url($copy-button-url); background-image: url($copy-button-url);
} }
.mx_EventTile_body pre { .mx_EventTile_body pre {
position: relative; position: relative;
border: 1px solid transparent; border: 1px solid transparent;
} }
.mx_EventTile:hover .mx_EventTile_body pre .mx_EventTile:hover .mx_EventTile_body pre
{ {
border: 1px solid $primary-hairline-color; border: 1px solid #e5e5e5; // deliberate constant as we're behind an invert filter
} }
.mx_EventTile_body pre:hover .mx_EventTile_copyButton .mx_EventTile_body pre:hover .mx_EventTile_copyButton
{ {
visibility: visible; visibility: visible;
@ -364,6 +376,7 @@ limitations under the License.
.mx_EventTile_content .markdown-body h6 .mx_EventTile_content .markdown-body h6
{ {
font-family: inherit ! important; font-family: inherit ! important;
color: inherit;
} }

View File

@ -78,6 +78,16 @@ limitations under the License.
margin-right: 6px; margin-right: 6px;
} }
@keyframes visualbell
{
from { background-color: #faa }
to { background-color: $primary-bg-color }
}
.mx_MessageComposer_input_error {
animation: 0.2s visualbell;
}
.mx_MessageComposer_input_empty .public-DraftEditorPlaceholder-root { .mx_MessageComposer_input_empty .public-DraftEditorPlaceholder-root {
display: none; display: none;
} }
@ -85,6 +95,15 @@ limitations under the License.
.mx_MessageComposer_input .DraftEditor-root { .mx_MessageComposer_input .DraftEditor-root {
width: 100%; width: 100%;
flex: 1; flex: 1;
word-break: break-word;
}
.mx_MessageComposer_input .DraftEditor-root .DraftEditor-editorContainer {
/* Ensure mx_UserPill and mx_RoomPill (see _RichText) are not obscured from the top */
padding-top: 2px;
}
.mx_MessageComposer_input .public-DraftEditor-content {
max-height: 120px; max-height: 120px;
overflow: auto; overflow: auto;
} }
@ -96,6 +115,12 @@ limitations under the License.
border-left: 4px solid $blockquote-bar-color; border-left: 4px solid $blockquote-bar-color;
} }
.mx_MessageComposer_input pre.public-DraftStyleDefault-pre pre {
background-color: $rte-code-bg-color;
border-radius: 3px;
padding: 10px;
}
.mx_MessageComposer_input textarea { .mx_MessageComposer_input textarea {
display: block; display: block;
width: 100%; width: 100%;
@ -128,7 +153,8 @@ limitations under the License.
.mx_MessageComposer_upload, .mx_MessageComposer_upload,
.mx_MessageComposer_hangup, .mx_MessageComposer_hangup,
.mx_MessageComposer_voicecall, .mx_MessageComposer_voicecall,
.mx_MessageComposer_videocall { .mx_MessageComposer_videocall,
.mx_MessageComposer_apps {
/*display: table-cell;*/ /*display: table-cell;*/
/*vertical-align: middle;*/ /*vertical-align: middle;*/
/*padding-left: 10px;*/ /*padding-left: 10px;*/
@ -140,7 +166,8 @@ limitations under the License.
.mx_MessageComposer_upload object, .mx_MessageComposer_upload object,
.mx_MessageComposer_hangup object, .mx_MessageComposer_hangup object,
.mx_MessageComposer_voicecall object, .mx_MessageComposer_voicecall object,
.mx_MessageComposer_videocall object { .mx_MessageComposer_videocall object,
.mx_MessageComposer_apps object {
pointer-events: none; pointer-events: none;
} }
@ -181,11 +208,6 @@ limitations under the License.
cursor: pointer; cursor: pointer;
} }
.mx_MessageComposer_format_button_disabled {
cursor: not-allowed;
opacity: 0.5;
}
.mx_MessageComposer_formatbar_cancel { .mx_MessageComposer_formatbar_cancel {
margin-right: 22px; margin-right: 22px;
} }

View File

@ -1,56 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_TabCompleteBar {
overflow: hidden;
}
.mx_TabCompleteBar_item {
display: inline-block;
margin-right: 15px;
margin-bottom: 2px;
cursor: pointer;
}
.mx_TabCompleteBar_command {
margin-right: 8px;
background-color: $accent-color;
padding-left: 8px;
padding-right: 8px;
padding-top: 2px;
padding-bottom: 2px;
margin-bottom: 6px;
border-radius: 30px;
position: relative;
top: 1px;
}
.mx_TabCompleteBar_command .mx_TabCompleteBar_text {
opacity: 1.0;
vertical-align: initial;
color: $accent-fg-color;
}
.mx_TabCompleteBar_item img {
margin-right: 8px;
vertical-align: middle;
}
.mx_TabCompleteBar_text {
color: $primary-fg-color;
vertical-align: middle;
opacity: 0.5;
}

View File

@ -19,6 +19,8 @@ $focus-brightness: 125%;
// red warning colour // red warning colour
$warning-color: #ff0064; $warning-color: #ff0064;
$mention-user-pill-bg-color: #ff0064;
$other-user-pill-bg-color: rgba(0, 0, 0, 0.1);
$preview-bar-bg-color: #f7f7f7; $preview-bar-bg-color: #f7f7f7;
@ -78,6 +80,7 @@ $voip-accept-color: #80f480;
$rte-bg-color: #e9e9e9; $rte-bg-color: #e9e9e9;
$rte-code-bg-color: rgba(0, 0, 0, 0.04); $rte-code-bg-color: rgba(0, 0, 0, 0.04);
$rte-room-pill-color: #aaa;
// ******************** // ********************

View File

@ -20,6 +20,8 @@ $focus-brightness: 200%;
// red warning colour // red warning colour
$warning-color: #ff0064; $warning-color: #ff0064;
$other-user-pill-bg-color: rgba(255, 255, 255, 0.1);
$preview-bar-bg-color: #333; $preview-bar-bg-color: #333;
// left-panel style muted accent color // left-panel style muted accent color

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: Sketch 3.4.2 (15857) - http://www.bohemiancoding.com/sketch -->
<title>Slice 1</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<path d="M9.74464309,-3.02908503 L8.14106175,-3.02908503 L8.14106175,8.19448443 L-3.03028759,8.19448443 L-3.03028759,9.7978515 L8.14106175,9.7978515 L8.14106175,20.9685098 L9.74464309,20.9685098 L9.74464309,9.7978515 L20.9697124,9.7978515 L20.9697124,8.19448443 L9.74464309,8.19448443 L9.74464309,-3.02908503" id="Fill-108" opacity="0.9" fill="#ff0064" sketch:type="MSShapeGroup" transform="translate(8.969712, 8.969712) rotate(-315.000000) translate(-8.969712, -8.969712) "></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
<g>
<rect x="178.846" y="92.087" transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 224.3476 631.1498)" width="128.085" height="354.049"/>
<path d="M471.723,88.393l-48.115-48.114c-11.723-11.724-31.558-10.896-44.304,1.85l-45.202,45.203l90.569,90.568l45.202-45.202
C482.616,119.952,483.445,100.116,471.723,88.393z"/>
<polygon points="64.021,363.252 32,480 148.737,447.979 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 876 B

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="35px"
height="35px" viewBox="0 0 35 35" enable-background="new 0 0 35 35" xml:space="preserve">
<g id="Layer_1">
<path id="Oval-109-Copy" fill="#76CFA6" enable-background="new " d="M17.5,35C27.165,35,35,27.165,35,17.5S27.165,0,17.5,0
S0,7.835,0,17.5S7.835,35,17.5,35z"/>
<g id="Icon">
<g>
<path fill="none" stroke="#FFFFFF" d="M7.5,12.5h5v-5h-5V12.5z M15,27.5h5v-5h-5V27.5z M7.5,27.5h5v-5h-5V27.5z M7.5,20h5v-5h-5
V20z M15,20h5v-5h-5V20z M22.5,7.5v5h5v-5H22.5z M15,12.5h5v-5h-5V12.5z M22.5,20h5v-5h-5V20z M22.5,27.5h5v-5h-5V27.5z"/>
</g>
</g>
</g>
<g id="Layer_2">
<g id="Icon_1_" opacity="0.15">
<g>
<path fill="none" stroke="#76CFA6" d="M7.5,12.5h5v-5h-5V12.5z M15,27.5h5v-5h-5V27.5z M7.5,27.5h5v-5h-5V27.5z M7.5,20h5v-5h-5
V20z M15,20h5v-5h-5V20z M22.5,7.5v5h5v-5H22.5z M15,12.5h5v-5h-5V12.5z M22.5,20h5v-5h-5V20z M22.5,27.5h5v-5h-5V27.5z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="35px" height="35px" viewBox="0 0 35 35" enable-background="new 0 0 35 35" xml:space="preserve">
<path id="Oval-109-Copy" opacity="0.15" fill="#76CFA6" enable-background="new " d="M17.5,35C27.165,35,35,27.165,35,17.5
S27.165,0,17.5,0S0,7.835,0,17.5S7.835,35,17.5,35z"/>
<g id="Icon">
<g>
<path fill="none" stroke="#76CFA6" d="M7.5,12.5h5v-5h-5V12.5z M15,27.5h5v-5h-5V27.5z M7.5,27.5h5v-5h-5V27.5z M7.5,20h5v-5h-5
V20z M15,20h5v-5h-5V20z M22.5,7.5v5h5v-5H22.5z M15,12.5h5v-5h-5V12.5z M22.5,20h5v-5h-5V20z M22.5,27.5h5v-5h-5V27.5z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 941 B

View File

@ -45,6 +45,12 @@ rageshake.init().then(() => {
console.error("Failed to initialise rageshake: " + err); console.error("Failed to initialise rageshake: " + err);
}); });
window.addEventListener('beforeunload', (e) => {
console.log('riot-web closing');
// try to flush the logs to indexeddb
rageshake.flush();
});
// add React and ReactPerf to the global namespace, to make them easier to // add React and ReactPerf to the global namespace, to make them easier to
// access via the console // access via the console
@ -59,8 +65,7 @@ var sdk = require("matrix-react-sdk");
const PlatformPeg = require("matrix-react-sdk/lib/PlatformPeg"); const PlatformPeg = require("matrix-react-sdk/lib/PlatformPeg");
sdk.loadSkin(require('../component-index')); sdk.loadSkin(require('../component-index'));
var VectorConferenceHandler = require('../VectorConferenceHandler'); var VectorConferenceHandler = require('../VectorConferenceHandler');
var UpdateChecker = require("./updater"); import Promise from 'bluebird';
var q = require('q');
var request = require('browser-request'); var request = require('browser-request');
import * as UserSettingsStore from 'matrix-react-sdk/lib/UserSettingsStore'; import * as UserSettingsStore from 'matrix-react-sdk/lib/UserSettingsStore';
import * as languageHandler from 'matrix-react-sdk/lib/languageHandler'; import * as languageHandler from 'matrix-react-sdk/lib/languageHandler';
@ -182,11 +187,11 @@ var makeRegistrationUrl = function(params) {
window.addEventListener('hashchange', onHashChange); window.addEventListener('hashchange', onHashChange);
function getConfig() { function getConfig(configJsonFilename) {
let deferred = q.defer(); let deferred = Promise.defer();
request( request(
{ method: "GET", url: "config.json" }, { method: "GET", url: configJsonFilename },
(err, response, body) => { (err, response, body) => {
if (err || response.status < 200 || response.status >= 300) { if (err || response.status < 200 || response.status >= 300) {
// Lack of a config isn't an error, we should // Lack of a config isn't an error, we should
@ -216,11 +221,10 @@ function getConfig() {
return deferred.promise; return deferred.promise;
} }
function onLoadCompleted() { function onTokenLoginCompleted() {
// if we did a token login, we're now left with the token, hs and is // if we did a token login, we're now left with the token, hs and is
// url as query params in the url; a little nasty but let's redirect to // url as query params in the url; a little nasty but let's redirect to
// clear them. // clear them.
if (window.location.search) {
var parsedUrl = url.parse(window.location.href); var parsedUrl = url.parse(window.location.href);
parsedUrl.search = ""; parsedUrl.search = "";
var formatted = url.format(parsedUrl); var formatted = url.format(parsedUrl);
@ -228,7 +232,6 @@ function onLoadCompleted() {
"from queryparams"); "from queryparams");
window.location.href = formatted; window.location.href = formatted;
} }
}
async function loadApp() { async function loadApp() {
await loadLanguage(); await loadLanguage();
@ -258,10 +261,20 @@ async function loadApp() {
} }
} }
// Load the config file. First try to load up a domain-specific config of the
// form "config.$domain.json" and if that fails, fall back to config.json.
let configJson; let configJson;
let configError; let configError;
try { try {
configJson = await getConfig(); try {
configJson = await getConfig(`config.${document.domain}.json`);
// 404s succeed with an empty json config, so check that there are keys
if (Object.keys(configJson).length === 0) {
throw new Error(); // throw to enter the catch
}
} catch (e) {
configJson = await getConfig("config.json");
}
} catch (e) { } catch (e) {
configError = e; configError = e;
} }
@ -277,7 +290,9 @@ async function loadApp() {
Unable to load config file: please refresh the page to try again. Unable to load config file: please refresh the page to try again.
</div>, document.getElementById('matrixchat')); </div>, document.getElementById('matrixchat'));
} else if (validBrowser) { } else if (validBrowser) {
UpdateChecker.start(); const platform = PlatformPeg.get();
platform.startUpdater();
const MatrixChat = sdk.getComponent('structures.MatrixChat'); const MatrixChat = sdk.getComponent('structures.MatrixChat');
window.matrixChat = ReactDOM.render( window.matrixChat = ReactDOM.render(
<MatrixChat <MatrixChat
@ -288,9 +303,9 @@ async function loadApp() {
realQueryParams={params} realQueryParams={params}
startingFragmentQueryParams={fragparts.params} startingFragmentQueryParams={fragparts.params}
enableGuest={true} enableGuest={true}
onLoadCompleted={onLoadCompleted} onTokenLoginCompleted={onTokenLoginCompleted}
initialScreenAfterLogin={getScreenFromLocation(window.location)} initialScreenAfterLogin={getScreenFromLocation(window.location)}
defaultDeviceDisplayName={PlatformPeg.get().getDefaultDeviceDisplayName()} defaultDeviceDisplayName={platform.getDefaultDeviceDisplayName()}
/>, />,
document.getElementById('matrixchat') document.getElementById('matrixchat')
); );

View File

@ -17,14 +17,21 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import VectorBasePlatform from './VectorBasePlatform'; import VectorBasePlatform, {updateCheckStatusEnum} from './VectorBasePlatform';
import dis from 'matrix-react-sdk/lib/dispatcher'; import dis from 'matrix-react-sdk/lib/dispatcher';
import { _t } from 'matrix-react-sdk/lib/languageHandler'; import { _t } from 'matrix-react-sdk/lib/languageHandler';
import q from 'q'; import Promise from 'bluebird';
import electron, {remote, ipcRenderer} from 'electron'; import {remote, ipcRenderer} from 'electron';
import rageshake from '../rageshake';
remote.autoUpdater.on('update-downloaded', onUpdateDownloaded); remote.autoUpdater.on('update-downloaded', onUpdateDownloaded);
// try to flush the rageshake logs to indexeddb before quit.
ipcRenderer.on('before-quit', function () {
console.log('riot-desktop closing');
rageshake.flush();
});
function onUpdateDownloaded(ev: Event, releaseNotes: string, ver: string, date: Date, updateURL: string) { function onUpdateDownloaded(ev: Event, releaseNotes: string, ver: string, date: Date, updateURL: string) {
dis.dispatch({ dis.dispatch({
action: 'new_version', action: 'new_version',
@ -62,10 +69,42 @@ function _onAction(payload: Object) {
} }
} }
function getUpdateCheckStatus(status) {
if (status === true) {
return { status: updateCheckStatusEnum.DOWNLOADING };
} else if (status === false) {
return { status: updateCheckStatusEnum.NOTAVAILABLE };
} else {
return {
status: updateCheckStatusEnum.ERROR,
detail: status,
};
}
}
export default class ElectronPlatform extends VectorBasePlatform { export default class ElectronPlatform extends VectorBasePlatform {
constructor() { constructor() {
super(); super();
dis.register(_onAction); dis.register(_onAction);
this.updatable = Boolean(remote.autoUpdater.getFeedURL());
/*
IPC Call `check_updates` returns:
true if there is an update available
false if there is not
or the error if one is encountered
*/
ipcRenderer.on('check_updates', (event, status) => {
if (!this.showUpdateCheck) return;
dis.dispatch({
action: 'check_updates',
value: getUpdateCheckStatus(status),
});
this.showUpdateCheck = false;
});
this.startUpdateCheck = this.startUpdateCheck.bind(this);
this.stopUpdateCheck = this.stopUpdateCheck.bind(this);
} }
getHumanReadableName(): string { getHumanReadableName(): string {
@ -134,20 +173,21 @@ export default class ElectronPlatform extends VectorBasePlatform {
} }
getAppVersion(): Promise<string> { getAppVersion(): Promise<string> {
return q(remote.app.getVersion()); return Promise.resolve(remote.app.getVersion());
} }
pollForUpdate() { startUpdateCheck() {
// In electron we control the update process ourselves, since if (this.showUpdateCheck) return;
// it needs to run in the main process, so we just run the timer super.startUpdateCheck();
// loop in the main electron process instead.
ipcRenderer.send('check_updates');
} }
installUpdate() { installUpdate() {
// IPC to the main process to install the update, since quitAndInstall // IPC to the main process to install the update, since quitAndInstall
// doesn't fire the before-quit event so the main process needs to know // doesn't fire the before-quit event so the main process needs to know
// it should exit. // it should exit.
electron.ipcRenderer.send('install_update'); ipcRenderer.send('install_update');
} }
getDefaultDeviceDisplayName(): string { getDefaultDeviceDisplayName(): string {
@ -161,7 +201,7 @@ export default class ElectronPlatform extends VectorBasePlatform {
isElectron(): boolean { return true; } isElectron(): boolean { return true; }
requestNotificationPermission(): Promise<string> { requestNotificationPermission(): Promise<string> {
return q('granted'); return Promise.resolve('granted');
} }
reload() { reload() {

View File

@ -19,9 +19,18 @@ limitations under the License.
import BasePlatform from 'matrix-react-sdk/lib/BasePlatform'; import BasePlatform from 'matrix-react-sdk/lib/BasePlatform';
import { _t } from 'matrix-react-sdk/lib/languageHandler'; import { _t } from 'matrix-react-sdk/lib/languageHandler';
import dis from 'matrix-react-sdk/lib/dispatcher';
import Favico from 'favico.js'; import Favico from 'favico.js';
export const updateCheckStatusEnum = {
CHECKING: 'CHECKING',
ERROR: 'ERROR',
NOTAVAILABLE: 'NOTAVAILABLE',
DOWNLOADING: 'DOWNLOADING',
READY: 'READY',
};
/** /**
* Vector-specific extensions to the BasePlatform template * Vector-specific extensions to the BasePlatform template
*/ */
@ -34,7 +43,12 @@ export default class VectorBasePlatform extends BasePlatform {
// and we set the state each time, even if the value hasn't changed, // and we set the state each time, even if the value hasn't changed,
// so we'd need to fix that if enabling the animation. // so we'd need to fix that if enabling the animation.
this.favicon = new Favico({animation: 'none'}); this.favicon = new Favico({animation: 'none'});
this.showUpdateCheck = false;
this._updateFavicon(); this._updateFavicon();
this.updatable = true;
this.startUpdateCheck = this.startUpdateCheck.bind(this);
this.stopUpdateCheck = this.stopUpdateCheck.bind(this);
} }
getHumanReadableName(): string { getHumanReadableName(): string {
@ -75,12 +89,32 @@ export default class VectorBasePlatform extends BasePlatform {
} }
/** /**
* Check for the availability of an update to the version of the * Begin update polling, if applicable
* app that's currently running.
* If an update is available, this function should dispatch the
* 'new_version' action.
*/ */
pollForUpdate() { startUpdater() {
}
/**
* Whether we can call checkForUpdate on this platform build
*/
canSelfUpdate(): boolean {
return this.updatable;
}
startUpdateCheck() {
this.showUpdateCheck = true;
dis.dispatch({
action: 'check_updates',
value: { status: updateCheckStatusEnum.CHECKING },
});
}
stopUpdateCheck() {
this.showUpdateCheck = false;
dis.dispatch({
action: 'check_updates',
value: false,
})
} }
/** /**

View File

@ -17,19 +17,24 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import VectorBasePlatform from './VectorBasePlatform'; import VectorBasePlatform, {updateCheckStatusEnum} from './VectorBasePlatform';
import request from 'browser-request'; import request from 'browser-request';
import dis from 'matrix-react-sdk/lib/dispatcher.js'; import dis from 'matrix-react-sdk/lib/dispatcher.js';
import { _t } from 'matrix-react-sdk/lib/languageHandler'; import { _t } from 'matrix-react-sdk/lib/languageHandler';
import q from 'q'; import Promise from 'bluebird';
import url from 'url'; import url from 'url';
import UAParser from 'ua-parser-js'; import UAParser from 'ua-parser-js';
var POKE_RATE_MS = 10 * 60 * 1000; // 10 min
export default class WebPlatform extends VectorBasePlatform { export default class WebPlatform extends VectorBasePlatform {
constructor() { constructor() {
super(); super();
this.runningVersion = null; this.runningVersion = null;
this.startUpdateCheck = this.startUpdateCheck.bind(this);
this.stopUpdateCheck = this.stopUpdateCheck.bind(this);
} }
getHumanReadableName(): string { getHumanReadableName(): string {
@ -63,7 +68,7 @@ export default class WebPlatform extends VectorBasePlatform {
// annoyingly, the latest spec says this returns a // annoyingly, the latest spec says this returns a
// promise, but this is only supported in Chrome 46 // promise, but this is only supported in Chrome 46
// and Firefox 47, so adapt the callback API. // and Firefox 47, so adapt the callback API.
const defer = q.defer(); const defer = Promise.defer();
global.Notification.requestPermission((result) => { global.Notification.requestPermission((result) => {
defer.resolve(result); defer.resolve(result);
}); });
@ -98,7 +103,7 @@ export default class WebPlatform extends VectorBasePlatform {
} }
_getVersion(): Promise<string> { _getVersion(): Promise<string> {
const deferred = q.defer(); const deferred = Promise.defer();
// We add a cachebuster to the request to make sure that we know about // We add a cachebuster to the request to make sure that we know about
// the most recent version on the origin server. That might not // the most recent version on the origin server. That might not
@ -127,13 +132,18 @@ export default class WebPlatform extends VectorBasePlatform {
getAppVersion(): Promise<string> { getAppVersion(): Promise<string> {
if (this.runningVersion !== null) { if (this.runningVersion !== null) {
return q(this.runningVersion); return Promise.resolve(this.runningVersion);
} }
return this._getVersion(); return this._getVersion();
} }
startUpdater() {
this.pollForUpdate();
setInterval(this.pollForUpdate.bind(this), POKE_RATE_MS);
}
pollForUpdate() { pollForUpdate() {
this._getVersion().done((ver) => { return this._getVersion().then((ver) => {
if (this.runningVersion === null) { if (this.runningVersion === null) {
this.runningVersion = ver; this.runningVersion = ver;
} else if (this.runningVersion !== ver) { } else if (this.runningVersion !== ver) {
@ -142,9 +152,29 @@ export default class WebPlatform extends VectorBasePlatform {
currentVersion: this.runningVersion, currentVersion: this.runningVersion,
newVersion: ver, newVersion: ver,
}); });
// Return to skip a MatrixChat state update
return;
} }
return { status: updateCheckStatusEnum.NOTAVAILABLE };
}, (err) => { }, (err) => {
console.error("Failed to poll for update", err); console.error("Failed to poll for update", err);
return {
status: updateCheckStatusEnum.ERROR,
detail: err.message || err.status ? err.status.toString() : 'Unknown Error',
};
});
}
startUpdateCheck() {
if (this.showUpdateCheck) return;
super.startUpdateCheck();
this.pollForUpdate().then((updateState) => {
if (!this.showUpdateCheck) return;
if (!updateState) return;
dis.dispatch({
action: 'check_updates',
value: updateState,
});
}); });
} }

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import q from "q"; import Promise from 'bluebird';
// This module contains all the code needed to log the console, persist it to // This module contains all the code needed to log the console, persist it to
// disk and submit bug reports. Rationale is as follows: // disk and submit bug reports. Rationale is as follows:
@ -116,7 +116,7 @@ class IndexedDBLogStore {
*/ */
connect() { connect() {
let req = this.indexedDB.open("logs"); let req = this.indexedDB.open("logs");
return q.Promise((resolve, reject) => { return new Promise((resolve, reject) => {
req.onsuccess = (event) => { req.onsuccess = (event) => {
this.db = event.target.result; this.db = event.target.result;
// Periodically flush logs to local storage / indexeddb // Periodically flush logs to local storage / indexeddb
@ -193,7 +193,7 @@ class IndexedDBLogStore {
} }
// there is no flush promise or there was but it has finished, so do // there is no flush promise or there was but it has finished, so do
// a brand new one, destroying the chain which may have been built up. // a brand new one, destroying the chain which may have been built up.
this.flushPromise = q.Promise((resolve, reject) => { this.flushPromise = new Promise((resolve, reject) => {
if (!this.db) { if (!this.db) {
// not connected yet or user rejected access for us to r/w to // not connected yet or user rejected access for us to r/w to
// the db. // the db.
@ -277,7 +277,7 @@ class IndexedDBLogStore {
} }
function deleteLogs(id) { function deleteLogs(id) {
return q.Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const txn = db.transaction( const txn = db.transaction(
["logs", "logslastmod"], "readwrite" ["logs", "logslastmod"], "readwrite"
); );
@ -375,7 +375,7 @@ class IndexedDBLogStore {
*/ */
function selectQuery(store, keyRange, resultMapper) { function selectQuery(store, keyRange, resultMapper) {
const query = store.openCursor(keyRange); const query = store.openCursor(keyRange);
return q.Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let results = []; let results = [];
query.onerror = (event) => { query.onerror = (event) => {
reject(new Error("Query failed: " + event.target.errorCode)); reject(new Error("Query failed: " + event.target.errorCode));
@ -410,8 +410,16 @@ module.exports = {
} }
logger = new ConsoleLogger(); logger = new ConsoleLogger();
logger.monkeyPatch(window.console); logger.monkeyPatch(window.console);
if (window.indexedDB) {
store = new IndexedDBLogStore(window.indexedDB, logger); // just *accessing* indexedDB throws an exception in firefox with
// indexeddb disabled.
let indexedDB;
try {
indexedDB = window.indexedDB;
} catch(e) {}
if (indexedDB) {
store = new IndexedDBLogStore(indexedDB, logger);
initPromise = store.connect(); initPromise = store.connect();
return initPromise; return initPromise;
} }
@ -419,6 +427,13 @@ module.exports = {
return initPromise; return initPromise;
}, },
flush: function() {
if (!store) {
return;
}
store.flush();
},
/** /**
* Clean up old logs. * Clean up old logs.
* @return Promise Resolves if cleaned logs. * @return Promise Resolves if cleaned logs.

View File

@ -15,7 +15,7 @@ limitations under the License.
*/ */
import pako from 'pako'; import pako from 'pako';
import q from "q"; import Promise from 'bluebird';
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg'; import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
import PlatformPeg from 'matrix-react-sdk/lib/PlatformPeg'; import PlatformPeg from 'matrix-react-sdk/lib/PlatformPeg';
@ -100,7 +100,7 @@ export default async function sendBugReport(bugReportEndpoint, opts) {
} }
function _submitReport(endpoint, body, progressCallback) { function _submitReport(endpoint, body, progressCallback) {
const deferred = q.defer(); const deferred = Promise.defer();
const req = new XMLHttpRequest(); const req = new XMLHttpRequest();
req.open("POST", endpoint); req.open("POST", endpoint);

View File

@ -33,10 +33,10 @@ var React = require('react');
var ReactDOM = require('react-dom'); var ReactDOM = require('react-dom');
var ReactTestUtils = require('react-addons-test-utils'); var ReactTestUtils = require('react-addons-test-utils');
var expect = require('expect'); var expect = require('expect');
var q = require('q'); import Promise from 'bluebird';
var test_utils = require('../test-utils'); var test_utils = require('../test-utils');
var MockHttpBackend = require('../mock-request'); var MockHttpBackend = require('matrix-mock-request');
var HS_URL='http://localhost'; var HS_URL='http://localhost';
var IS_URL='http://localhost'; var IS_URL='http://localhost';
@ -100,29 +100,19 @@ describe('joining a room', function () {
// wait for /sync to happen. This may take some time, as the client // wait for /sync to happen. This may take some time, as the client
// has to initialise indexeddb. // has to initialise indexeddb.
console.log("waiting for /sync"); console.log("waiting for /sync");
let syncDone = false;
httpBackend.when('GET', '/sync') httpBackend.when('GET', '/sync')
.check((r) => {syncDone = true;})
.respond(200, {}); .respond(200, {});
function awaitSync(attempts) {
if (syncDone) {
return q();
}
if (!attempts) {
throw new Error("Gave up waiting for /sync")
}
return httpBackend.flush().then(() => awaitSync(attempts-1));
}
return awaitSync(10).then(() => { return httpBackend.flushAllExpected({
timeout: 1000,
}).then(() => {
// wait for the directory requests // wait for the directory requests
httpBackend.when('POST', '/publicRooms').respond(200, {chunk: []}); httpBackend.when('POST', '/publicRooms').respond(200, {chunk: []});
httpBackend.when('GET', '/thirdparty/protocols').respond(200, {}); httpBackend.when('GET', '/thirdparty/protocols').respond(200, {});
return q.all([ return httpBackend.flushAllExpected();
httpBackend.flush('/thirdparty/protocols'),
httpBackend.flush('/publicRooms'),
]);
}).then(() => { }).then(() => {
console.log(`${Date.now()} App made requests for directory view; switching to a room.`);
var roomDir = ReactTestUtils.findRenderedComponentWithType( var roomDir = ReactTestUtils.findRenderedComponentWithType(
matrixChat, RoomDirectory); matrixChat, RoomDirectory);
@ -139,19 +129,17 @@ describe('joining a room', function () {
httpBackend.when('GET', '/rooms/'+encodeURIComponent(ROOM_ID)+"/initialSync") httpBackend.when('GET', '/rooms/'+encodeURIComponent(ROOM_ID)+"/initialSync")
.respond(401, {errcode: 'M_GUEST_ACCESS_FORBIDDEN'}); .respond(401, {errcode: 'M_GUEST_ACCESS_FORBIDDEN'});
return q.all([ return httpBackend.flushAllExpected();
httpBackend.flush('/directory/room/'+encodeURIComponent(ROOM_ALIAS), 1, 200),
httpBackend.flush('/rooms/'+encodeURIComponent(ROOM_ID)+"/initialSync", 1, 200),
]);
}).then(() => { }).then(() => {
httpBackend.verifyNoOutstandingExpectation(); console.log(`${Date.now()} App made room preview request`);
return q.delay(1); // we should now have a roomview
}).then(() => {
// we should now have a roomview, with a preview bar
roomView = ReactTestUtils.findRenderedComponentWithType( roomView = ReactTestUtils.findRenderedComponentWithType(
matrixChat, RoomView); matrixChat, RoomView);
// the preview bar may take a tick to be displayed
return Promise.delay(1);
}).then(() => {
const previewBar = ReactTestUtils.findRenderedComponentWithType( const previewBar = ReactTestUtils.findRenderedComponentWithType(
roomView, RoomPreviewBar); roomView, RoomPreviewBar);
@ -164,14 +152,14 @@ describe('joining a room', function () {
.respond(200, {room_id: ROOM_ID}); .respond(200, {room_id: ROOM_ID});
}).then(() => { }).then(() => {
// wait for the join request to be made // wait for the join request to be made
return q.delay(1); return Promise.delay(1);
}).then(() => { }).then(() => {
// and again, because the state update has to go to the store and // and again, because the state update has to go to the store and
// then one dispatch within the store, then to the view // then one dispatch within the store, then to the view
// XXX: This is *super flaky*: a better way would be to declare // XXX: This is *super flaky*: a better way would be to declare
// that we expect a certain state transition to happen, then wait // that we expect a certain state transition to happen, then wait
// for that transition to occur. // for that transition to occur.
return q.delay(1); return Promise.delay(1);
}).then(() => { }).then(() => {
// the roomview should now be loading // the roomview should now be loading
expect(roomView.state.room).toBe(null); expect(roomView.state.room).toBe(null);
@ -186,7 +174,7 @@ describe('joining a room', function () {
}).then(() => { }).then(() => {
httpBackend.verifyNoOutstandingExpectation(); httpBackend.verifyNoOutstandingExpectation();
return q.delay(1); return Promise.delay(1);
}).then(() => { }).then(() => {
// We've joined, expect this to false // We've joined, expect this to false
expect(roomView.state.joining).toBe(false); expect(roomView.state.joining).toBe(false);

View File

@ -22,16 +22,19 @@ import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import ReactTestUtils from 'react-addons-test-utils'; import ReactTestUtils from 'react-addons-test-utils';
import expect from 'expect'; import expect from 'expect';
import q from 'q'; import Promise from 'bluebird';
import MatrixReactTestUtils from 'matrix-react-test-utils';
import jssdk from 'matrix-js-sdk'; import jssdk from 'matrix-js-sdk';
import sdk from 'matrix-react-sdk'; import sdk from 'matrix-react-sdk';
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg'; import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
import * as languageHandler from 'matrix-react-sdk/lib/languageHandler'; import * as languageHandler from 'matrix-react-sdk/lib/languageHandler';
import {VIEWS} from 'matrix-react-sdk/lib/components/structures/MatrixChat';
import dis from 'matrix-react-sdk/lib/dispatcher';
import * as test_utils from '../test-utils'; import * as test_utils from '../test-utils';
import MockHttpBackend from '../mock-request'; import MockHttpBackend from 'matrix-mock-request';
import {parseQs, parseQsFromFragment} from '../../src/vector/url_utils'; import {parseQs, parseQsFromFragment} from '../../src/vector/url_utils';
var DEFAULT_HS_URL='http://my_server'; var DEFAULT_HS_URL='http://my_server';
@ -47,8 +50,8 @@ describe('loading:', function () {
// the mounted MatrixChat // the mounted MatrixChat
let matrixChat; let matrixChat;
// a promise which resolves when the MatrixChat calls onLoadCompleted // a promise which resolves when the MatrixChat calls onTokenLoginCompleted
let loadCompletePromise; let tokenLoginCompletePromise;
beforeEach(function() { beforeEach(function() {
test_utils.beforeEach(this); test_utils.beforeEach(this);
@ -69,6 +72,7 @@ describe('loading:', function () {
}); });
afterEach(async function () { afterEach(async function () {
console.log(`${Date.now()}: loading: afterEach`);
if (parentDiv) { if (parentDiv) {
ReactDOM.unmountComponentAtNode(parentDiv); ReactDOM.unmountComponentAtNode(parentDiv);
parentDiv.remove(); parentDiv.remove();
@ -78,9 +82,15 @@ describe('loading:', function () {
// unmounting should have cleared the MatrixClientPeg // unmounting should have cleared the MatrixClientPeg
expect(MatrixClientPeg.get()).toBe(null); expect(MatrixClientPeg.get()).toBe(null);
// chrome seems to take *ages* to delete the indexeddbs.
this.timeout(10000);
// clear the indexeddbs so we can start from a clean slate next time. // clear the indexeddbs so we can start from a clean slate next time.
await test_utils.deleteIndexedDB('matrix-js-sdk:crypto'); await Promise.all([
await test_utils.deleteIndexedDB('matrix-js-sdk:riot-web-sync'); test_utils.deleteIndexedDB('matrix-js-sdk:crypto'),
test_utils.deleteIndexedDB('matrix-js-sdk:riot-web-sync'),
]);
console.log(`${Date.now()}: loading: afterEach complete`);
}); });
/* simulate the load process done by index.js /* simulate the load process done by index.js
@ -99,8 +109,8 @@ describe('loading:', function () {
toString: function() { return this.search + this.hash; }, toString: function() { return this.search + this.hash; },
}; };
let loadCompleteDefer = q.defer(); let tokenLoginCompleteDefer = Promise.defer();
loadCompletePromise = loadCompleteDefer.promise; tokenLoginCompletePromise = tokenLoginCompleteDefer.promise;
function onNewScreen(screen) { function onNewScreen(screen) {
console.log(Date.now() + " newscreen "+screen); console.log(Date.now() + " newscreen "+screen);
@ -135,7 +145,7 @@ describe('loading:', function () {
realQueryParams={params} realQueryParams={params}
startingFragmentQueryParams={fragParts.params} startingFragmentQueryParams={fragParts.params}
enableGuest={true} enableGuest={true}
onLoadCompleted={loadCompleteDefer.resolve} onTokenLoginCompleted={() => tokenLoginCompleteDefer.resolve()}
initialScreenAfterLogin={getScreenFromLocation(windowLocation)} initialScreenAfterLogin={getScreenFromLocation(windowLocation)}
makeRegistrationUrl={() => {throw new Error('Not implemented');}} makeRegistrationUrl={() => {throw new Error('Not implemented');}}
/>, parentDiv />, parentDiv
@ -153,8 +163,8 @@ describe('loading:', function () {
.check((r) => {syncRequest = r;}) .check((r) => {syncRequest = r;})
.respond(200, response); .respond(200, response);
console.log("waiting for /sync");
for (let attempts = 10; attempts > 0; attempts--) { for (let attempts = 10; attempts > 0; attempts--) {
console.log(Date.now() + " waiting for /sync");
if (syncRequest) { if (syncRequest) {
return syncRequest; return syncRequest;
} }
@ -167,7 +177,7 @@ describe('loading:', function () {
it('gives a login panel by default', function (done) { it('gives a login panel by default', function (done) {
loadApp(); loadApp();
q.delay(1).then(() => { Promise.delay(1).then(() => {
// at this point, we're trying to do a guest registration; // at this point, we're trying to do a guest registration;
// we expect a spinner // we expect a spinner
assertAtLoadingSpinner(matrixChat); assertAtLoadingSpinner(matrixChat);
@ -179,12 +189,9 @@ describe('loading:', function () {
return httpBackend.flush(); return httpBackend.flush();
}).then(() => { }).then(() => {
// Wait for another trip around the event loop for the UI to update // Wait for another trip around the event loop for the UI to update
return q.delay(1); return awaitLoginComponent(matrixChat);
}).then(() => { }).then(() => {
// we expect a single <Login> component following session load expect(windowLocation.hash).toEqual("#/login");
ReactTestUtils.findRenderedComponentWithType(
matrixChat, sdk.getComponent('structures.login.Login'));
expect(windowLocation.hash).toEqual("");
}).done(done, done); }).done(done, done);
}); });
@ -193,7 +200,7 @@ describe('loading:', function () {
uriFragment: "#/room/!room:id", uriFragment: "#/room/!room:id",
}); });
q.delay(1).then(() => { Promise.delay(1).then(() => {
// at this point, we're trying to do a guest registration; // at this point, we're trying to do a guest registration;
// we expect a spinner // we expect a spinner
assertAtLoadingSpinner(matrixChat); assertAtLoadingSpinner(matrixChat);
@ -205,7 +212,7 @@ describe('loading:', function () {
return httpBackend.flush(); return httpBackend.flush();
}).then(() => { }).then(() => {
// Wait for another trip around the event loop for the UI to update // Wait for another trip around the event loop for the UI to update
return q.delay(1); return Promise.delay(10);
}).then(() => { }).then(() => {
return completeLogin(matrixChat); return completeLogin(matrixChat);
}).then(() => { }).then(() => {
@ -228,7 +235,7 @@ describe('loading:', function () {
uriFragment: "#/login", uriFragment: "#/login",
}); });
return q.delay(100).then(() => { return awaitLoginComponent(matrixChat).then(() => {
// we expect a single <Login> component // we expect a single <Login> component
ReactTestUtils.findRenderedComponentWithType( ReactTestUtils.findRenderedComponentWithType(
matrixChat, sdk.getComponent('structures.login.Login')); matrixChat, sdk.getComponent('structures.login.Login'));
@ -335,7 +342,7 @@ describe('loading:', function () {
}, },
}); });
return q.delay(1).then(() => { return Promise.delay(1).then(() => {
// we expect a loading spinner while we log into the RTS // we expect a loading spinner while we log into the RTS
assertAtLoadingSpinner(matrixChat); assertAtLoadingSpinner(matrixChat);
@ -360,6 +367,9 @@ describe('loading:', function () {
loadApp({ loadApp({
uriFragment: "#/login", uriFragment: "#/login",
}); });
// give the UI a chance to display
return awaitLoginComponent(matrixChat);
}); });
it('shows a login view', function() { it('shows a login view', function() {
@ -396,7 +406,7 @@ describe('loading:', function () {
it('shows a home page by default', function (done) { it('shows a home page by default', function (done) {
loadApp(); loadApp();
q.delay(1).then(() => { Promise.delay(1).then(() => {
// at this point, we're trying to do a guest registration; // at this point, we're trying to do a guest registration;
// we expect a spinner // we expect a spinner
assertAtLoadingSpinner(matrixChat); assertAtLoadingSpinner(matrixChat);
@ -429,7 +439,7 @@ describe('loading:', function () {
loadApp(); loadApp();
q.delay(1).then(() => { Promise.delay(1).then(() => {
// at this point, we're trying to do a guest registration; // at this point, we're trying to do a guest registration;
// we expect a spinner // we expect a spinner
assertAtLoadingSpinner(matrixChat); assertAtLoadingSpinner(matrixChat);
@ -464,7 +474,7 @@ describe('loading:', function () {
loadApp({ loadApp({
uriFragment: "#/room/!room:id" uriFragment: "#/room/!room:id"
}); });
q.delay(1).then(() => { Promise.delay(1).then(() => {
// at this point, we're trying to do a guest registration; // at this point, we're trying to do a guest registration;
// we expect a spinner // we expect a spinner
assertAtLoadingSpinner(matrixChat); assertAtLoadingSpinner(matrixChat);
@ -489,6 +499,76 @@ describe('loading:', function () {
expect(windowLocation.hash).toEqual("#/room/!room:id"); expect(windowLocation.hash).toEqual("#/room/!room:id");
}).done(done, done); }).done(done, done);
}); });
describe('Login as user', function() {
beforeEach(function() {
// first we have to load the homepage
loadApp();
httpBackend.when('POST', '/register').check(function(req) {
expect(req.queryParams.kind).toEqual('guest');
}).respond(200, {
user_id: "@guest:localhost",
access_token: "secret_token",
});
return httpBackend.flush().then(() => {
return awaitSyncingSpinner(matrixChat);
}).then(() => {
// we got a sync spinner - let the sync complete
return expectAndAwaitSync();
}).then(() => {
// once the sync completes, we should have a home page
ReactTestUtils.findRenderedComponentWithType(
matrixChat, sdk.getComponent('structures.HomePage'));
// we simulate a click on the 'login' button by firing off
// the relevant dispatch.
//
// XXX: is it an anti-pattern to access the react-sdk's
// dispatcher in this way? Is it better to find the login
// button and simulate a click? (we might have to arrange
// for it to be shown - it's not always, due to the
// collapsing left panel
dis.dispatch({ action: 'start_login' });
return awaitLoginComponent(matrixChat);
});
});
it('should give us a login page', function() {
expect(windowLocation.hash).toEqual("#/login");
// we expect a single <Login> component
ReactTestUtils.findRenderedComponentWithType(
matrixChat, sdk.getComponent('structures.login.Login')
);
});
it('should allow us to return to the app', function() {
const login = ReactTestUtils.findRenderedComponentWithType(
matrixChat, sdk.getComponent('structures.login.Login')
);
const linkText = 'Return to app';
const returnToApp = ReactTestUtils.scryRenderedDOMComponentsWithTag(
login, 'a').find((e) => e.innerText === linkText);
if (!returnToApp) {
throw new Error(`Couldn't find '${linkText}' link`);
}
ReactTestUtils.Simulate.click(returnToApp);
return Promise.delay(1).then(() => {
// we should be straight back into the home page
ReactTestUtils.findRenderedComponentWithType(
matrixChat, sdk.getComponent('structures.HomePage'));
});
});
});
}); });
describe('Token login:', function() { describe('Token login:', function() {
@ -497,7 +577,7 @@ describe('loading:', function () {
queryString: "?loginToken=secretToken&homeserver=https%3A%2F%2Fhomeserver&identityServer=https%3A%2F%2Fidserver", queryString: "?loginToken=secretToken&homeserver=https%3A%2F%2Fhomeserver&identityServer=https%3A%2F%2Fidserver",
}); });
q.delay(1).then(() => { Promise.delay(1).then(() => {
// we expect a spinner while we're logging in // we expect a spinner while we're logging in
assertAtLoadingSpinner(matrixChat); assertAtLoadingSpinner(matrixChat);
@ -513,12 +593,12 @@ describe('loading:', function () {
return httpBackend.flush(); return httpBackend.flush();
}).then(() => { }).then(() => {
// at this point, MatrixChat should fire onLoadCompleted, which // at this point, MatrixChat should fire onTokenLoginCompleted, which
// makes index.js reload the app. We're not going to attempt to // makes index.js reload the app. We're not going to attempt to
// simulate the reload - just check that things are left in the // simulate the reload - just check that things are left in the
// right state for the reloaded app. // right state for the reloaded app.
return loadCompletePromise; return tokenLoginCompletePromise;
}).then(() => { }).then(() => {
// check that the localstorage has been set up in such a way that // check that the localstorage has been set up in such a way that
// the reloaded app can pick up where we leave off. // the reloaded app can pick up where we leave off.
@ -530,7 +610,6 @@ describe('loading:', function () {
}); });
}); });
// check that we have a Login component, send a 'user:pass' login, // check that we have a Login component, send a 'user:pass' login,
// and await the HTTP requests. // and await the HTTP requests.
function completeLogin(matrixChat) { function completeLogin(matrixChat) {
@ -539,7 +618,6 @@ describe('loading:', function () {
matrixChat, sdk.getComponent('structures.login.Login')); matrixChat, sdk.getComponent('structures.login.Login'));
httpBackend.when('POST', '/login').check(function(req) { httpBackend.when('POST', '/login').check(function(req) {
console.log(req);
expect(req.data.type).toEqual('m.login.password'); expect(req.data.type).toEqual('m.login.password');
expect(req.data.identifier.type).toEqual('m.id.user'); expect(req.data.identifier.type).toEqual('m.id.user');
expect(req.data.identifier.user).toEqual('user'); expect(req.data.identifier.user).toEqual('user');
@ -553,7 +631,7 @@ describe('loading:', function () {
return httpBackend.flush().then(() => { return httpBackend.flush().then(() => {
// Wait for another trip around the event loop for the UI to update // Wait for another trip around the event loop for the UI to update
return q.delay(1); return Promise.delay(1);
}).then(() => { }).then(() => {
// we expect a spinner // we expect a spinner
ReactTestUtils.findRenderedComponentWithType( ReactTestUtils.findRenderedComponentWithType(
@ -589,7 +667,8 @@ function awaitSyncingSpinner(matrixChat, retryLimit, retryCount) {
retryCount = 0; retryCount = 0;
} }
if (matrixChat.state.loading || matrixChat.state.loggingIn) { if (matrixChat.state.view === VIEWS.LOADING ||
matrixChat.state.view === VIEWS.LOGGING_IN) {
console.log(Date.now() + " Awaiting sync spinner: still loading."); console.log(Date.now() + " Awaiting sync spinner: still loading.");
if (retryCount >= retryLimit) { if (retryCount >= retryLimit) {
throw new Error("MatrixChat still not loaded after " + throw new Error("MatrixChat still not loaded after " +
@ -597,7 +676,7 @@ function awaitSyncingSpinner(matrixChat, retryLimit, retryCount) {
} }
// loading can take quite a long time, because we delete the // loading can take quite a long time, because we delete the
// indexedDB store. // indexedDB store.
return q.delay(5).then(() => { return Promise.delay(5).then(() => {
return awaitSyncingSpinner(matrixChat, retryLimit, retryCount + 1); return awaitSyncingSpinner(matrixChat, retryLimit, retryCount + 1);
}); });
} }
@ -606,7 +685,7 @@ function awaitSyncingSpinner(matrixChat, retryLimit, retryCount) {
// state looks good, check the rendered output // state looks good, check the rendered output
assertAtSyncingSpinner(matrixChat); assertAtSyncingSpinner(matrixChat);
return q(); return Promise.resolve();
} }
function assertAtSyncingSpinner(matrixChat) { function assertAtSyncingSpinner(matrixChat) {
@ -628,14 +707,13 @@ function awaitRoomView(matrixChat, retryLimit, retryCount) {
retryCount = 0; retryCount = 0;
} }
if (matrixChat.state.loading || if (matrixChat.state.view !== VIEWS.LOGGED_IN || !matrixChat.state.ready) {
!(matrixChat.state.loggedIn && matrixChat.state.ready)) {
console.log(Date.now() + " Awaiting room view: not ready yet."); console.log(Date.now() + " Awaiting room view: not ready yet.");
if (retryCount >= retryLimit) { if (retryCount >= retryLimit) {
throw new Error("MatrixChat still not ready after " + throw new Error("MatrixChat still not ready after " +
retryCount + " tries"); retryCount + " tries");
} }
return q.delay(0).then(() => { return Promise.delay(0).then(() => {
return awaitRoomView(matrixChat, retryLimit, retryCount + 1); return awaitRoomView(matrixChat, retryLimit, retryCount + 1);
}); });
} }
@ -645,5 +723,11 @@ function awaitRoomView(matrixChat, retryLimit, retryCount) {
// state looks good, check the rendered output // state looks good, check the rendered output
ReactTestUtils.findRenderedComponentWithType( ReactTestUtils.findRenderedComponentWithType(
matrixChat, sdk.getComponent('structures.RoomView')); matrixChat, sdk.getComponent('structures.RoomView'));
return q(); return Promise.resolve();
}
function awaitLoginComponent(matrixChat, attempts) {
return MatrixReactTestUtils.waitForRenderedComponentWithType(
matrixChat, sdk.getComponent('structures.login.Login'), attempts,
);
} }

View File

@ -1,336 +0,0 @@
"use strict";
const q = require("q");
import expect from 'expect';
/**
* Construct a mock HTTP backend, heavily inspired by Angular.js.
* @constructor
*/
function HttpBackend() {
this.requests = [];
this.expectedRequests = [];
const self = this;
// the request function dependency that the SDK needs.
this.requestFn = function(opts, callback) {
const req = new Request(opts, callback);
console.log(`${Date.now()} HTTP backend received request: ${req}`);
self.requests.push(req);
const abort = function() {
const idx = self.requests.indexOf(req);
if (idx >= 0) {
console.log("Aborting HTTP request: %s %s", opts.method,
opts.uri);
self.requests.splice(idx, 1);
req.callback("aborted");
}
};
return {
abort: abort,
};
};
// very simplistic mapping from the whatwg fetch interface onto the request
// interface, so we can use the same mock backend for both.
this.fetchFn = function(input, init) {
init = init || {};
const requestOpts = {
uri: input,
method: init.method || 'GET',
body: init.body,
};
return new Promise((resolve, reject) => {
function callback(err, response, body) {
if (err) {
reject(err);
}
resolve({
ok: response.statusCode >= 200 && response.statusCode < 300,
json: () => body,
});
};
const req = new Request(requestOpts, callback);
console.log(`HTTP backend received request: ${req}`);
self.requests.push(req);
});
};
}
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.
* @param {integer=} waitTime The time (in ms) to wait for a request to happen.
* default: 100
*
* @return {Promise} resolves when there is nothing left to flush, with the
* number of requests flushed
*/
flush: function(path, numToFlush, waitTime) {
const defer = q.defer();
const self = this;
let flushed = 0;
if (waitTime === undefined) {
waitTime = 100;
}
function log(msg) {
console.log(`${Date.now()} flush[${path || ''}]: ${msg}`);
}
log("HTTP backend flushing... (path=" + path
+ " numToFlush=" + numToFlush
+ " waitTime=" + waitTime
+ ")",
);
const endTime = waitTime + Date.now();
const tryFlush = function() {
// if there's more real requests and more expected requests, flush 'em.
log(` trying to flush => reqs=[${self.requests}] ` +
`expected=[${self.expectedRequests}]`,
);
if (self._takeFromQueue(path)) {
// try again on the next tick.
flushed += 1;
if (numToFlush && flushed === numToFlush) {
log(`Flushed assigned amount: ${numToFlush}`);
defer.resolve(flushed);
} else {
log(` flushed. Trying for more.`);
setTimeout(tryFlush, 0);
}
} else if (flushed === 0 && Date.now() < endTime) {
// we may not have made the request yet, wait a generous amount of
// time before giving up.
log(` nothing to flush yet; waiting for requests.`);
setTimeout(tryFlush, 5);
} else {
if (flushed === 0) {
log("nothing to flush; giving up");
} else {
log(`no more flushes after flushing ${flushed} requests`);
}
defer.resolve(flushed);
}
};
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) {
let req = null;
let i;
let j;
let matchingReq = null;
let expectedReq = null;
let 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(`${Date.now()} responding to ${matchingReq.path}`);
let 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() {
const 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() {
const firstOutstandingExpectation = this.expectedRequests[0] || {};
expect(this.expectedRequests.length).toEqual(0,
"Expected to see HTTP request for " + 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) {
const pendingReq = new ExpectedRequest(method, path, data);
this.expectedRequests.push(pendingReq);
return pendingReq;
},
};
/**
* Represents the expectation of a request.
*
* <p>Includes the conditions to be matched against, the checks to be made,
* and the response to be returned.
*
* @constructor
* @param {string} method
* @param {string} path
* @param {object?} data
*/
function ExpectedRequest(method, path, data) {
this.method = method;
this.path = path;
this.data = data;
this.response = null;
this.checks = [];
}
ExpectedRequest.prototype = {
toString: function() {
return this.method + " " + this.path
},
/**
* 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,
};
},
};
/**
* Represents a request made by the app.
*
* @constructor
* @param {object} opts opts passed to request()
* @param {function} callback
*/
function Request(opts, callback) {
this.opts = opts;
this.callback = callback;
Object.defineProperty(this, 'method', {
get: function() {
return opts.method;
},
});
Object.defineProperty(this, 'path', {
get: function() {
return opts.uri;
},
});
Object.defineProperty(this, 'data', {
get: function() {
return opts.body;
},
});
Object.defineProperty(this, 'queryParams', {
get: function() {
return opts.qs;
},
});
Object.defineProperty(this, 'headers', {
get: function() {
return opts.headers || {};
},
});
}
Request.prototype = {
toString: function() {
return this.method + " " + this.path;
},
};
/**
* The HttpBackend class.
*/
module.exports = HttpBackend;

View File

@ -1,6 +1,6 @@
"use strict"; "use strict";
var q = require('q'); import Promise from 'bluebird';
/** /**
* Perform common actions before each test case, e.g. printing the test case * Perform common actions before each test case, e.g. printing the test case
@ -28,28 +28,33 @@ export function browserSupportsWebRTC() {
} }
export function deleteIndexedDB(dbName) { export function deleteIndexedDB(dbName) {
return new q.Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!window.indexedDB) { if (!window.indexedDB) {
resolve(); resolve();
return; return;
} }
console.log(`Removing indexeddb instance: ${dbName}`); const startTime = Date.now();
console.log(`${startTime}: Removing indexeddb instance: ${dbName}`);
const req = window.indexedDB.deleteDatabase(dbName); const req = window.indexedDB.deleteDatabase(dbName);
req.onblocked = () => { req.onblocked = () => {
console.log(`can't yet delete indexeddb because it is open elsewhere`); console.log(`${Date.now()}: can't yet delete indexeddb ${dbName} because it is open elsewhere`);
}; };
req.onerror = (ev) => { req.onerror = (ev) => {
reject(new Error( reject(new Error(
"unable to delete indexeddb: " + ev.target.error, `${Date.now()}: unable to delete indexeddb ${dbName}: ${ev.target.error}`,
)); ));
}; };
req.onsuccess = () => { req.onsuccess = () => {
console.log(`Removed indexeddb instance: ${dbName}`); const now = Date.now();
console.log(`${now}: Removed indexeddb instance: ${dbName} in ${now-startTime} ms`);
resolve(); resolve();
}; };
}).catch((e) => {
console.error(`${Date.now()}: Error removing indexeddb instance ${dbName}: ${e}`);
throw e;
}); });
} }