diff --git a/.travis.yml b/.travis.yml index 9a8f804644..a405b9ef35 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,5 +5,6 @@ install: - npm install - (cd node_modules/matrix-js-sdk && npm install) script: - - npm run test - - ./.travis-test-riot.sh + # don't run the riot tests unless the react-sdk tests pass, otherwise + # the output is confusing. + - npm run test && ./.travis-test-riot.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b9ecdb325..870b42ecfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,178 @@ +Changes in [0.9.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.1) (2017-06-02) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.9.0...v0.9.1) + + * Update from Weblate. + [\#1012](https://github.com/matrix-org/matrix-react-sdk/pull/1012) + * typo, missing import and mis-casing + [\#1014](https://github.com/matrix-org/matrix-react-sdk/pull/1014) + * Update from Weblate. + [\#1010](https://github.com/matrix-org/matrix-react-sdk/pull/1010) + +Changes in [0.9.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.0) (2017-06-02) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.9.0-rc.2...v0.9.0) + + * sync pt with pt_BR + [\#1009](https://github.com/matrix-org/matrix-react-sdk/pull/1009) + * Update from Weblate. + [\#1008](https://github.com/matrix-org/matrix-react-sdk/pull/1008) + * Update from Weblate. + [\#1003](https://github.com/matrix-org/matrix-react-sdk/pull/1003) + * allow hiding redactions, restoring old behaviour + [\#1004](https://github.com/matrix-org/matrix-react-sdk/pull/1004) + * Add missing translations + [\#1005](https://github.com/matrix-org/matrix-react-sdk/pull/1005) + +Changes in [0.9.0-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.0-rc.2) (2017-06-02) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.9.0-rc.1...v0.9.0-rc.2) + + * Update from Weblate. + [\#1002](https://github.com/matrix-org/matrix-react-sdk/pull/1002) + * webrtc config electron + [\#850](https://github.com/matrix-org/matrix-react-sdk/pull/850) + * enable useCompactLayout user setting an add a class when it's enabled + [\#986](https://github.com/matrix-org/matrix-react-sdk/pull/986) + * Update from Weblate. + [\#987](https://github.com/matrix-org/matrix-react-sdk/pull/987) + * Translation fixes for everything but src/components + [\#990](https://github.com/matrix-org/matrix-react-sdk/pull/990) + * Fix tests + [\#1001](https://github.com/matrix-org/matrix-react-sdk/pull/1001) + * Fix tests for PR #989 + [\#999](https://github.com/matrix-org/matrix-react-sdk/pull/999) + * Revert "Revert "add labels to language picker"" + [\#1000](https://github.com/matrix-org/matrix-react-sdk/pull/1000) + * maybe fixxy [Electron] external thing? + [\#997](https://github.com/matrix-org/matrix-react-sdk/pull/997) + * travisci: Don't run the riot-web tests if the react-sdk tests fail + [\#992](https://github.com/matrix-org/matrix-react-sdk/pull/992) + * Support 12hr time on DateSeparator + [\#991](https://github.com/matrix-org/matrix-react-sdk/pull/991) + * Revert "add labels to language picker" + [\#994](https://github.com/matrix-org/matrix-react-sdk/pull/994) + * Call MatrixClient.clearStores on logout + [\#983](https://github.com/matrix-org/matrix-react-sdk/pull/983) + * Matthew/room avatar event + [\#988](https://github.com/matrix-org/matrix-react-sdk/pull/988) + * add labels to language picker + [\#989](https://github.com/matrix-org/matrix-react-sdk/pull/989) + * Update from Weblate. + [\#981](https://github.com/matrix-org/matrix-react-sdk/pull/981) + +Changes in [0.9.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.0-rc.1) (2017-06-01) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.8.9...v0.9.0-rc.1) + + * Fix rare case where presence duration is undefined + [\#982](https://github.com/matrix-org/matrix-react-sdk/pull/982) + * add concept of platform handling loudNotifications (bings/pings/whatHaveYou) + [\#985](https://github.com/matrix-org/matrix-react-sdk/pull/985) + * Fixes to i18n code + [\#984](https://github.com/matrix-org/matrix-react-sdk/pull/984) + * Update from Weblate. + [\#978](https://github.com/matrix-org/matrix-react-sdk/pull/978) + * Add partial support for RTL languages + [\#955](https://github.com/matrix-org/matrix-react-sdk/pull/955) + * Added two strings to translate + [\#975](https://github.com/matrix-org/matrix-react-sdk/pull/975) + * Update from Weblate. + [\#976](https://github.com/matrix-org/matrix-react-sdk/pull/976) + * Update from Weblate. + [\#974](https://github.com/matrix-org/matrix-react-sdk/pull/974) + * Initial Electron Settings - for Auto Launch + [\#920](https://github.com/matrix-org/matrix-react-sdk/pull/920) + * Fix missing string in the room settings + [\#973](https://github.com/matrix-org/matrix-react-sdk/pull/973) + * fix error in i18n string + [\#972](https://github.com/matrix-org/matrix-react-sdk/pull/972) + * Update from Weblate. + [\#970](https://github.com/matrix-org/matrix-react-sdk/pull/970) + * Support 12hr time in full date + [\#971](https://github.com/matrix-org/matrix-react-sdk/pull/971) + * Add _tJsx() + [\#968](https://github.com/matrix-org/matrix-react-sdk/pull/968) + * Update from Weblate. + [\#966](https://github.com/matrix-org/matrix-react-sdk/pull/966) + * Remove space between time and AM/PM + [\#969](https://github.com/matrix-org/matrix-react-sdk/pull/969) + * Piwik Analytics + [\#948](https://github.com/matrix-org/matrix-react-sdk/pull/948) + * Update from Weblate. + [\#965](https://github.com/matrix-org/matrix-react-sdk/pull/965) + * Improve ChatInviteDialog perf by ditching fuse, using indexOf and + lastActiveTs() + [\#960](https://github.com/matrix-org/matrix-react-sdk/pull/960) + * Say "X removed the room name" instead of showing nothing + [\#958](https://github.com/matrix-org/matrix-react-sdk/pull/958) + * roomview/roomheader fixes + [\#959](https://github.com/matrix-org/matrix-react-sdk/pull/959) + * Update from Weblate. + [\#953](https://github.com/matrix-org/matrix-react-sdk/pull/953) + * fix i18n in a situation where navigator.languages=[] + [\#956](https://github.com/matrix-org/matrix-react-sdk/pull/956) + * `t_` -> `_t` fix typo + [\#957](https://github.com/matrix-org/matrix-react-sdk/pull/957) + * Change redact -> remove for clarity + [\#831](https://github.com/matrix-org/matrix-react-sdk/pull/831) + * Update from Weblate. + [\#950](https://github.com/matrix-org/matrix-react-sdk/pull/950) + * fix mis-linting - missed it in code review :( + [\#952](https://github.com/matrix-org/matrix-react-sdk/pull/952) + * i18n fixes + [\#951](https://github.com/matrix-org/matrix-react-sdk/pull/951) + * Message Forwarding + [\#812](https://github.com/matrix-org/matrix-react-sdk/pull/812) + * don't focus_composer on window focus + [\#944](https://github.com/matrix-org/matrix-react-sdk/pull/944) + * Fix vector-im/riot-web#4042 + [\#947](https://github.com/matrix-org/matrix-react-sdk/pull/947) + * import _t, drop two unused imports + [\#946](https://github.com/matrix-org/matrix-react-sdk/pull/946) + * Fix punctuation in TextForEvent to be i18n'd consistently + [\#945](https://github.com/matrix-org/matrix-react-sdk/pull/945) + * actually wire up alwaysShowTimestamps + [\#940](https://github.com/matrix-org/matrix-react-sdk/pull/940) + * Update from Weblate. + [\#943](https://github.com/matrix-org/matrix-react-sdk/pull/943) + * Update from Weblate. + [\#942](https://github.com/matrix-org/matrix-react-sdk/pull/942) + * Update from Weblate. + [\#941](https://github.com/matrix-org/matrix-react-sdk/pull/941) + * Update from Weblate. + [\#938](https://github.com/matrix-org/matrix-react-sdk/pull/938) + * Fix PM being AM + [\#939](https://github.com/matrix-org/matrix-react-sdk/pull/939) + * pass call state through dispatcher, for poor electron + [\#918](https://github.com/matrix-org/matrix-react-sdk/pull/918) + * Translations! + [\#934](https://github.com/matrix-org/matrix-react-sdk/pull/934) + * Remove suffix and prefix from login input username + [\#906](https://github.com/matrix-org/matrix-react-sdk/pull/906) + * Kierangould/12hourtimestamp + [\#903](https://github.com/matrix-org/matrix-react-sdk/pull/903) + * Don't include src in the test resolve root + [\#931](https://github.com/matrix-org/matrix-react-sdk/pull/931) + * Make the linked versions open a new tab, turt2live complained :P + [\#910](https://github.com/matrix-org/matrix-react-sdk/pull/910) + * Fix lint errors in SlashCommands + [\#919](https://github.com/matrix-org/matrix-react-sdk/pull/919) + * autoFocus input box + [\#911](https://github.com/matrix-org/matrix-react-sdk/pull/911) + * Make travis test against riot-web new-guest-access + [\#917](https://github.com/matrix-org/matrix-react-sdk/pull/917) + * Add right-branch logic to travis test script + [\#916](https://github.com/matrix-org/matrix-react-sdk/pull/916) + * Group e2e keys into blocks of 4 characters + [\#914](https://github.com/matrix-org/matrix-react-sdk/pull/914) + * Factor out DeviceVerifyDialog + [\#913](https://github.com/matrix-org/matrix-react-sdk/pull/913) + * Fix 'missing page_type' error + [\#909](https://github.com/matrix-org/matrix-react-sdk/pull/909) + * code style update + [\#904](https://github.com/matrix-org/matrix-react-sdk/pull/904) + Changes in [0.8.9](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.8.9) (2017-05-22) =================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.8.9-rc.1...v0.8.9) diff --git a/karma.conf.js b/karma.conf.js index 4ad72b4927..d544248332 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -177,6 +177,11 @@ module.exports = function (config) { ], }, devtool: 'inline-source-map', + externals: { + // Don't try to bundle electron: leave it as a commonjs dependency + // (the 'commonjs' here means it will output a 'require') + "electron": "commonjs electron", + }, }, webpackMiddleware: { diff --git a/package.json b/package.json index adaeaf3c29..163127e8d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "0.8.9", + "version": "0.9.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -64,7 +64,7 @@ "isomorphic-fetch": "^2.2.1", "linkifyjs": "^2.1.3", "lodash": "^4.13.1", - "matrix-js-sdk": "matrix-org/matrix-js-sdk#develop", + "matrix-js-sdk": "0.7.10", "optimist": "^0.6.1", "prop-types": "^15.5.8", "q": "^1.4.1", diff --git a/scripts/check-i18n.pl b/scripts/check-i18n.pl index 0ace98d0fc..fa11bc5292 100755 --- a/scripts/check-i18n.pl +++ b/scripts/check-i18n.pl @@ -178,12 +178,12 @@ sub read_src_strings { $src =~ s/"\s*\+\s*"//g; $file =~ s/^.*\/src/src/; - while ($src =~ /_t\(\s*'(.*?[^\\])'/sg) { + while ($src =~ /_t(?:Jsx)?\(\s*'(.*?[^\\])'/sg) { my $s = $1; $s =~ s/\\'/'/g; push @$strings, [$s, $file]; } - while ($src =~ /_t\(\s*"(.*?[^\\])"/sg) { + while ($src =~ /_t(?:Jsx)?\(\s*"(.*?[^\\])"/sg) { push @$strings, [$1, $file]; } } diff --git a/scripts/fix-i18n.pl b/scripts/fix-i18n.pl index d4ae3dfb49..247b2b663f 100755 --- a/scripts/fix-i18n.pl +++ b/scripts/fix-i18n.pl @@ -48,6 +48,19 @@ Guests can't use labs features. Please register. A new password must be entered. Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved. Guests cannot join this room even if explicitly invited. +Guest users can't invite users. Please register to invite. +This room is inaccessible to guests. You may be able to join if you register. +delete the alias. +remove %(name)s from the directory. +Conference call failed. +Conference calling is in development and may not be reliable. +Guest users can't create new rooms. Please register to create room and start a chat. +Server may be unavailable, overloaded, or you hit a bug. +Server unavailable, overloaded, or something else went wrong. +You are already in a call. +You cannot place VoIP calls in this browser. +You cannot place a call with yourself. +Your email address does not appear to be associated with a Matrix ID on this Homeserver. EOT )]; } diff --git a/src/Analytics.js b/src/Analytics.js index 4f9ce6ad7d..c079011db7 100644 --- a/src/Analytics.js +++ b/src/Analytics.js @@ -19,8 +19,10 @@ import MatrixClientPeg from './MatrixClientPeg'; import PlatformPeg from './PlatformPeg'; import SdkConfig from './SdkConfig'; -function redact(str) { - return str.replace(/#\/(room|user)\/(.+)/, "#/$1/"); +function getRedactedUrl() { + const base = window.location.pathname.split('/').slice(-2).join('/'); + const redactedHash = window.location.hash.replace(/#\/(room|user)\/(.+)/, "#/$1/"); + return base + redactedHash; } const customVariables = { @@ -108,7 +110,7 @@ class Analytics { this.firstPage = false; return; } - this._paq.push(['setCustomUrl', redact(window.location.href)]); + this._paq.push(['setCustomUrl', getRedactedUrl()]); this._paq.push(['trackPageView']); } diff --git a/src/BasePlatform.js b/src/BasePlatform.js index 7e5242b1fd..d0d8e0c74e 100644 --- a/src/BasePlatform.js +++ b/src/BasePlatform.js @@ -71,11 +71,14 @@ export default class BasePlatform { displayNotification(title: string, msg: string, avatarUrl: string, room: Object) { } + loudNotification(ev: Event, room: Object) { + } + /** * Returns a promise that resolves to a string representing * the current version of the application. */ - getAppVersion() { + getAppVersion(): Promise { throw new Error("getAppVersion not implemented!"); } @@ -84,10 +87,12 @@ export default class BasePlatform { * with getUserMedia, return a string explaining why not. * Otherwise, return null. */ - screenCaptureErrorString() { + screenCaptureErrorString(): string { return "Not implemented"; } + isElectron(): boolean { return false; } + /** * Restarts the application, without neccessarily reloading * any application code diff --git a/src/CallHandler.js b/src/CallHandler.js index ca913528ed..b2ccf65df7 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -226,7 +226,7 @@ function _onAction(payload) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { title: _t('Existing Call'), - description: _t('You are already in a call') + '.', + description: _t('You are already in a call.'), }); return; // don't allow >1 call to be placed. } @@ -236,7 +236,7 @@ function _onAction(payload) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { title: _t('VoIP is unsupported'), - description: _t('You cannot place VoIP calls in this browser') + '.', + description: _t('You cannot place VoIP calls in this browser.'), }); return; } @@ -251,7 +251,7 @@ function _onAction(payload) { if (members.length <= 1) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { - description: _t('You cannot place a call with yourself') + '.', + description: _t('You cannot place a call with yourself.'), }); return; } @@ -284,7 +284,7 @@ function _onAction(payload) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { title: _t('VoIP is unsupported'), - description: _t('You cannot place VoIP calls in this browser') + '.', + description: _t('You cannot place VoIP calls in this browser.'), }); } else if (MatrixClientPeg.get().isRoomEncrypted(payload.room_id)) { @@ -303,7 +303,7 @@ function _onAction(payload) { var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); Modal.createDialog(QuestionDialog, { title: _t('Warning!'), - description: _t('Conference calling is in development and may not be reliable') + '.', + description: _t('Conference calling is in development and may not be reliable.'), onFinished: confirm=>{ if (confirm) { ConferenceHandler.createNewMatrixCall( @@ -315,7 +315,7 @@ function _onAction(payload) { console.error("Conference call failed: " + err); Modal.createDialog(ErrorDialog, { title: _t('Failed to set up conference call'), - description: _t('Conference call failed') + '. ' + ((err && err.message) ? err.message : ''), + description: _t('Conference call failed.') + ' ' + ((err && err.message) ? err.message : ''), }); }); } diff --git a/src/CallMediaHandler.js b/src/CallMediaHandler.js new file mode 100644 index 0000000000..45ca5dc30d --- /dev/null +++ b/src/CallMediaHandler.js @@ -0,0 +1,65 @@ +/* + 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. +*/ + +import UserSettingsStore from './UserSettingsStore'; +import * as Matrix from 'matrix-js-sdk'; +import q from 'q'; + +export default { + getDevices: function() { + // Only needed for Electron atm, though should work in modern browsers + // once permission has been granted to the webapp + return navigator.mediaDevices.enumerateDevices().then(function(devices) { + const audioIn = []; + const videoIn = []; + + if (devices.some((device) => !device.label)) return false; + + devices.forEach((device) => { + switch (device.kind) { + case 'audioinput': audioIn.push(device); break; + case 'videoinput': videoIn.push(device); break; + } + }); + + // console.log("Loaded WebRTC Devices", mediaDevices); + return { + audioinput: audioIn, + videoinput: videoIn, + }; + }, (error) => { console.log('Unable to refresh WebRTC Devices: ', error); }); + }, + + loadDevices: function() { + // this.getDevices().then((devices) => { + const localSettings = UserSettingsStore.getLocalSettings(); + // // if deviceId is not found, automatic fallback is in spec + // // recall previously stored inputs if any + Matrix.setMatrixCallAudioInput(localSettings['webrtc_audioinput']); + Matrix.setMatrixCallVideoInput(localSettings['webrtc_videoinput']); + // }); + }, + + setAudioInput: function(deviceId) { + UserSettingsStore.setLocalSetting('webrtc_audioinput', deviceId); + Matrix.setMatrixCallAudioInput(deviceId); + }, + + setVideoInput: function(deviceId) { + UserSettingsStore.setLocalSetting('webrtc_videoinput', deviceId); + Matrix.setMatrixCallVideoInput(deviceId); + }, +}; diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js index 4acb314c2f..8af1894c79 100644 --- a/src/HtmlUtils.js +++ b/src/HtmlUtils.js @@ -360,7 +360,7 @@ export function bodyToHtml(content, highlights, opts) { 'mx_EventTile_bigEmoji': emojiBody, 'markdown-body': isHtml, }); - return ; + return ; } export function emojifyText(text) { diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 8bce6f8b48..54014a0166 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -240,7 +240,9 @@ function _handleRestoreFailure(e) { let msg = e.message; if (msg == "OLM.BAD_LEGACY_ACCOUNT_PICKLE") { msg = _t( - 'You need to log back in to generate end-to-end encryption keys for this device and submit the public key to your homeserver. This is a once off; sorry for the inconvenience.' + 'You need to log back in to generate end-to-end encryption keys' + + ' for this device and submit the public key to your homeserver.' + + ' This is a once off; sorry for the inconvenience.', ); _clearLocalStorage(); diff --git a/src/Login.js b/src/Login.js index 107a8825e9..87731744e9 100644 --- a/src/Login.js +++ b/src/Login.js @@ -16,6 +16,7 @@ limitations under the License. */ import Matrix from "matrix-js-sdk"; +import { _t } from "./languageHandler"; import q from 'q'; import url from 'url'; @@ -97,9 +98,9 @@ export default class Login { }; }, (error) => { if (error.httpStatus === 403) { - error.friendlyText = "Guest access is disabled on this Home Server."; + error.friendlyText = _t("Guest access is disabled on this Home Server."); } else { - error.friendlyText = "Failed to register as guest: " + error.data; + error.friendlyText = _t("Failed to register as guest:") + ' ' + error.data; } throw error; }); @@ -158,12 +159,12 @@ export default class Login { }, function(error) { if (error.httpStatus == 400 && loginParams.medium) { error.friendlyText = ( - 'This Home Server does not support login using email address.' + _t('This Home Server does not support login using email address.') ); } else if (error.httpStatus === 403) { error.friendlyText = ( - 'Incorrect username and/or password.' + _t('Incorrect username and/or password.') ); if (self._fallbackHsUrl) { var fbClient = Matrix.createClient({ @@ -187,7 +188,7 @@ export default class Login { } else { error.friendlyText = ( - 'There was a problem logging in. (HTTP ' + error.httpStatus + ")" + _t("There was a problem logging in.") + ' (HTTP ' + error.httpStatus + ")" ); } throw error; diff --git a/src/Notifier.js b/src/Notifier.js index e89947e958..40a65d4106 100644 --- a/src/Notifier.js +++ b/src/Notifier.js @@ -250,6 +250,7 @@ const Notifier = { this._displayPopupNotification(ev, room); } if (actions.tweaks.sound && this.isAudioEnabled()) { + PlatformPeg.get().loudNotification(ev, room); this._playAudioNotification(ev, room); } } diff --git a/src/PasswordReset.js b/src/PasswordReset.js index 668b8f67da..0739ca0a24 100644 --- a/src/PasswordReset.js +++ b/src/PasswordReset.js @@ -82,7 +82,7 @@ class PasswordReset { err.message = _t('Failed to verify email address: make sure you clicked the link in the email'); } else if (err.httpStatus === 404) { - err.message = _t('Your email address does not appear to be associated with a Matrix ID on this Homeserver') + '.'; + err.message = _t('Your email address does not appear to be associated with a Matrix ID on this Homeserver.'); } else if (err.httpStatus) { err.message += ` (Status ${err.httpStatus})`; diff --git a/src/Skinner.js b/src/Skinner.js index 4482f2239c..0688c9fc26 100644 --- a/src/Skinner.js +++ b/src/Skinner.js @@ -23,22 +23,28 @@ class Skinner { if (this.components === null) { throw new Error( "Attempted to get a component before a skin has been loaded."+ - "This is probably because either:"+ + " This is probably because either:"+ " a) Your app has not called sdk.loadSkin(), or"+ - " b) A component has called getComponent at the root level" + " b) A component has called getComponent at the root level", ); } - var comp = this.components[name]; - if (comp) { - return comp; - } + let comp = this.components[name]; // XXX: Temporarily also try 'views.' as we're currently // leaving the 'views.' off views. - var comp = this.components['views.'+name]; - if (comp) { - return comp; + if (!comp) { + comp = this.components['views.'+name]; } - throw new Error("No such component: "+name); + + if (!comp) { + throw new Error("No such component: "+name); + } + + // components have to be functions. + const validType = typeof comp === 'function'; + if (!validType) { + throw new Error(`Not a valid component: ${name}.`); + } + return comp; } load(skinObject) { diff --git a/src/SlashCommands.js b/src/SlashCommands.js index ae619a1359..185ea504ac 100644 --- a/src/SlashCommands.js +++ b/src/SlashCommands.js @@ -186,7 +186,7 @@ const commands = { if (targetRoomId) { break; } } if (!targetRoomId) { - return reject("Unrecognised room alias: " + roomAlias); + return reject(_t("Unrecognised room alias:") + ' ' + roomAlias); } } } @@ -303,14 +303,14 @@ const commands = { const device = MatrixClientPeg.get().getStoredDevice(userId, deviceId); if (!device) { - return reject(`Unknown (user, device) pair: (${userId}, ${deviceId})`); + return reject(_t(`Unknown (user, device) pair:`) + ` (${userId}, ${deviceId})`); } if (device.isVerified()) { if (device.getFingerprint() === fingerprint) { - return reject(`Device already verified!`); + return reject(_t(`Device already verified!`)); } else { - return reject(`WARNING: Device already verified, but keys do NOT MATCH!`); + return reject(_t(`WARNING: Device already verified, but keys do NOT MATCH!`)); } } @@ -322,12 +322,15 @@ const commands = { // Tell the user we verified everything! const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); Modal.createDialog(QuestionDialog, { - title: "Verified key", + title: _t("Verified key"), description: (

- The signing key you provided matches the signing key you received - from { userId }'s device { deviceId }. Device marked as verified. + { + _t("The signing key you provided matches the signing key you received " + + "from %(userId)s's device %(deviceId)s. Device marked as verified.", + {userId: userId, deviceId: deviceId}) + }

), @@ -336,9 +339,13 @@ const commands = { return success(); } else { - return reject(`WARNING: KEY VERIFICATION FAILED! The signing key for ${userId} and device - ${deviceId} is "${device.getFingerprint()}" which does not match the provided key - "${fingerprint}". This could mean your communications are being intercepted!`); + const fprint = device.getFingerprint(); + return reject( + _t('WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device' + + ' %(deviceId)s is "%(fprint)s" which does not match the provided key' + + ' "%(fingerprint)s". This could mean your communications are being intercepted!', + {deviceId: deviceId, fprint: fprint, userId: userId, fingerprint: fingerprint}) + ); } } } @@ -383,7 +390,7 @@ module.exports = { if (commands[cmd]) { return commands[cmd].run(roomId, args); } else { - return reject("Unrecognised command: " + input); + return reject(_t("Unrecognised command:") + ' ' + input); } } return null; // not a command diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js index 5b96692dc9..84d85e7565 100644 --- a/src/UserSettingsStore.js +++ b/src/UserSettingsStore.js @@ -17,24 +17,26 @@ limitations under the License. import q from 'q'; import MatrixClientPeg from './MatrixClientPeg'; import Notifier from './Notifier'; +import { _t } from './languageHandler'; /* * TODO: Make things use this. This is all WIP - see UserSettings.js for usage. */ -/* - * TODO: Find a way to translate the names of LABS_FEATURES. In other words, guarantee that languages were already loaded before building this array. - */ - export default { LABS_FEATURES: [ { - name: "New Composer & Autocomplete", + name: "-", id: 'rich_text_editor', default: false, }, ], + // horrible but it works. The locality makes this somewhat more palatable. + doTranslations: function() { + this.LABS_FEATURES[0].name = _t("New Composer & Autocomplete"); + }, + loadProfileInfo: function() { const cli = MatrixClientPeg.get(); return cli.getProfileInfo(cli.credentials.userId); diff --git a/src/async-components/views/dialogs/ExportE2eKeysDialog.js b/src/async-components/views/dialogs/ExportE2eKeysDialog.js index 5abd758fa8..d6f16a7105 100644 --- a/src/async-components/views/dialogs/ExportE2eKeysDialog.js +++ b/src/async-components/views/dialogs/ExportE2eKeysDialog.js @@ -166,11 +166,11 @@ export default React.createClass({
-
diff --git a/src/async-components/views/dialogs/ImportE2eKeysDialog.js b/src/async-components/views/dialogs/ImportE2eKeysDialog.js index 75b66e2969..61d2aeec74 100644 --- a/src/async-components/views/dialogs/ImportE2eKeysDialog.js +++ b/src/async-components/views/dialogs/ImportE2eKeysDialog.js @@ -164,11 +164,11 @@ export default React.createClass({
-
diff --git a/src/autocomplete/AutocompleteProvider.js b/src/autocomplete/AutocompleteProvider.js index 5c90990295..cbdb839ce3 100644 --- a/src/autocomplete/AutocompleteProvider.js +++ b/src/autocomplete/AutocompleteProvider.js @@ -1,3 +1,20 @@ +/* +Copyright 2016 Aviral Dasgupta +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. +*/ + import React from 'react'; import type {Completion, SelectionRange} from './Autocompleter'; diff --git a/src/autocomplete/Autocompleter.js b/src/autocomplete/Autocompleter.js index 1bf1b1dc14..f8564a43a0 100644 --- a/src/autocomplete/Autocompleter.js +++ b/src/autocomplete/Autocompleter.js @@ -1,3 +1,19 @@ +/* +Copyright 2016 Aviral Dasgupta + +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. +*/ + // @flow import type {Component} from 'react'; diff --git a/src/autocomplete/CommandProvider.js b/src/autocomplete/CommandProvider.js index fef0544b8a..205a3737dc 100644 --- a/src/autocomplete/CommandProvider.js +++ b/src/autocomplete/CommandProvider.js @@ -1,3 +1,20 @@ +/* +Copyright 2016 Aviral Dasgupta +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. +*/ + import React from 'react'; import { _t } from '../languageHandler'; import AutocompleteProvider from './AutocompleteProvider'; diff --git a/src/autocomplete/Components.js b/src/autocomplete/Components.js index 4595f7456d..b26a217ec6 100644 --- a/src/autocomplete/Components.js +++ b/src/autocomplete/Components.js @@ -1,3 +1,19 @@ +/* +Copyright 2016 Aviral Dasgupta + +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. +*/ + import React from 'react'; import ReactDOM from 'react-dom'; import classNames from 'classnames'; diff --git a/src/autocomplete/DuckDuckGoProvider.js b/src/autocomplete/DuckDuckGoProvider.js index bffd924976..9c996bb1cc 100644 --- a/src/autocomplete/DuckDuckGoProvider.js +++ b/src/autocomplete/DuckDuckGoProvider.js @@ -1,4 +1,22 @@ +/* +Copyright 2016 Aviral Dasgupta +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. +*/ + import React from 'react'; +import { _t } from '../languageHandler'; import AutocompleteProvider from './AutocompleteProvider'; import 'whatwg-fetch'; @@ -75,7 +93,7 @@ export default class DuckDuckGoProvider extends AutocompleteProvider { } getName() { - return '🔍 Results from DuckDuckGo'; + return '🔍 ' + _t('Results from DuckDuckGo'); } static getInstance(): DuckDuckGoProvider { diff --git a/src/autocomplete/EmojiProvider.js b/src/autocomplete/EmojiProvider.js index 7b9e8fb669..810212315b 100644 --- a/src/autocomplete/EmojiProvider.js +++ b/src/autocomplete/EmojiProvider.js @@ -1,3 +1,20 @@ +/* +Copyright 2016 Aviral Dasgupta +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. +*/ + import React from 'react'; import { _t } from '../languageHandler'; import AutocompleteProvider from './AutocompleteProvider'; diff --git a/src/autocomplete/RoomProvider.js b/src/autocomplete/RoomProvider.js index 4a2dd09eb5..be35c53e5d 100644 --- a/src/autocomplete/RoomProvider.js +++ b/src/autocomplete/RoomProvider.js @@ -1,3 +1,20 @@ +/* +Copyright 2016 Aviral Dasgupta +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. +*/ + import React from 'react'; import { _t } from '../languageHandler'; import AutocompleteProvider from './AutocompleteProvider'; diff --git a/src/autocomplete/UserProvider.js b/src/autocomplete/UserProvider.js index 281b6fd40d..fedebb3618 100644 --- a/src/autocomplete/UserProvider.js +++ b/src/autocomplete/UserProvider.js @@ -1,3 +1,20 @@ +/* +Copyright 2016 Aviral Dasgupta +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. +*/ + import React from 'react'; import { _t } from '../languageHandler'; import AutocompleteProvider from './AutocompleteProvider'; diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 67e2fe8856..a201a0bea7 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -18,9 +18,11 @@ limitations under the License. import * as Matrix from 'matrix-js-sdk'; import React from 'react'; +import UserSettingsStore from '../../UserSettingsStore'; import KeyCode from '../../KeyCode'; import Notifier from '../../Notifier'; import PageTypes from '../../PageTypes'; +import CallMediaHandler from '../../CallMediaHandler'; import sdk from '../../index'; import dis from '../../dispatcher'; import sessionStore from '../../stores/SessionStore'; @@ -68,6 +70,13 @@ export default React.createClass({ }; }, + getInitialState: function() { + return { + // use compact timeline view + useCompactLayout: UserSettingsStore.getSyncedSetting('useCompactLayout'), + }; + }, + componentWillMount: function() { // stash the MatrixClient in case we log out before we are unmounted this._matrixClient = this.props.matrixClient; @@ -76,6 +85,8 @@ export default React.createClass({ // RoomView.getScrollState() this._scrollStateMap = {}; + CallMediaHandler.loadDevices(); + document.addEventListener('keydown', this._onKeyDown); this._sessionStore = sessionStore; @@ -83,10 +94,13 @@ export default React.createClass({ this._setStateFromSessionStore, ); this._setStateFromSessionStore(); + + this._matrixClient.on("accountData", this.onAccountData); }, componentWillUnmount: function() { document.removeEventListener('keydown', this._onKeyDown); + this._matrixClient.removeListener("accountData", this.onAccountData); if (this._sessionStoreToken) { this._sessionStoreToken.remove(); } @@ -119,6 +133,14 @@ export default React.createClass({ }); }, + onAccountData: function(event) { + if (event.getType() === "im.vector.web.settings") { + this.setState({ + useCompactLayout: event.getContent().useCompactLayout, + }); + } + }, + _onKeyDown: function(ev) { /* // Remove this for now as ctrl+alt = alt-gr so this breaks keyboards which rely on alt-gr for numbers @@ -279,6 +301,9 @@ export default React.createClass({ if (topBar) { bodyClasses += ' mx_MatrixChat_toolbarShowing'; } + if (this.state.useCompactLayout) { + bodyClasses += ' mx_MatrixChat_useCompactLayout'; + } return (
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 3c0552b84b..4f9077eac7 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -41,7 +41,7 @@ import PageTypes from '../../PageTypes'; import createRoom from "../../createRoom"; import * as UDEHandler from '../../UnknownDeviceErrorHandler'; -import { _t } from '../../languageHandler'; +import { _t, getCurrentLanguage } from '../../languageHandler'; module.exports = React.createClass({ displayName: 'MatrixChat', @@ -404,7 +404,7 @@ module.exports = React.createClass({ }, (err) => { modal.close(); Modal.createDialog(ErrorDialog, { - title: _t('Failed to reject invitation'), + title: _t('Failed to reject invitation'), description: err.toString(), }); }); @@ -465,8 +465,8 @@ module.exports = React.createClass({ title: _t('Create Room'), description: _t('Room name (optional)'), button: _t('Create Room'), - onFinished: (should_create, name) => { - if (should_create) { + onFinished: (shouldCreate, name) => { + if (shouldCreate) { const createOpts = {}; if (name) createOpts.name = name; createRoom({createOpts}).done(); @@ -613,39 +613,44 @@ module.exports = React.createClass({ // switch view to the given room // - // @param {Object} room_info Object containing data about the room to be joined - // @param {string=} room_info.room_id ID of the room to join. One of room_id or room_alias must be given. - // @param {string=} room_info.room_alias Alias of the room to join. One of room_id or room_alias must be given. - // @param {boolean=} room_info.auto_join If true, automatically attempt to join the room if not already a member. - // @param {boolean=} room_info.show_settings Makes RoomView show the room settings dialog. - // @param {string=} room_info.event_id ID of the event in this room to show: this will cause a switch to the + // @param {Object} roomInfo Object containing data about the room to be joined + // @param {string=} roomInfo.room_id ID of the room to join. One of room_id or room_alias must be given. + // @param {string=} roomInfo.room_alias Alias of the room to join. One of room_id or room_alias must be given. + // @param {boolean=} roomInfo.auto_join If true, automatically attempt to join the room if not already a member. + // @param {boolean=} roomInfo.show_settings Makes RoomView show the room settings dialog. + // @param {string=} roomInfo.event_id ID of the event in this room to show: this will cause a switch to the // context of that particular event. - // @param {Object=} room_info.third_party_invite Object containing data about the third party + // @param {Object=} roomInfo.third_party_invite Object containing data about the third party // we received to join the room, if any. - // @param {string=} room_info.third_party_invite.inviteSignUrl 3pid invite sign URL - // @param {string=} room_info.third_party_invite.invitedEmail The email address the invite was sent to - // @param {Object=} room_info.oob_data Object of additional data about the room + // @param {string=} roomInfo.third_party_invite.inviteSignUrl 3pid invite sign URL + // @param {string=} roomInfo.third_party_invite.invitedEmail The email address the invite was sent to + // @param {Object=} roomInfo.oob_data Object of additional data about the room // that has been passed out-of-band (eg. // room name and avatar from an invite email) - _viewRoom: function(room_info) { + _viewRoom: function(roomInfo) { this.focusComposer = true; const newState = { - initialEventId: room_info.event_id, - highlightedEventId: room_info.event_id, + initialEventId: roomInfo.event_id, + highlightedEventId: roomInfo.event_id, initialEventPixelOffset: undefined, page_type: PageTypes.RoomView, - thirdPartyInvite: room_info.third_party_invite, - roomOobData: room_info.oob_data, - autoJoin: room_info.auto_join, + thirdPartyInvite: roomInfo.third_party_invite, + roomOobData: roomInfo.oob_data, + currentRoomAlias: roomInfo.room_alias, + autoJoin: roomInfo.auto_join, }; + if (!roomInfo.room_alias) { + newState.currentRoomId = roomInfo.room_id; + } + // if we aren't given an explicit event id, look for one in the // scrollStateMap. // // TODO: do this in RoomView rather than here - if (!room_info.event_id && this.refs.loggedInView) { - const scrollState = this.refs.loggedInView.getScrollStateForRoom(room_info.room_id); + if (!roomInfo.event_id && this.refs.loggedInView) { + const scrollState = this.refs.loggedInView.getScrollStateForRoom(roomInfo.room_id); if (scrollState) { newState.initialEventId = scrollState.focussedEvent; newState.initialEventPixelOffset = scrollState.pixelOffset; @@ -657,15 +662,15 @@ module.exports = React.createClass({ let waitFor = q(null); if (!this.firstSyncComplete) { if (!this.firstSyncPromise) { - console.warn('Cannot view a room before first sync. room_id:', room_info.room_id); + console.warn('Cannot view a room before first sync. room_id:', roomInfo.room_id); return; } waitFor = this.firstSyncPromise.promise; } waitFor.done(() => { - let presentedId = room_info.room_alias || room_info.room_id; - const room = MatrixClientPeg.get().getRoom(room_info.room_id); + let presentedId = roomInfo.room_alias || roomInfo.room_id; + const room = MatrixClientPeg.get().getRoom(roomInfo.room_id); if (room) { const theAlias = Rooms.getDisplayAliasForRoom(room); if (theAlias) presentedId = theAlias; @@ -677,8 +682,8 @@ module.exports = React.createClass({ } } - if (room_info.event_id) { - presentedId += "/" + room_info.event_id; + if (roomInfo.event_id) { + presentedId += "/" + roomInfo.event_id; } this.notifyNewScreen('room/' + presentedId); newState.ready = true; @@ -723,7 +728,7 @@ module.exports = React.createClass({ title: _t('Start a chat'), description: _t("Who would you like to communicate with?"), placeholder: _t("Email, name or matrix ID"), - button: _t("Start Chat") + button: _t("Start Chat"), }); }, @@ -793,8 +798,12 @@ module.exports = React.createClass({ const roomToLeave = MatrixClientPeg.get().getRoom(roomId); Modal.createDialog(QuestionDialog, { - title: "Leave room", - description: Are you sure you want to leave the room {roomToLeave.name}?, + title: _t("Leave room"), + description: ( + + {_t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name})} + + ), onFinished: (shouldLeave) => { if (shouldLeave) { const d = MatrixClientPeg.get().leave(roomId); @@ -907,7 +916,7 @@ module.exports = React.createClass({ MatrixClientPeg.get().getUserIdLocalpart(), ); - if (this.props.config.welcomeUserId) { + if (this.props.config.welcomeUserId && getCurrentLanguage().startsWith("en")) { createRoom({ dmUserId: this.props.config.welcomeUserId, // Only view the welcome user if we're NOT looking at a room diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 6b80223e42..9251ff2bdb 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -90,6 +90,9 @@ module.exports = React.createClass({ // show timestamps always alwaysShowTimestamps: React.PropTypes.bool, + + // hide redacted events as per old behaviour + hideRedactions: React.PropTypes.bool, }, componentWillMount: function() { @@ -316,7 +319,7 @@ module.exports = React.createClass({ const key = "membereventlistsummary-" + (prevEvent ? mxEv.getId() : "initial"); if (this._wantsDateSeparator(prevEvent, mxEv.getDate())) { - let dateSeparator =
  • ; + let dateSeparator =
  • ; ret.push(dateSeparator); } @@ -458,11 +461,13 @@ module.exports = React.createClass({ // do we need a date separator since the last event? if (this._wantsDateSeparator(prevEvent, eventDate)) { - var dateSeparator =
  • ; + var dateSeparator =
  • ; ret.push(dateSeparator); continuation = false; } + if (mxEv.isRedacted() && this.props.hideRedactions) return ret; + var eventId = mxEv.getId(); var highlight = (eventId == this.props.highlightedEventId); diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 76004ebbac..aecb468f71 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -131,6 +131,8 @@ var TimelinePanel = React.createClass({ } } + const syncedSettings = UserSettingsStore.getSyncedSettings(); + return { events: [], timelineLoading: true, // track whether our room timeline is loading @@ -175,10 +177,13 @@ var TimelinePanel = React.createClass({ clientSyncState: MatrixClientPeg.get().getSyncState(), // should the event tiles have twelve hour times - isTwelveHour: UserSettingsStore.getSyncedSetting('showTwelveHourTimestamps'), + isTwelveHour: syncedSettings.showTwelveHourTimestamps, // always show timestamps on event tiles? - alwaysShowTimestamps: UserSettingsStore.getSyncedSetting('alwaysShowTimestamps'), + alwaysShowTimestamps: syncedSettings.alwaysShowTimestamps, + + // hide redacted events as per old behaviour + hideRedactions: syncedSettings.hideRedactions, }; }, @@ -915,7 +920,7 @@ var TimelinePanel = React.createClass({ }); }; } - var message = (error.errcode == 'M_FORBIDDEN') + var message = (error.errcode == 'M_FORBIDDEN') ? _t("Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question") + "." : _t("Tried to load a specific point in this room's timeline, but was unable to find it") + "."; Modal.createDialog(ErrorDialog, { @@ -1113,26 +1118,27 @@ var TimelinePanel = React.createClass({ ); return (