diff --git a/.babelrc b/.babelrc index 8c7b66269d..6ba0e0dae0 100644 --- a/.babelrc +++ b/.babelrc @@ -1,4 +1,4 @@ { "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"] } diff --git a/.gitignore b/.gitignore index 060ca6e934..2ad05012a0 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,6 @@ npm-debug.log electron/dist electron/pub -/.idea +**/.idea /config.json /src/component-index.js diff --git a/.travis.yml b/.travis.yml index e020ba7d15..94ed745cd8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 node_js: # 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 # extract-zip) don't work with it # - 5.x has been EOLed for nearly a year. - # - 6.x is the current 'LTS' version - # - 7.x is the current 'current' version (until October 2017) + # - 6.x is the active 'LTS' version + # - 7.x is no longer supported + # - 8.x is the current 'current' version (until October 2017) # # see: https://github.com/nodejs/LTS/ # @@ -16,6 +24,8 @@ node_js: - 6.3 - 6 - 7 +addons: + chrome: stable install: # clone the deps with depth 1: we know we will only ever need that one # commit. diff --git a/CHANGELOG.md b/CHANGELOG.md index cdba055144..a78d26e0bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,168 @@ +Changes in [0.12.1](https://github.com/vector-im/riot-web/releases/tag/v0.12.1) (2017-08-23) +============================================================================================ +[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.12.1-rc.1...v0.12.1) + + * [No changes] + +Changes in [0.12.1-rc.1](https://github.com/vector-im/riot-web/releases/tag/v0.12.1-rc.1) (2017-08-22) +====================================================================================================== +[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.12.0-rc.2...v0.12.1-rc.1) + + * Update from Weblate. + [\#4832](https://github.com/vector-im/riot-web/pull/4832) + * Misc styling fixes. + [\#4826](https://github.com/vector-im/riot-web/pull/4826) + * Show / Hide apps icons + [\#4774](https://github.com/vector-im/riot-web/pull/4774) + +Changes in [0.12.0-rc.1](https://github.com/vector-im/riot-web/releases/tag/v0.12.0-rc.1) (2017-08-16) +====================================================================================================== +[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.11.4...v0.12.0-rc.1) + + * Update from Weblate. + [\#4797](https://github.com/vector-im/riot-web/pull/4797) + * move focus-via-up/down cursors to LeftPanel + [\#4777](https://github.com/vector-im/riot-web/pull/4777) + * Remove userId property on RightPanel + [\#4775](https://github.com/vector-im/riot-web/pull/4775) + * Make member device info buttons fluid and stackable with flexbox + [\#4776](https://github.com/vector-im/riot-web/pull/4776) + * un-i18n Modal Analytics + [\#4688](https://github.com/vector-im/riot-web/pull/4688) + * Quote using innerText + [\#4773](https://github.com/vector-im/riot-web/pull/4773) + * Karma tweaks for riot-web + [\#4765](https://github.com/vector-im/riot-web/pull/4765) + * Fix typo with scripts/fetch-develop-deps.sh in Building From Source + [\#4764](https://github.com/vector-im/riot-web/pull/4764) + * Adjust CSS for optional avatars in pills + [\#4757](https://github.com/vector-im/riot-web/pull/4757) + * Fix crypto on develop + [\#4754](https://github.com/vector-im/riot-web/pull/4754) + * Fix signing key url in readme + [\#4464](https://github.com/vector-im/riot-web/pull/4464) + * update gitignore to allow .idea directory to exist in subdirs + [\#4749](https://github.com/vector-im/riot-web/pull/4749) + * tweak compact theme + [\#4665](https://github.com/vector-im/riot-web/pull/4665) + * Update draft-js from 0.10.1 to 0.11.0-alpha + [\#4740](https://github.com/vector-im/riot-web/pull/4740) + * electron support for mouse forward/back buttons in Windows + [\#4739](https://github.com/vector-im/riot-web/pull/4739) + * Update draft-js from 0.8.1 to 0.10.1 + [\#4730](https://github.com/vector-im/riot-web/pull/4730) + * Make pills, emoji translucent when sending + [\#4693](https://github.com/vector-im/riot-web/pull/4693) + * Widget permissions styling and icon + [\#4690](https://github.com/vector-im/riot-web/pull/4690) + * CSS required for composer autoscroll + [\#4682](https://github.com/vector-im/riot-web/pull/4682) + * CSS for group edit UI + [\#4608](https://github.com/vector-im/riot-web/pull/4608) + * Fix a couple of minor errors in the room list + [\#4671](https://github.com/vector-im/riot-web/pull/4671) + * Styling for beta testing icon. + [\#4584](https://github.com/vector-im/riot-web/pull/4584) + * Increase the timeout for clearing indexeddbs + [\#4650](https://github.com/vector-im/riot-web/pull/4650) + * Make some adjustments to mx_UserPill and mx_RoomPill + [\#4597](https://github.com/vector-im/riot-web/pull/4597) + * Apply CSS to
tags to distinguish them from each other + [\#4639](https://github.com/vector-im/riot-web/pull/4639) + * Use `catch` instead of `fail` to handle room tag error + [\#4643](https://github.com/vector-im/riot-web/pull/4643) + * CSS for decorated matrix.to links in the composer + [\#4583](https://github.com/vector-im/riot-web/pull/4583) + * Deflake the joining test + [\#4579](https://github.com/vector-im/riot-web/pull/4579) + * Bump react to 15.6 to fix build problems + [\#4577](https://github.com/vector-im/riot-web/pull/4577) + * Improve AppTile menu bar button styling. + [\#4567](https://github.com/vector-im/riot-web/pull/4567) + * Transform `async` functions to bluebird promises + [\#4572](https://github.com/vector-im/riot-web/pull/4572) + * use flushAllExpected in joining test + [\#4570](https://github.com/vector-im/riot-web/pull/4570) + * Switch riot-web to bluebird + [\#4565](https://github.com/vector-im/riot-web/pull/4565) + * loading tests: wait for login component + [\#4564](https://github.com/vector-im/riot-web/pull/4564) + * Remove CSS for the MessageComposerInputOld + [\#4568](https://github.com/vector-im/riot-web/pull/4568) + * Implement the focus_room_filter action + [\#4560](https://github.com/vector-im/riot-web/pull/4560) + * CSS for Rooms in Group View + [\#4530](https://github.com/vector-im/riot-web/pull/4530) + * more HomePage tweaks + [\#4557](https://github.com/vector-im/riot-web/pull/4557) + * Give HomePage an unmounted guard + [\#4556](https://github.com/vector-im/riot-web/pull/4556) + * Take RTE out of labs + [\#4500](https://github.com/vector-im/riot-web/pull/4500) + * CSS for Groups page + [\#4468](https://github.com/vector-im/riot-web/pull/4468) + * CSS for GroupView + [\#4442](https://github.com/vector-im/riot-web/pull/4442) + * remove unused class + [\#4525](https://github.com/vector-im/riot-web/pull/4525) + * Fix long words causing MessageComposer to widen + [\#4466](https://github.com/vector-im/riot-web/pull/4466) + * Add visual bell animation for RTE + [\#4516](https://github.com/vector-im/riot-web/pull/4516) + * Truncate auto-complete pills properly + [\#4502](https://github.com/vector-im/riot-web/pull/4502) + * Use chrome headless instead of phantomjs + [\#4512](https://github.com/vector-im/riot-web/pull/4512) + * Use external mock-request + [\#4489](https://github.com/vector-im/riot-web/pull/4489) + * fix Quote not closing contextual menu + [\#4443](https://github.com/vector-im/riot-web/pull/4443) + * Apply white-space: pre-wrap to mx_MEmoteBody + [\#4470](https://github.com/vector-im/riot-web/pull/4470) + * Add some style improvements to autocompletions + [\#4456](https://github.com/vector-im/riot-web/pull/4456) + * Styling for apps / widgets + [\#4447](https://github.com/vector-im/riot-web/pull/4447) + * Attempt to flush the rageshake logs on close + [\#4400](https://github.com/vector-im/riot-web/pull/4400) + * Update from Weblate. + [\#4401](https://github.com/vector-im/riot-web/pull/4401) + * improve update polling electron and provide a manual check for updates + button + [\#4176](https://github.com/vector-im/riot-web/pull/4176) + * Fix load failure in firefox when indexedDB is disabled + [\#4395](https://github.com/vector-im/riot-web/pull/4395) + * Change missed 'Redact' to 'Remove' in ImageView. + [\#4362](https://github.com/vector-im/riot-web/pull/4362) + * explicit convert to nativeImage to stabilise trayIcon on Windows [Electron] + [\#4355](https://github.com/vector-im/riot-web/pull/4355) + * Use _tJsx for PasswordNagBar (because it has ) + [\#4373](https://github.com/vector-im/riot-web/pull/4373) + * Clean up some log outputs from the integ tests + [\#4376](https://github.com/vector-im/riot-web/pull/4376) + * CSS for redeisng of password warning + [\#4367](https://github.com/vector-im/riot-web/pull/4367) + * Give _t to PasswordNagBar, add CSS for UserSettings password warning + [\#4366](https://github.com/vector-im/riot-web/pull/4366) + * Update from Weblate. + [\#4361](https://github.com/vector-im/riot-web/pull/4361) + * Update from Weblate. + [\#4360](https://github.com/vector-im/riot-web/pull/4360) + * Test 'return-to-app' functionality + [\#4352](https://github.com/vector-im/riot-web/pull/4352) + * Update from Weblate. + [\#4354](https://github.com/vector-im/riot-web/pull/4354) + * onLoadCompleted is now onTokenLoginCompleted + [\#4335](https://github.com/vector-im/riot-web/pull/4335) + * Tweak tests to match updates to matrixchat + [\#4325](https://github.com/vector-im/riot-web/pull/4325) + * Update from Weblate. + [\#4346](https://github.com/vector-im/riot-web/pull/4346) + * change dispatcher forward_event signature + [\#4337](https://github.com/vector-im/riot-web/pull/4337) + * Add border on hover for code blocks + [\#4259](https://github.com/vector-im/riot-web/pull/4259) + Changes in [0.11.4](https://github.com/vector-im/riot-web/releases/tag/v0.11.4) (2017-06-22) ============================================================================================ [Full Changelog](https://github.com/vector-im/riot-web/compare/v0.11.3...v0.11.4) @@ -5,7 +170,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 background indexedb worker was disabled, failures to open indexeddb 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. Changes in [0.11.3](https://github.com/vector-im/riot-web/releases/tag/v0.11.3) (2017-06-20) diff --git a/README.md b/README.md index 89f2148f5e..271382030e 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ released version of Riot: 1. Enter the URL into your browser and log into Riot! Releases are signed by PGP, and can be checked against the public key -at https://riot.im/packages/keys/riot-master.asc +at https://riot.im/packages/keys/riot.asc Note that Chrome does not allow microphone or webcam access for sites served over http (except localhost), so for working VoIP you will need to serve Riot @@ -62,7 +62,7 @@ to build. 1. If you're using the `develop` branch, install the develop versions of the dependencies, as the released ones will be too old: ``` - scripts/fetch-develop-deps.sh + scripts/fetch-develop.deps.sh ``` Whenever you git pull on riot-web you will also probably need to force an update to these dependencies - the simplest way is to re-run the script, but you can also @@ -81,7 +81,7 @@ to build. npm run build ``` 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 up-to-date. Or just use https://riot.im/develop - the continuous integration 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. `npm start` 1. Wait a few seconds for the initial build to finish; you should see something like: - ``` Hash: b0af76309dd56d7275c8 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. 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). [](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 =============== diff --git a/electron_app/package.json b/electron_app/package.json index 2c6e62f2f5..0a3b092e77 100644 --- a/electron_app/package.json +++ b/electron_app/package.json @@ -2,7 +2,7 @@ "name": "riot-web", "productName": "Riot", "main": "src/electron-main.js", - "version": "0.11.4", + "version": "0.12.1", "description": "A feature-rich client for Matrix.org", "author": "Vector Creations Ltd.", "dependencies": { diff --git a/electron_app/src/electron-main.js b/electron_app/src/electron-main.js index 3491ce0fa3..ce5ac38413 100644 --- a/electron_app/src/electron-main.js +++ b/electron_app/src/electron-main.js @@ -29,6 +29,7 @@ const AutoLaunch = require('auto-launch'); const tray = require('./tray'); const vectorMenu = require('./vectormenu'); const webContentsHandler = require('./webcontents-handler'); +const updater = require('./updater'); const windowStateKeeper = require('electron-window-state'); @@ -46,69 +47,9 @@ try { // 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 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 // stack traces in popup dialogs, which is terrible (which @@ -120,8 +61,6 @@ process.on('uncaughtException', function(error) { console.log('Unhandled exception', error); }); -electron.ipcMain.on('install_update', installUpdate); - let focusHandlerAttached = false; electron.ipcMain.on('setBadgeCount', function(ev, count) { electron.app.setBadgeCount(count); @@ -233,7 +172,7 @@ electron.app.on('ready', () => { if (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 { console.log('No update_base_url is defined: auto update is disabled'); } @@ -246,7 +185,7 @@ electron.app.on('ready', () => { defaultHeight: 768, }); - mainWindow = new electron.BrowserWindow({ + mainWindow = global.mainWindow = new electron.BrowserWindow({ icon: iconPath, show: false, autoHideMenuBar: true, @@ -264,7 +203,7 @@ electron.app.on('ready', () => { mainWindow.hide(); // Create trayIcon icon - tray.create(mainWindow, { + tray.create({ icon_path: iconPath, brand: vectorConfig.brand || 'Riot', }); @@ -276,10 +215,10 @@ electron.app.on('ready', () => { } mainWindow.on('closed', () => { - mainWindow = null; + mainWindow = global.mainWindow = null; }); 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 // (this is generally how single-window Mac apps // behave, eg. Mail.app) @@ -289,6 +228,17 @@ electron.app.on('ready', () => { } }); + if (process.platform === 'win32') { + // Handle forward/backward mouse buttons in Windows + mainWindow.on('app-command', (e, cmd) => { + if (cmd === 'browser-backward' && mainWindow.webContents.canGoBack()) { + mainWindow.webContents.goBack(); + } else if (cmd === 'browser-forward' && mainWindow.webContents.canGoForward()) { + mainWindow.webContents.goForward(); + } + }); + } + webContentsHandler(mainWindow.webContents); mainWindowState.manage(mainWindow); }); @@ -302,7 +252,10 @@ electron.app.on('activate', () => { }); 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 diff --git a/electron_app/src/tray.js b/electron_app/src/tray.js index 039e7133fa..bd07d7d433 100644 --- a/electron_app/src/tray.js +++ b/electron_app/src/tray.js @@ -26,17 +26,17 @@ exports.hasTray = function hasTray() { return (trayIcon !== null); }; -exports.create = function(win, config) { +exports.create = function(config) { // no trays on darwin if (process.platform === 'darwin' || trayIcon) return; const toggleWin = function() { - if (win.isVisible() && !win.isMinimized()) { - win.hide(); + if (global.mainWindow.isVisible() && !global.mainWindow.isMinimized()) { + global.mainWindow.hide(); } else { - if (win.isMinimized()) win.restore(); - if (!win.isVisible()) win.show(); - win.focus(); + if (global.mainWindow.isMinimized()) global.mainWindow.restore(); + if (!global.mainWindow.isVisible()) global.mainWindow.show(); + 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.setContextMenu(contextMenu); trayIcon.on('click', toggleWin); let lastFavicon = null; - win.webContents.on('page-favicon-updated', async function(ev, favicons) { - let newFavicon = config.icon_path; - if (favicons && favicons.length > 0 && favicons[0].startsWith('data:')) { - newFavicon = favicons[0]; + global.mainWindow.webContents.on('page-favicon-updated', async function(ev, favicons) { + if (!favicons || favicons.length <= 0 || !favicons[0].startsWith('data:')) { + if (lastFavicon !== null) { + win.setIcon(defaultIcon); + trayIcon.setImage(defaultIcon); + lastFavicon = null; + } + return; } // No need to change, shortcut - if (newFavicon === lastFavicon) return; - lastFavicon = newFavicon; + if (favicons[0] === lastFavicon) return; + lastFavicon = favicons[0]; - // if its not default we have to construct into nativeImage - if (newFavicon !== config.icon_path) { - newFavicon = nativeImage.createFromDataURL(favicons[0]); + let newFavicon = nativeImage.createFromDataURL(favicons[0]); - if (process.platform === 'win32') { - try { - const icoPath = path.join(app.getPath('temp'), 'win32_riot_icon.ico') - const icoBuf = await pngToIco(newFavicon.toPNG()); - fs.writeFileSync(icoPath, icoBuf); - newFavicon = icoPath; - } catch (e) {console.error(e);} + // Windows likes ico's too much. + if (process.platform === 'win32') { + try { + const icoPath = path.join(app.getPath('temp'), 'win32_riot_icon.ico'); + fs.writeFileSync(icoPath, await pngToIco(newFavicon.toPNG())); + newFavicon = nativeImage.createFromPath(icoPath); + } catch (e) { + console.error("Failed to make win32 ico", e); } } 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); }); }; diff --git a/electron_app/src/updater.js b/electron_app/src/updater.js new file mode 100644 index 0000000000..49fa4e0419 --- /dev/null +++ b/electron_app/src/updater.js @@ -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); +}); diff --git a/karma.conf.js b/karma.conf.js index 1e04366313..3b415b1ae6 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -84,13 +84,23 @@ module.exports = function (config) { // available preprocessors: // https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { - '{src,test}/**/*.js': ['webpack'], + '{src,test}/**/*.js': ['webpack', 'sourcemap'], }, // test results reporter to use - // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['progress', 'junit'], + reporters: ['logcapture', 'spec', 'junit', 'summary'], + + specReporter: { + suppressErrorSummary: false, // do print error summary + suppressFailed: false, // do print information about failed tests + suppressPassed: false, // do print information about passed tests + showSpecTiming: true, // print the time elapsed for each spec + }, + + client: { + captureLogs: true, + }, // web server port port: 9876, @@ -113,8 +123,23 @@ module.exports = function (config) { browsers: [ 'Chrome', //'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 // if true, Karma captures browsers, runs the tests and exits // singleRun: false, diff --git a/package.json b/package.json index f51290061d..44b1dc5948 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "riot-web", "productName": "Riot", "main": "electron_app/src/electron-main.js", - "version": "0.11.4", + "version": "0.12.1", "description": "A feature-rich client for Matrix.org", "author": "Vector Creations Ltd.", "repository": { @@ -31,8 +31,8 @@ "build:res": "node scripts/copy-res.js", "build:modernizr": "modernizr -c .modernizr.json -d src/vector/modernizr.js", "build:compile": "npm run reskindex && babel --source-maps -d lib src", - "build:bundle": "cross-env NODE_ENV=production webpack -p --progress", - "build:bundle:dev": "webpack --optimize-occurence-order --progress", + "build:bundle": "cross-env NODE_ENV=production webpack -p --progress --bail", + "build:bundle:dev": "webpack --optimize-occurence-order --progress --bail", "build:electron": "npm run clean && npm run build && npm run install:electron && build -wml --ia32 --x64", "build": "npm run reskindex && npm run build:res && npm run build:bundle", "build:dev": "npm run reskindex && npm run build:res && npm run build:bundle:dev", @@ -48,15 +48,16 @@ "lintall": "eslint src/ test/", "clean": "rimraf lib webapp electron_app/dist", "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" }, "dependencies": { "babel-polyfill": "^6.5.0", "babel-runtime": "^6.11.6", + "bluebird": "^3.5.0", "browser-request": "^0.3.3", "classnames": "^2.1.2", - "draft-js": "^0.8.1", + "draft-js": "^0.11.0-alpha", "extract-text-webpack-plugin": "^0.9.1", "favico.js": "^0.3.10", "filesize": "3.5.6", @@ -65,15 +66,14 @@ "gfm.css": "^1.1.1", "highlight.js": "^9.0.0", "linkifyjs": "^2.1.3", - "matrix-js-sdk": "0.7.13", - "matrix-react-sdk": "0.9.7", + "matrix-js-sdk": "0.8.1", + "matrix-react-sdk": "0.10.1", "modernizr": "^3.1.0", "pako": "^1.0.5", - "q": "^1.4.1", - "react": "^15.4.0", + "react": "^15.6.0", "react-dnd": "^2.1.4", "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", "sanitize-html": "^1.11.1", "text-encoding-utf-8": "^1.0.1", @@ -88,7 +88,7 @@ "babel-eslint": "^6.1.0", "babel-loader": "^6.2.5", "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-object-rest-spread": "^6.16.0", "babel-plugin-transform-runtime": "^6.15.0", @@ -114,18 +114,22 @@ "fs-extra": "^0.30.0", "html-webpack-plugin": "^2.24.0", "json-loader": "^0.5.3", - "karma": "^0.13.22", + "karma": "^1.7.0", "karma-chrome-launcher": "^0.2.3", "karma-cli": "^0.1.2", "karma-junit-reporter": "^0.4.1", + "karma-logcapture-reporter": "0.0.1", "karma-mocha": "^0.2.2", - "karma-phantomjs-launcher": "^1.0.0", + "karma-sourcemap-loader": "^0.3.7", + "karma-spec-reporter": "0.0.31", + "karma-summary-reporter": "^1.3.3", "karma-webpack": "^1.7.0", + "matrix-mock-request": "^1.2.0", + "matrix-react-test-utils": "^0.2.0", "minimist": "^1.2.0", "mkdirp": "^0.5.1", "mocha": "^2.4.5", "parallelshell": "^1.2.0", - "phantomjs-prebuilt": "^2.1.7", "postcss-extend": "^1.0.5", "postcss-import": "^9.0.0", "postcss-loader": "^1.2.2", @@ -135,7 +139,7 @@ "postcss-simple-vars": "^3.0.0", "postcss-strip-inline-comments": "^0.1.5", "react-addons-perf": "^15.4.0", - "react-addons-test-utils": "^15.4.0", + "react-addons-test-utils": "^15.6.0", "rimraf": "^2.4.3", "source-map-loader": "^0.1.5", "webpack": "^1.12.14", diff --git a/release.sh b/release.sh index 8ae307f7e2..136750181e 100755 --- a/release.sh +++ b/release.sh @@ -11,7 +11,7 @@ cd `dirname $0` for i in matrix-js-sdk matrix-react-sdk do - depver=`cat package.json | jq -r .dependencies.\"$i\"` + depver=`cat package.json | jq -r .dependencies[\"$i\"]` latestver=`npm show $i version` if [ "$depver" != "$latestver" ] then diff --git a/scripts/copy-res.js b/scripts/copy-res.js index e8f6684d21..fa52492e00 100755 --- a/scripts/copy-res.js +++ b/scripts/copy-res.js @@ -9,24 +9,27 @@ // This could readily be automated, but it's nice to explicitly // control when we languages are available. const INCLUDE_LANGS = [ + {'value': 'da', 'label': 'Dansk'}, + {'value': 'de_DE', 'label': 'Deutsch'}, {'value': 'en_EN', 'label': 'English'}, {'value': 'en_US', 'label': 'English (US)'}, - {'value': 'da', 'label': 'Dansk'}, {'value': 'el', 'label': 'Ελληνικά'}, {'value': 'eo', 'label': 'Esperanto'}, - {'value': 'nl', 'label': 'Nederlands'}, - {'value': 'de_DE', 'label': 'Deutsch'}, + {'value': 'es', 'label': 'Español'}, + {'value': 'eu', 'label': 'Euskal'}, {'value': 'fr', 'label': 'Français'}, {'value': 'hu', 'label': 'Magyar'}, {'value': 'ko', 'label': '한국어'}, + {'value': 'lv', 'label': 'Latviešu'}, {'value': 'nb_NO', 'label': 'Norwegian Bokmål'}, + {'value': 'nl', 'label': 'Nederlands'}, {'value': 'pl', 'label': 'Polski'}, {'value': 'pt', 'label': 'Português'}, {'value': 'pt_BR', 'label': 'Português do Brasil'}, {'value': 'ru', 'label': 'Русский'}, {'value': 'sv', 'label': 'Svenska'}, - {'value': 'es', 'label': 'Español'}, {'value': 'th', 'label': 'ไทย'}, + {'value': 'te', 'label': 'తెలుగు'}, {'value': 'tr', 'label': 'Türk'}, {'value': 'zh_Hans', 'label': '简体中文'}, // simplified chinese {'value': 'zh_Hant', 'label': '繁體中文'}, // traditional chinese diff --git a/scripts/deploy.py b/scripts/deploy.py index c96b46e81f..cc350e4c9a 100755 --- a/scripts/deploy.py +++ b/scripts/deploy.py @@ -63,7 +63,8 @@ class Deployer: self.packages_path = "." self.bundles_path = None 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 def deploy(self, tarball, extract_path): @@ -95,11 +96,12 @@ class Deployer: print ("Extracted into: %s" % extracted_dir) - if self.config_location: - create_relative_symlink( - target=self.config_location, - linkname=os.path.join(extracted_dir, 'config.json') - ) + if self.config_locations: + for config_filename, config_loc in self.config_locations.iteritems(): + create_relative_symlink( + target=config_loc, + linkname=os.path.join(extracted_dir, config_filename) + ) if self.bundles_path: extracted_bundles = os.path.join(extracted_dir, 'bundles') @@ -178,6 +180,8 @@ if __name__ == "__main__": deployer.packages_path = args.packages_dir deployer.bundles_path = args.bundles_dir deployer.should_clean = args.clean - deployer.config_location = args.config + deployer.config_locations = { + "config.json": args.config, + } deployer.deploy(args.tarball, args.extract_path) diff --git a/scripts/fetch-develop.deps.sh b/scripts/fetch-develop.deps.sh index 4fa1a4a22c..e2d40341a0 100755 --- a/scripts/fetch-develop.deps.sh +++ b/scripts/fetch-develop.deps.sh @@ -49,42 +49,47 @@ function dodep() { [ "$curbranch" != 'develop' ] && clone $org $repo develop } || return $? - ( - cd $repo - echo "$repo set to branch "`git rev-parse --abbrev-ref HEAD` - ) + echo "$repo set to branch "`git -C "$repo" rev-parse --abbrev-ref HEAD` mkdir -p node_modules rm -r "node_modules/$repo" 2>/dev/null || true ln -sv "../$repo" node_modules/ + + ( + cd $repo + npm install + ) } +############################## + echo -en 'travis_fold:start:matrix-js-sdk\r' echo 'Setting up matrix-js-sdk' dodep matrix-org matrix-js-sdk -( - cd node_modules/matrix-js-sdk - npm install -) echo -en 'travis_fold:end:matrix-js-sdk\r' +############################## + echo -en 'travis_fold:start:matrix-react-sdk\r' echo 'Setting up matrix-react-sdk' dodep matrix-org matrix-react-sdk -mkdir -p node_modules/matrix-react-sdk/node_modules +# replace the version of js-sdk that got pulled into react-sdk with a symlink +# to our version. Make sure to do this *after* doing 'npm i' in react-sdk, +# otherwise npm helpfully moves another-json from matrix-js-sdk/node_modules +# into matrix-react-sdk/node_modules. +# +# (note this matches the instructions in the README.) +rm -r node_modules/matrix-react-sdk/node_modules/matrix-js-sdk ln -s ../../matrix-js-sdk node_modules/matrix-react-sdk/node_modules/ -( - cd node_modules/matrix-react-sdk - npm install -) - echo -en 'travis_fold:end:matrix-react-sdk\r' +############################## + # Link the reskindex binary in place: if we used npm link, # npm would do this for us, but we don't because we'd have # to define the npm prefix somewhere so it could put the diff --git a/scripts/jenkins.sh b/scripts/jenkins.sh index 4f2e940564..7b5b4c8e2e 100755 --- a/scripts/jenkins.sh +++ b/scripts/jenkins.sh @@ -8,8 +8,11 @@ nvm use 6 set -x -# check out corresponding branches of dependencies -`dirname $0`/fetch-develop.deps.sh +# check out corresponding branches of dependencies. +# +# clone the deps with depth 1: we know we will only ever need that one +# commit. +`dirname $0`/fetch-develop.deps.sh --depth 1 npm install diff --git a/scripts/redeploy.py b/scripts/redeploy.py index 598f6c5265..e10a48c008 100755 --- a/scripts/redeploy.py +++ b/scripts/redeploy.py @@ -13,6 +13,7 @@ from __future__ import print_function import json, requests, tarfile, argparse, os, errno import time +import traceback from urlparse import urljoin from flask import Flask, jsonify, request, abort @@ -124,6 +125,7 @@ def fetch_jenkins_build(job_name, build_num): try: extracted_dir = deploy_tarball(tar_gz_url, build_dir) except DeployException as e: + traceback.print_exc() abort(400, e.message) create_symlink(source=extracted_dir, linkname=arg_symlink) @@ -185,10 +187,16 @@ if __name__ == "__main__": 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( - "--config", dest="config", help=( - "Write a symlink to config.json in the extracted tarball. \ - To this location." + "--config", action="append", dest="configs", + type=lambda kv: kv.split("=", 1) if "=" in kv else _raise(Exception("Missing =")), help=( + "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( @@ -212,7 +220,8 @@ if __name__ == "__main__": deployer = Deployer() deployer.bundles_path = args.bundles_dir 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 # 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) else: 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, arg_extract_path, " (clean after)" if deployer.should_clean else "", arg_symlink, arg_jenkins_url, - deployer.config_location, + deployer.config_locations, ) ) app.run(host="0.0.0.0", port=args.port, debug=True) diff --git a/src/VectorConferenceHandler.js b/src/VectorConferenceHandler.js index f34a7b732b..933f59937e 100644 --- a/src/VectorConferenceHandler.js +++ b/src/VectorConferenceHandler.js @@ -16,7 +16,7 @@ limitations under the License. "use strict"; -var q = require("q"); +import Promise from 'bluebird'; var Matrix = require("matrix-js-sdk"); var Room = Matrix.Room; 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 var groupRoom = this.client.getRoom(this.groupRoomId); if (!groupRoom) { - return q.reject("Bad group room ID"); + return Promise.reject("Bad group room ID"); } var member = groupRoom.getMember(this.confUserId); if (member && member.membership === "join") { - return q(); + return Promise.resolve(); } return this.client.invite(this.groupRoomId, this.confUserId); }; @@ -75,7 +75,7 @@ ConferenceCall.prototype._getConferenceUserRoom = function() { } } if (confRoom) { - return q(confRoom); + return Promise.resolve(confRoom); } return this.client.createRoom({ preset: "private_chat", diff --git a/src/components/structures/HomePage.js b/src/components/structures/HomePage.js index 2311cc1f30..bdba55eb0e 100644 --- a/src/components/structures/HomePage.js +++ b/src/components/structures/HomePage.js @@ -52,6 +52,8 @@ module.exports = React.createClass({ }, componentWillMount: function() { + this._unmounted = false; + if (this.props.teamToken && this.props.teamServerUrl) { this.setState({ iframeSrc: `${this.props.teamServerUrl}/static/${this.props.teamToken}/home.html` @@ -67,9 +69,14 @@ module.exports = React.createClass({ request( { method: "GET", url: src }, (err, response, body) => { + if (this._unmounted) { + return; + } + if (err || response.status < 200 || response.status >= 300) { - console.log(err); - this.setState({ page: "Couldn't load home page" }); + console.warn(`Error loading home page: ${err}`); + this.setState({ page: _t("Couldn't load home page") }); + return; } 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() { if (this.state.iframeSrc) { return ( diff --git a/src/components/structures/LeftPanel.js b/src/components/structures/LeftPanel.js index 77338404fa..4539df1ffa 100644 --- a/src/components/structures/LeftPanel.js +++ b/src/components/structures/LeftPanel.js @@ -16,17 +16,16 @@ limitations under the License. 'use strict'; -var React = require('react'); -var DragDropContext = require('react-dnd').DragDropContext; -var HTML5Backend = require('react-dnd-html5-backend'); -var sdk = require('matrix-react-sdk') -var dis = require('matrix-react-sdk/lib/dispatcher'); +import React from 'react'; +import { DragDropContext } from 'react-dnd'; +import HTML5Backend from 'react-dnd-html5-backend'; +import KeyCode from 'matrix-react-sdk/lib/KeyCode'; +import sdk from 'matrix-react-sdk'; +import dis from 'matrix-react-sdk/lib/dispatcher'; import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg'; - -var VectorConferenceHandler = require('../../VectorConferenceHandler'); -var CallHandler = require("matrix-react-sdk/lib/CallHandler"); - +import CallHandler from 'matrix-react-sdk/lib/CallHandler'; import AccessibleButton from 'matrix-react-sdk/lib/components/views/elements/AccessibleButton'; +import VectorConferenceHandler from '../../VectorConferenceHandler'; var LeftPanel = React.createClass({ displayName: 'LeftPanel', @@ -42,6 +41,10 @@ var LeftPanel = React.createClass({ }; }, + componentWillMount: function() { + this.focusedElement = null; + }, + componentDidMount: function() { this.dispatcherRef = dis.register(this.onAction); }, @@ -64,6 +67,91 @@ var LeftPanel = React.createClass({ } }, + _onFocus: function(ev) { + this.focusedElement = ev.target; + }, + + _onBlur: function(ev) { + this.focusedElement = null; + }, + + _onKeyDown: function(ev) { + if (!this.focusedElement) return; + let handled = false; + + switch (ev.keyCode) { + case KeyCode.UP: + this._onMoveFocus(true); + handled = true; + break; + case KeyCode.DOWN: + this._onMoveFocus(false); + handled = true; + break; + } + + if (handled) { + ev.stopPropagation(); + ev.preventDefault(); + } + }, + + _onMoveFocus: function(up) { + var element = this.focusedElement; + + // unclear why this isn't needed + // var descending = (up == this.focusDirection) ? this.focusDescending : !this.focusDescending; + // this.focusDirection = up; + + var descending = false; // are we currently descending or ascending through the DOM tree? + var classes; + + do { + var child = up ? element.lastElementChild : element.firstElementChild; + var sibling = up ? element.previousElementSibling : element.nextElementSibling; + + if (descending) { + if (child) { + element = child; + } + else if (sibling) { + element = sibling; + } + else { + descending = false; + element = element.parentElement; + } + } + else { + if (sibling) { + element = sibling; + descending = true; + } + else { + element = element.parentElement; + } + } + + if (element) { + classes = element.classList; + if (classes.contains("mx_LeftPanel")) { // we hit the top + element = up ? element.lastElementChild : element.firstElementChild; + descending = true; + } + } + + } while(element && !( + classes.contains("mx_RoomTile") || + classes.contains("mx_SearchBox_search") || + classes.contains("mx_RoomSubList_ellipsis"))); + + if (element) { + element.focus(); + this.focusedElement = element; + this.focusedDescending = descending; + } + }, + _recheckCallElement: function(selectedRoomId) { // if we aren't viewing a room with an ongoing call, but there is an // active call, show the call element - we need to do this to make @@ -126,7 +214,8 @@ var LeftPanel = React.createClass({ } return ( -