diff --git a/src/ScalarAuthClient.js b/src/ScalarAuthClient.js index 3e775a94ab..7bd8603264 100644 --- a/src/ScalarAuthClient.js +++ b/src/ScalarAuthClient.js @@ -15,6 +15,7 @@ limitations under the License. */ import Promise from 'bluebird'; +import SettingsStore from "./settings/SettingsStore"; const request = require('browser-request'); const SdkConfig = require('./SdkConfig'); @@ -109,6 +110,7 @@ class ScalarAuthClient { let url = SdkConfig.get().integrations_ui_url; url += "?scalar_token=" + encodeURIComponent(this.scalarToken); url += "&room_id=" + encodeURIComponent(roomId); + url += "&theme=" + encodeURIComponent(SettingsStore.getValue("theme")); if (id) { url += '&integ_id=' + encodeURIComponent(id); } diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js index 7bde607451..3c164c6551 100644 --- a/src/ScalarMessaging.js +++ b/src/ScalarMessaging.js @@ -557,8 +557,16 @@ const onMessage = function(event) { // // All strings start with the empty string, so for sanity return if the length // of the event origin is 0. + // + // TODO -- Scalar postMessage API should be namespaced with event.data.api field + // Fix following "if" statement to respond only to specific API messages. const url = SdkConfig.get().integrations_ui_url; - if (event.origin.length === 0 || !url.startsWith(event.origin) || !event.data.action) { + if ( + event.origin.length === 0 || + !url.startsWith(event.origin) || + !event.data.action || + event.data.api // Ignore messages with specific API set + ) { return; // don't log this - debugging APIs like to spam postMessage which floods the log otherwise } diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 3ac2f5bd50..3452d13841 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1068,7 +1068,7 @@ export default React.createClass({ if (!self._loggedInView) { return true; } - return self._loggedInView.canResetTimelineInRoom(roomId); + return self._loggedInView.getDecoratedComponentInstance().canResetTimelineInRoom(roomId); }); cli.on('sync', function(state, prevState) { diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 138c110c4f..e240ab38d5 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -854,9 +854,13 @@ module.exports = React.createClass({ ev.dataTransfer.dropEffect = 'none'; - const items = ev.dataTransfer.items; - if (items.length == 1) { - if (items[0].kind == 'file') { + const items = [...ev.dataTransfer.items]; + if (items.length >= 1) { + const isDraggingFiles = items.every(function(item) { + return item.kind == 'file'; + }); + + if (isDraggingFiles) { this.setState({ draggingFile: true }); ev.dataTransfer.dropEffect = 'copy'; } @@ -867,10 +871,8 @@ module.exports = React.createClass({ ev.stopPropagation(); ev.preventDefault(); this.setState({ draggingFile: false }); - const files = ev.dataTransfer.files; - if (files.length == 1) { - this.uploadFile(files[0]); - } + const files = [...ev.dataTransfer.files]; + files.forEach(this.uploadFile); }, onDragLeaveOrEnd: function(ev) { diff --git a/src/components/views/login/InteractiveAuthEntryComponents.js b/src/components/views/login/InteractiveAuthEntryComponents.js index d0b6c8decb..e8997b4112 100644 --- a/src/components/views/login/InteractiveAuthEntryComponents.js +++ b/src/components/views/login/InteractiveAuthEntryComponents.js @@ -374,7 +374,7 @@ export const MsisdnAuthEntry = React.createClass({ return (

{ _t("A text message has been sent to %(msisdn)s", - { msisdn: this._msisdn }, + { msisdn: { this._msisdn } }, ) }

{ _t("Please enter the code it contains:") }

diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 78677e138b..e29f46fd37 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -27,6 +27,7 @@ import ScalarAuthClient from '../../../ScalarAuthClient'; import ScalarMessaging from '../../../ScalarMessaging'; import { _t } from '../../../languageHandler'; import WidgetUtils from '../../../WidgetUtils'; +import SettingsStore from "../../../settings/SettingsStore"; // The maximum number of widgets that can be added in a room const MAX_WIDGETS = 2; @@ -131,6 +132,9 @@ module.exports = React.createClass({ '$matrix_room_id': this.props.room.roomId, '$matrix_display_name': user ? user.displayName : this.props.userId, '$matrix_avatar_url': user ? MatrixClientPeg.get().mxcUrlToHttp(user.avatarUrl) : '', + + // TODO: Namespace themes through some standard + '$theme': SettingsStore.getValue("theme"), }; app.id = appId; diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 8c2bdd5994..45e76da839 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -89,11 +89,11 @@ module.exports = React.createClass({ this._groupStoreTokens = []; // A map between tags which are group IDs and the room IDs of rooms that should be kept // in the room list when filtering by that tag. - this._selectedTagsRoomIdsForGroup = { + this._visibleRoomsForGroup = { // $groupId: [$roomId1, $roomId2, ...], }; // All rooms that should be kept in the room list when filtering - this._selectedTagsRoomIds = []; + this._visibleRooms = []; // When the selected tags are changed, initialise a group store if necessary this._filterStoreToken = FilterStore.addListener(() => { FilterStore.getSelectedTags().forEach((tag) => { @@ -109,7 +109,7 @@ module.exports = React.createClass({ ); }); // Filters themselves have changed, refresh the selected tags - this.updateSelectedTagsRooms(dmRoomMap, FilterStore.getSelectedTags()); + this.updateVisibleRooms(); }); this.refreshRoomList(); @@ -276,29 +276,30 @@ module.exports = React.createClass({ this.refreshRoomList(); }, 500), - // Update which rooms and users should appear in RoomList as dictated by selected tags - updateSelectedTagsRooms: function(dmRoomMap, updatedTags) { + // Update which rooms and users should appear in RoomList for a given group tag + updateVisibleRoomsForTag: function(dmRoomMap, tag) { if (!this.mounted) return; - updatedTags.forEach((tag) => { - // For now, only handle group tags - const store = this._groupStores[tag]; - if (!store) return; + // For now, only handle group tags + const store = this._groupStores[tag]; + if (!store) return; - this._selectedTagsRoomIdsForGroup[tag] = []; - store.getGroupRooms().forEach((room) => this._selectedTagsRoomIdsForGroup[tag].push(room.roomId)); - store.getGroupMembers().forEach((member) => { - if (member.userId === MatrixClientPeg.get().credentials.userId) return; - dmRoomMap.getDMRoomsForUserId(member.userId).forEach( - (roomId) => this._selectedTagsRoomIdsForGroup[tag].push(roomId), - ); - }); - // TODO: Check if room has been tagged to the group by the user + this._visibleRoomsForGroup[tag] = []; + store.getGroupRooms().forEach((room) => this._visibleRoomsForGroup[tag].push(room.roomId)); + store.getGroupMembers().forEach((member) => { + if (member.userId === MatrixClientPeg.get().credentials.userId) return; + dmRoomMap.getDMRoomsForUserId(member.userId).forEach( + (roomId) => this._visibleRoomsForGroup[tag].push(roomId), + ); }); + // TODO: Check if room has been tagged to the group by the user + }, - this._selectedTagsRoomIds = []; + // Update which rooms and users should appear according to which tags are selected + updateVisibleRooms: function() { + this._visibleRooms = []; FilterStore.getSelectedTags().forEach((tag) => { - (this._selectedTagsRoomIdsForGroup[tag] || []).forEach( - (roomId) => this._selectedTagsRoomIds.push(roomId), + (this._visibleRoomsForGroup[tag] || []).forEach( + (roomId) => this._visibleRooms.push(roomId), ); }); @@ -311,7 +312,7 @@ module.exports = React.createClass({ isRoomInSelectedTags: function(room) { // No selected tags = every room is visible in the list - return this.state.selectedTags.length === 0 || this._selectedTagsRoomIds.includes(room.roomId); + return this.state.selectedTags.length === 0 || this._visibleRooms.includes(room.roomId); }, refreshRoomList: function() { diff --git a/src/languageHandler.js b/src/languageHandler.js index 59d71505a4..e732927a75 100644 --- a/src/languageHandler.js +++ b/src/languageHandler.js @@ -23,6 +23,10 @@ import SettingsStore, {SettingLevel} from "./settings/SettingsStore"; const i18nFolder = 'i18n/'; +// Control whether to also return original, untranslated strings +// Useful for debugging and testing +const ANNOTATE_STRINGS = false; + // We use english strings as keys, some of which contain full stops counterpart.setSeparator('|'); // Fall back to English @@ -84,7 +88,21 @@ export function _t(text, variables, tags) { // The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution) const translated = safeCounterpartTranslate(text, args); - return substitute(translated, variables, tags); + let substituted = substitute(translated, variables, tags); + + // For development/testing purposes it is useful to also output the original string + // Don't do that for release versions + if (ANNOTATE_STRINGS) { + if (typeof substituted === 'string') { + return `@@${text}##${substituted}@@` + } + else { + return {substituted}; + } + } + else { + return substituted; + } } /*