From 0cc890c02043445e077f8b6cba93a5ba431b071c Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Thu, 20 Jul 2017 18:01:39 +0100 Subject: [PATCH 01/34] WIP store history as raw content state Not sure this solves any problems because we still have to convert from md and back --- src/ComposerHistoryManager.js | 32 +++++++++++-------- .../views/rooms/MessageComposerInput.js | 13 +++----- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/ComposerHistoryManager.js b/src/ComposerHistoryManager.js index 1ae836574b..ee2c748dc6 100644 --- a/src/ComposerHistoryManager.js +++ b/src/ComposerHistoryManager.js @@ -15,36 +15,40 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ContentState} from 'draft-js'; +import {ContentState, convertToRaw, convertFromRaw} from 'draft-js'; import * as RichText from './RichText'; import Markdown from './Markdown'; -import _flow from 'lodash/flow'; import _clamp from 'lodash/clamp'; type MessageFormat = 'html' | 'markdown'; class HistoryItem { - message: string = ''; + + // Keeping message for backwards-compatibility + message: string; + rawContentState: RawDraftContentState; format: MessageFormat = 'html'; - constructor(message: string, format: MessageFormat) { - this.message = message; + constructor(contentState: ?ContentState, format: ?MessageFormat) { + this.rawContentState = contentState ? convertToRaw(contentState) : null; this.format = format; } - toContentState(format: MessageFormat): ContentState { - let {message} = this; - if (format === 'markdown') { + toContentState(outputFormat: MessageFormat): ContentState { + const contentState = convertFromRaw(this.rawContentState); + if (outputFormat === 'markdown') { if (this.format === 'html') { - message = _flow([RichText.htmlToContentState, RichText.stateToMarkdown])(message); + console.info(outputFormat, 'to other format'); + return ContentState.createFromText(RichText.stateToMarkdown(contentState)); } - return ContentState.createFromText(message); } else { if (this.format === 'markdown') { - message = new Markdown(message).toHTML(); + console.info(outputFormat, 'to other format'); + return RichText.htmlToContentState(new Markdown(contentState).toHTML()); } - return RichText.htmlToContentState(message); } + // history item has format === outputFormat + return contentState; } } @@ -67,8 +71,8 @@ export default class ComposerHistoryManager { this.lastIndex = this.currentIndex; } - addItem(message: string, format: MessageFormat) { - const item = new HistoryItem(message, format); + save(contentState: ContentState, format: MessageFormat) { + const item = new HistoryItem(contentState, format); this.history.push(item); this.currentIndex = this.lastIndex + 1; sessionStorage.setItem(`${this.prefix}[${this.lastIndex++}]`, JSON.stringify(item)); diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index 3165765167..06c854a69f 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -762,15 +762,10 @@ export default class MessageComposerInput extends React.Component { let sendHtmlFn = this.client.sendHtmlMessage; let sendTextFn = this.client.sendTextMessage; - if (this.state.isRichtextEnabled) { - this.historyManager.addItem( - contentHTML ? contentHTML : contentText, - contentHTML ? 'html' : 'markdown', - ); - } else { - // Always store MD input as input history - this.historyManager.addItem(contentText, 'markdown'); - } + this.historyManager.save( + contentState, + this.state.isRichtextEnabled ? 'html' : 'markdown', + ); if (contentText.startsWith('/me')) { contentText = contentText.substring(4); From bf98c0da7c09f1088fb7395e188ea171a9ff1c81 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 27 Jul 2017 17:19:18 +0100 Subject: [PATCH 02/34] un-i18n Modal Analytics Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/Analytics.js | 25 ++++---- src/CallHandler.js | 20 +++--- src/ContentMessages.js | 2 +- src/KeyRequestHandler.js | 2 +- src/Lifecycle.js | 4 +- src/Modal.js | 13 +++- src/Notifier.js | 2 +- src/SlashCommands.js | 8 +-- src/UnknownDeviceErrorHandler.js | 2 +- src/components/structures/GroupView.js | 4 +- src/components/structures/MatrixChat.js | 20 +++--- src/components/structures/MyGroups.js | 2 +- src/components/structures/RoomView.js | 14 ++--- src/components/structures/TimelinePanel.js | 2 +- src/components/structures/UserSettings.js | 62 +++++++++---------- .../structures/login/ForgotPassword.js | 23 ++++--- .../views/dialogs/ChatInviteDialog.js | 10 +-- .../views/dialogs/DeactivateAccountDialog.js | 2 + src/components/views/dialogs/ErrorDialog.js | 2 +- .../views/dialogs/KeyShareDialog.js | 2 +- .../dialogs/SessionRestoreErrorDialog.js | 2 +- .../views/dialogs/SetEmailDialog.js | 10 +-- src/components/views/elements/AppTile.js | 2 +- .../views/elements/DeviceVerifyButtons.js | 2 +- .../views/login/RegistrationForm.js | 2 +- src/components/views/login/ServerConfig.js | 2 +- src/components/views/messages/MFileBody.js | 4 +- src/components/views/messages/TextualBody.js | 2 +- .../views/room_settings/AliasSettings.js | 4 +- src/components/views/rooms/AppsDrawer.js | 2 +- src/components/views/rooms/EventTile.js | 2 +- src/components/views/rooms/MemberInfo.js | 16 ++--- src/components/views/rooms/MessageComposer.js | 2 +- .../views/rooms/MessageComposerInput.js | 4 +- src/components/views/rooms/RoomHeader.js | 2 +- src/components/views/rooms/RoomSettings.js | 12 ++-- .../views/settings/AddPhoneNumber.js | 4 +- .../views/settings/ChangePassword.js | 20 +++--- .../views/settings/DevicesPanelEntry.js | 4 +- src/createRoom.js | 2 +- src/stores/RoomViewStore.js | 2 +- 41 files changed, 163 insertions(+), 160 deletions(-) diff --git a/src/Analytics.js b/src/Analytics.js index 92691da1ea..5831eb7f8d 100644 --- a/src/Analytics.js +++ b/src/Analytics.js @@ -15,7 +15,6 @@ */ import { getCurrentLanguage } from './languageHandler'; -import MatrixClientPeg from './MatrixClientPeg'; import PlatformPeg from './PlatformPeg'; import SdkConfig from './SdkConfig'; @@ -31,8 +30,17 @@ const customVariables = { 'User Type': 3, 'Chosen Language': 4, 'Instance': 5, + 'Homeserver URL': 6, + 'Identity Server URL': 7, }; +function whitelistRedact(whitelist, str) { + if (whitelist.includes(str)) return str; + return ''; +} + +const whitelistedHSUrls = ["https://matrix.org"]; +const whitelistedISUrls = ["https://vector.im"]; class Analytics { constructor() { @@ -76,7 +84,7 @@ class Analytics { this._paq.push(['trackAllContentImpressions']); this._paq.push(['discardHashTag', false]); this._paq.push(['enableHeartBeatTimer']); - this._paq.push(['enableLinkTracking', true]); + // this._paq.push(['enableLinkTracking', true]); const platform = PlatformPeg.get(); this._setVisitVariable('App Platform', platform.getHumanReadableName()); @@ -130,20 +138,15 @@ class Analytics { this._paq.push(['deleteCookies']); } - login() { // not used currently - const cli = MatrixClientPeg.get(); - if (this.disabled || !cli) return; - - this._paq.push(['setUserId', `@${cli.getUserIdLocalpart()}:${cli.getDomain()}`]); - } - _setVisitVariable(key, value) { this._paq.push(['setCustomVariable', customVariables[key], key, value, 'visit']); } - setGuest(guest) { + setLoggedIn(isGuest, homeserverUrl, identityServerUrl) { if (this.disabled) return; - this._setVisitVariable('User Type', guest ? 'Guest' : 'Logged In'); + this._setVisitVariable('User Type', isGuest ? 'Guest' : 'Logged In'); + this._setVisitVariable('Homeserver URL', whitelistRedact(whitelistedHSUrls, homeserverUrl)); + this._setVisitVariable('Identity Server URL', whitelistRedact(whitelistedISUrls, identityServerUrl)); } } diff --git a/src/CallHandler.js b/src/CallHandler.js index e3fbe9e5e3..8331d579df 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -143,7 +143,7 @@ function _setCallListeners(call) { pause("ringbackAudio"); play("busyAudio"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Call Handler', 'Call Timeout', ErrorDialog, { title: _t('Call Timeout'), description: _t('The remote side failed to pick up') + '.', }); @@ -205,7 +205,7 @@ function _onAction(payload) { _setCallState(undefined, newCall.roomId, "ended"); console.log("Can't capture screen: " + screenCapErrorString); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Call Handler', 'Unable to capture screen', ErrorDialog, { title: _t('Unable to capture screen'), description: screenCapErrorString, }); @@ -225,7 +225,7 @@ function _onAction(payload) { case 'place_call': if (module.exports.getAnyActiveCall()) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Call Handler', 'Existing Call', ErrorDialog, { title: _t('Existing Call'), description: _t('You are already in a call.'), }); @@ -235,7 +235,7 @@ function _onAction(payload) { // if the runtime env doesn't do VoIP, whine. if (!MatrixClientPeg.get().supportsVoip()) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Call Handler', 'VoIP is unsupported', ErrorDialog, { title: _t('VoIP is unsupported'), description: _t('You cannot place VoIP calls in this browser.'), }); @@ -251,7 +251,7 @@ function _onAction(payload) { var members = room.getJoinedMembers(); if (members.length <= 1) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Call Handler', 'Cannot place call with self', ErrorDialog, { description: _t('You cannot place a call with yourself.'), }); return; @@ -277,13 +277,13 @@ function _onAction(payload) { console.log("Place conference call in %s", payload.room_id); if (!ConferenceHandler) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Call Handler', 'Conference call unsupported client', ErrorDialog, { description: _t('Conference calls are not supported in this client'), }); } else if (!MatrixClientPeg.get().supportsVoip()) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Call Handler', 'VoIP is unsupported', ErrorDialog, { title: _t('VoIP is unsupported'), description: _t('You cannot place VoIP calls in this browser.'), }); @@ -296,13 +296,13 @@ function _onAction(payload) { // participant. // Therefore we disable conference calling in E2E rooms. const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Call Handler', 'Conference calls unsupported e2e', ErrorDialog, { description: _t('Conference calls are not supported in encrypted rooms'), }); } else { var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - Modal.createDialog(QuestionDialog, { + Modal.createTrackedDialog('Call Handler', 'Conference calling in development', QuestionDialog, { title: _t('Warning!'), description: _t('Conference calling is in development and may not be reliable.'), onFinished: confirm=>{ @@ -314,7 +314,7 @@ function _onAction(payload) { }, function(err) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Conference call failed: " + err); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Call Handler', 'Failed to set up conference call', ErrorDialog, { title: _t('Failed to set up conference call'), description: _t('Conference call failed.') + ' ' + ((err && err.message) ? err.message : ''), }); diff --git a/src/ContentMessages.js b/src/ContentMessages.js index 9239de9d8f..1bd1332ab3 100644 --- a/src/ContentMessages.js +++ b/src/ContentMessages.js @@ -360,7 +360,7 @@ class ContentMessages { desc = _t('The file \'%(fileName)s\' exceeds this home server\'s size limit for uploads', {fileName: upload.fileName}); } var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Upload failed', err.message, ErrorDialog, { title: _t('Upload Failed'), description: desc, }); diff --git a/src/KeyRequestHandler.js b/src/KeyRequestHandler.js index 1da4922153..0b54d88e5f 100644 --- a/src/KeyRequestHandler.js +++ b/src/KeyRequestHandler.js @@ -125,7 +125,7 @@ export default class KeyRequestHandler { }; const KeyShareDialog = sdk.getComponent("dialogs.KeyShareDialog"); - Modal.createDialog(KeyShareDialog, { + Modal.createTrackedDialog('Key Share', 'Process Next Request', KeyShareDialog, { matrixClient: this._matrixClient, userId: userId, deviceId: deviceId, diff --git a/src/Lifecycle.js b/src/Lifecycle.js index eb2156e780..f4d1eeef08 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -240,7 +240,7 @@ function _handleRestoreFailure(e) { const SessionRestoreErrorDialog = sdk.getComponent('views.dialogs.SessionRestoreErrorDialog'); - Modal.createDialog(SessionRestoreErrorDialog, { + Modal.createTrackedDialog('Session Restore Error', e.message, SessionRestoreErrorDialog, { error: e.message, onFinished: (success) => { def.resolve(success); @@ -318,7 +318,7 @@ async function _doSetLoggedIn(credentials, clearStorage) { await _clearStorage(); } - Analytics.setGuest(credentials.guest); + Analytics.setLoggedIn(credentials.guest, credentials.homeserverUrl, credentials.identityServerUrl); // Resolves by default let teamPromise = Promise.resolve(null); diff --git a/src/Modal.js b/src/Modal.js index e100105a88..79fcaaefd1 100644 --- a/src/Modal.js +++ b/src/Modal.js @@ -103,13 +103,20 @@ class ModalManager { return container; } + createTrackedDialog(analyticsAction, analyticsInfo, Element, props, className) { + Analytics.trackEvent('Modal', analyticsAction, analyticsInfo); + return this.createDialog(Element, props, className); + } + createDialog(Element, props, className) { - if (props && props.title) { - Analytics.trackEvent('Modal', props.title, 'createDialog'); - } return this.createDialogAsync((cb) => {cb(Element);}, props, className); } + createTrackedDialogAsync(analyticsId, loader, props, className) { + Analytics.trackEvent('Modal', analyticsId); + return this.createDialogAsync(loader, props, className); + } + /** * Open a modal view. * diff --git a/src/Notifier.js b/src/Notifier.js index 40a65d4106..1bb435307d 100644 --- a/src/Notifier.js +++ b/src/Notifier.js @@ -142,7 +142,7 @@ const Notifier = { ? _t('Riot does not have permission to send you notifications - please check your browser settings') : _t('Riot was not given permission to send notifications - please try again'); const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Unable to enable Notifications', result, ErrorDialog, { title: _t('Unable to enable Notifications'), description, }); diff --git a/src/SlashCommands.js b/src/SlashCommands.js index dea3d27751..e5378d4347 100644 --- a/src/SlashCommands.js +++ b/src/SlashCommands.js @@ -68,7 +68,7 @@ const commands = { ddg: new Command("ddg", "", function(roomId, args) { const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); // TODO Don't explain this away, actually show a search UI here. - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Slash Commands', '/ddg is not a command', ErrorDialog, { title: _t('/ddg is not a command'), description: _t('To use it, just wait for autocomplete results to load and tab through them.'), }); @@ -326,13 +326,11 @@ const commands = { {deviceId: deviceId, fprint: fprint, userId: userId, fingerprint: fingerprint})); } - return MatrixClientPeg.get().setDeviceVerified( - userId, deviceId, true, - ); + return MatrixClientPeg.get().setDeviceVerified(userId, deviceId, true); }).then(() => { // Tell the user we verified everything const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - Modal.createDialog(QuestionDialog, { + Modal.createTrackedDialog('Slash Commands', 'Verified key', QuestionDialog, { title: _t("Verified key"), description: (
diff --git a/src/UnknownDeviceErrorHandler.js b/src/UnknownDeviceErrorHandler.js index 2b1cf23380..e7d77b3b66 100644 --- a/src/UnknownDeviceErrorHandler.js +++ b/src/UnknownDeviceErrorHandler.js @@ -24,7 +24,7 @@ const onAction = function(payload) { if (payload.action === 'unknown_device_error' && !isDialogOpen) { const UnknownDeviceDialog = sdk.getComponent('dialogs.UnknownDeviceDialog'); isDialogOpen = true; - Modal.createDialog(UnknownDeviceDialog, { + Modal.createTrackedDialog('Unknown Device Error', '', UnknownDeviceDialog, { devices: payload.err.devices, room: payload.room, onFinished: (r) => { diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 5f7866773d..fff61b1c24 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -266,7 +266,7 @@ export default React.createClass({ this.setState({uploadingAvatar: false}); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Failed to upload avatar image", e); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to upload image', e.toString(), ErrorDialog, { title: _t('Error'), description: _t('Failed to upload image'), }); @@ -288,7 +288,7 @@ export default React.createClass({ }); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Failed to save group profile", e); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to update group', e.toString(), ErrorDialog, { title: _t('Error'), description: _t('Failed to update group'), }); diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index b90cb53435..f042b41991 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -410,7 +410,7 @@ module.exports = React.createClass({ this._leaveRoom(payload.room_id); break; case 'reject_invite': - Modal.createDialog(QuestionDialog, { + Modal.createTrackedDialog('Reject invitation', '', QuestionDialog, { title: _t('Reject invitation'), description: _t('Are you sure you want to reject the invitation?'), onFinished: (confirm) => { @@ -426,7 +426,7 @@ module.exports = React.createClass({ } }, (err) => { modal.close(); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to reject invitation', err.toString(), ErrorDialog, { title: _t('Failed to reject invitation'), description: err.toString(), }); @@ -728,7 +728,7 @@ module.exports = React.createClass({ _setMxId: function(payload) { const SetMxIdDialog = sdk.getComponent('views.dialogs.SetMxIdDialog'); - const close = Modal.createDialog(SetMxIdDialog, { + const close = Modal.createTrackedDialog('Set MXID', '', SetMxIdDialog, { homeserverUrl: MatrixClientPeg.get().getHomeserverUrl(), onFinished: (submitted, credentials) => { if (!submitted) { @@ -767,7 +767,7 @@ module.exports = React.createClass({ return; } const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog"); - Modal.createDialog(ChatInviteDialog, { + Modal.createTrackedDialog('Start a chat', '', ChatInviteDialog, { title: _t('Start a chat'), description: _t("Who would you like to communicate with?"), placeholder: _t("Email, name or matrix ID"), @@ -787,7 +787,7 @@ module.exports = React.createClass({ return; } const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog"); - Modal.createDialog(TextInputDialog, { + Modal.createTrackedDialog('Create Room', '', TextInputDialog, { title: _t('Create Room'), description: _t('Room name (optional)'), button: _t('Create Room'), @@ -831,7 +831,7 @@ module.exports = React.createClass({ return; } - const close = Modal.createDialog(ChatCreateOrReuseDialog, { + const close = Modal.createTrackedDialog('Chat create or reuse', '', ChatCreateOrReuseDialog, { userId: userId, onFinished: (success) => { if (!success && goHomeOnCancel) { @@ -859,7 +859,7 @@ module.exports = React.createClass({ _invite: function(roomId) { const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog"); - Modal.createDialog(ChatInviteDialog, { + Modal.createTrackedDialog('Chat Invite', '', ChatInviteDialog, { title: _t('Invite new room members'), description: _t('Who would you like to add to this room?'), button: _t('Send Invites'), @@ -873,7 +873,7 @@ module.exports = React.createClass({ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const roomToLeave = MatrixClientPeg.get().getRoom(roomId); - Modal.createDialog(QuestionDialog, { + Modal.createTrackedDialog('Leave room', '', QuestionDialog, { title: _t("Leave room"), description: ( @@ -896,7 +896,7 @@ module.exports = React.createClass({ }, (err) => { modal.close(); console.error("Failed to leave room " + roomId + " " + err); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to leave room', err.toString(), ErrorDialog, { title: _t("Failed to leave room"), description: (err && err.message ? err.message : _t("Server may be unavailable, overloaded, or you hit a bug.")), @@ -1092,7 +1092,7 @@ module.exports = React.createClass({ }); cli.on('Session.logged_out', function(call) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Signed out', '', ErrorDialog, { title: _t('Signed Out'), description: _t('For security, this session has been signed out. Please sign in again.'), }); diff --git a/src/components/structures/MyGroups.js b/src/components/structures/MyGroups.js index 3eb694acce..0b8055beda 100644 --- a/src/components/structures/MyGroups.js +++ b/src/components/structures/MyGroups.js @@ -63,7 +63,7 @@ export default withMatrixClient(React.createClass({ _onCreateGroupClick: function() { const CreateGroupDialog = sdk.getComponent("dialogs.CreateGroupDialog"); - Modal.createDialog(CreateGroupDialog); + Modal.createTrackedDialog('Create Group', '', CreateGroupDialog); }, _fetch: function() { diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 094251f4c1..40dbc93071 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -544,7 +544,7 @@ module.exports = React.createClass({ } if (!userHasUsedEncryption) { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - Modal.createDialog(QuestionDialog, { + Modal.createTrackedDialog('E2E Warning', '', QuestionDialog, { title: _t("Warning!"), hasCancelButton: false, description: ( @@ -820,7 +820,7 @@ module.exports = React.createClass({ }); const SetMxIdDialog = sdk.getComponent('views.dialogs.SetMxIdDialog'); - const close = Modal.createDialog(SetMxIdDialog, { + const close = Modal.createTrackedDialog('Set MXID', '', SetMxIdDialog, { homeserverUrl: cli.getHomeserverUrl(), onFinished: (submitted, credentials) => { if (submitted) { @@ -934,7 +934,7 @@ module.exports = React.createClass({ } const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Failed to upload file " + file + " " + error); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to upload file', error.toString(), ErrorDialog, { title: _t('Failed to upload file'), description: ((error && error.message) ? error.message : _t("Server may be unavailable, overloaded, or the file too big")), }); @@ -1021,7 +1021,7 @@ module.exports = React.createClass({ }, function(error) { var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Search failed: " + error); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Search failed', error.toString(), ErrorDialog, { title: _t("Search failed"), description: ((error && error.message) ? error.message : _t("Server may be unavailable, overloaded, or search timed out :(")), }); @@ -1148,7 +1148,7 @@ module.exports = React.createClass({ console.error(result.reason); }); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to save room settings', '', ErrorDialog, { title: _t("Failed to save settings"), description: fails.map(function(result) { return result.reason; }).join("\n"), }); @@ -1195,7 +1195,7 @@ module.exports = React.createClass({ }, function(err) { var errCode = err.errcode || _t("unknown error code"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to forget room', err.toString(), ErrorDialog, { title: _t("Error"), description: _t("Failed to forget room %(errCode)s", { errCode: errCode }), }); @@ -1217,7 +1217,7 @@ module.exports = React.createClass({ var msg = error.message ? error.message : JSON.stringify(error); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to reject invite', error.toString(), ErrorDialog, { title: _t("Failed to reject invite"), description: msg, }); diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 0aee19545c..7cd67f3da8 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -923,7 +923,7 @@ var TimelinePanel = React.createClass({ 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, { + Modal.createTrackedDialog('Failed to load timeline position', error.toString(), ErrorDialog, { title: _t("Failed to load timeline position"), description: message, onFinished: onFinished, diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index 1e0fcff445..9a0567ec30 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -331,7 +331,7 @@ module.exports = React.createClass({ }, function(error) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Failed to load user settings: " + error); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Can\'t load user settings', error.toString(), ErrorDialog, { title: _t("Can't load user settings"), description: ((error && error.message) ? error.message : _t("Server may be unavailable or overloaded")), }); @@ -364,7 +364,7 @@ module.exports = React.createClass({ // const errMsg = (typeof err === "string") ? err : (err.error || ""); console.error("Failed to set avatar: " + err); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to set avatar', err.toString(), ErrorDialog, { title: _t("Failed to set avatar."), description: ((err && err.message) ? err.message : _t("Operation failed")), }); @@ -373,7 +373,7 @@ module.exports = React.createClass({ onLogoutClicked: function(ev) { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - Modal.createDialog(QuestionDialog, { + Modal.createTrackedDialog('Logout E2E Export', '', QuestionDialog, { title: _t("Sign out"), description:
@@ -409,7 +409,7 @@ module.exports = React.createClass({ } const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Failed to change password: " + errMsg); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to change password', err.toString(), ErrorDialog, { title: _t("Error"), description: errMsg, }); @@ -417,7 +417,7 @@ module.exports = React.createClass({ onPasswordChanged: function() { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Password changed', '', ErrorDialog, { title: _t("Success"), description: _t( "Your password was successfully changed. You will not receive " + @@ -442,7 +442,7 @@ module.exports = React.createClass({ const emailAddress = this.refs.add_email_input.value; if (!Email.looksValid(emailAddress)) { - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Invalid email address', '', ErrorDialog, { title: _t("Invalid Email Address"), description: _t("This doesn't appear to be a valid email address"), }); @@ -452,7 +452,7 @@ module.exports = React.createClass({ // we always bind emails when registering, so let's do the // same here. this._addThreepid.addEmailAddress(emailAddress, true).done(() => { - Modal.createDialog(QuestionDialog, { + Modal.createTrackedDialog('Verification Pending', '', QuestionDialog, { title: _t("Verification Pending"), description: _t( "Please check your email and click on the link it contains. Once this " + @@ -464,7 +464,7 @@ module.exports = React.createClass({ }, (err) => { this.setState({email_add_pending: false}); console.error("Unable to add email address " + emailAddress + " " + err); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Unable to add email address', err.toString(), ErrorDialog, { title: _t("Unable to add email address"), description: ((err && err.message) ? err.message : _t("Operation failed")), }); @@ -475,7 +475,7 @@ module.exports = React.createClass({ onRemoveThreepidClicked: function(threepid) { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - Modal.createDialog(QuestionDialog, { + Modal.createTrackedDialog('Remove 3pid', '', QuestionDialog, { title: _t("Remove Contact Information?"), description: _t("Remove %(threePid)s?", { threePid: threepid.address }), button: _t('Remove'), @@ -489,7 +489,7 @@ module.exports = React.createClass({ }).catch((err) => { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Unable to remove contact information: " + err); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Remove 3pid failed', err.toString(), ErrorDialog, { title: _t("Unable to remove contact information"), description: ((err && err.message) ? err.message : _t("Operation failed")), }); @@ -521,7 +521,7 @@ module.exports = React.createClass({ const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const message = _t("Unable to verify email address.") + " " + _t("Please check your email and click on the link it contains. Once this is done, click continue."); - Modal.createDialog(QuestionDialog, { + Modal.createTrackedDialog('Verification Pending', '', QuestionDialog, { title: _t("Verification Pending"), description: message, button: _t('Continue'), @@ -530,7 +530,7 @@ module.exports = React.createClass({ } else { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Unable to verify email address: " + err); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Unable to verify email address', err.toString(), ErrorDialog, { title: _t("Unable to verify email address."), description: ((err && err.message) ? err.message : _t("Operation failed")), }); @@ -540,7 +540,7 @@ module.exports = React.createClass({ _onDeactivateAccountClicked: function() { const DeactivateAccountDialog = sdk.getComponent("dialogs.DeactivateAccountDialog"); - Modal.createDialog(DeactivateAccountDialog, {}); + Modal.createTrackedDialog('Deactivate Account', '', DeactivateAccountDialog, {}); }, _onBugReportClicked: function() { @@ -548,7 +548,7 @@ module.exports = React.createClass({ if (!BugReportDialog) { return; } - Modal.createDialog(BugReportDialog, {}); + Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {}); }, _onClearCacheClicked: function() { @@ -585,27 +585,23 @@ module.exports = React.createClass({ }, _onExportE2eKeysClicked: function() { - Modal.createDialogAsync( - (cb) => { - require.ensure(['../../async-components/views/dialogs/ExportE2eKeysDialog'], () => { - cb(require('../../async-components/views/dialogs/ExportE2eKeysDialog')); - }, "e2e-export"); - }, { - matrixClient: MatrixClientPeg.get(), - }, - ); + Modal.createTrackedDialogAsync('Export E2E Keys', '', (cb) => { + require.ensure(['../../async-components/views/dialogs/ExportE2eKeysDialog'], () => { + cb(require('../../async-components/views/dialogs/ExportE2eKeysDialog')); + }, "e2e-export"); + }, { + matrixClient: MatrixClientPeg.get(), + }); }, _onImportE2eKeysClicked: function() { - Modal.createDialogAsync( - (cb) => { - require.ensure(['../../async-components/views/dialogs/ImportE2eKeysDialog'], () => { - cb(require('../../async-components/views/dialogs/ImportE2eKeysDialog')); - }, "e2e-export"); - }, { - matrixClient: MatrixClientPeg.get(), - }, - ); + Modal.createTrackedDialogAsync('Import E2E Keys', '', (cb) => { + require.ensure(['../../async-components/views/dialogs/ImportE2eKeysDialog'], () => { + cb(require('../../async-components/views/dialogs/ImportE2eKeysDialog')); + }, "e2e-export"); + }, { + matrixClient: MatrixClientPeg.get(), + }); }, _renderReferral: function() { @@ -1004,7 +1000,7 @@ module.exports = React.createClass({ this._refreshMediaDevices, function() { const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('No media permissions', '', ErrorDialog, { title: _t('No media permissions'), description: _t('You may need to manually permit Riot to access your microphone/webcam'), }); diff --git a/src/components/structures/login/ForgotPassword.js b/src/components/structures/login/ForgotPassword.js index 18a9dca5dd..2a6d5042dd 100644 --- a/src/components/structures/login/ForgotPassword.js +++ b/src/components/structures/login/ForgotPassword.js @@ -89,14 +89,14 @@ module.exports = React.createClass({ } else { var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - Modal.createDialog(QuestionDialog, { + Modal.createTrackedDialog('Forgot Password Warning', '', QuestionDialog, { title: _t('Warning!'), description:
{ _t( 'Resetting password will currently reset any ' + 'end-to-end encryption keys on all devices, ' + - 'making encrypted chat history unreadable, ' + + 'making encrypted chat history unreadable, ' + 'unless you first export your room keys and re-import ' + 'them afterwards. In future this will be improved.' ) } @@ -121,15 +121,13 @@ module.exports = React.createClass({ }, _onExportE2eKeysClicked: function() { - Modal.createDialogAsync( - (cb) => { - require.ensure(['../../../async-components/views/dialogs/ExportE2eKeysDialog'], () => { - cb(require('../../../async-components/views/dialogs/ExportE2eKeysDialog')); - }, "e2e-export"); - }, { - matrixClient: MatrixClientPeg.get(), - } - ); + Modal.createTrackedDialogAsync('Export E2E Keys', 'Forgot Password', (cb) => { + require.ensure(['../../../async-components/views/dialogs/ExportE2eKeysDialog'], () => { + cb(require('../../../async-components/views/dialogs/ExportE2eKeysDialog')); + }, "e2e-export"); + }, { + matrixClient: MatrixClientPeg.get(), + }); }, onInputChanged: function(stateKey, ev) { @@ -152,7 +150,8 @@ module.exports = React.createClass({ showErrorDialog: function(body, title) { var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + // TODO this will still lead to i18n in Analytics. + Modal.createTrackedDialog('Forgot Password Error', body, ErrorDialog, { title: title, description: body, }); diff --git a/src/components/views/dialogs/ChatInviteDialog.js b/src/components/views/dialogs/ChatInviteDialog.js index d3a208a785..156d493fee 100644 --- a/src/components/views/dialogs/ChatInviteDialog.js +++ b/src/components/views/dialogs/ChatInviteDialog.js @@ -103,7 +103,7 @@ module.exports = React.createClass({ const ChatCreateOrReuseDialog = sdk.getComponent( "views.dialogs.ChatCreateOrReuseDialog", ); - const close = Modal.createDialog(ChatCreateOrReuseDialog, { + const close = Modal.createTrackedDialog('Create or Reuse', '', ChatCreateOrReuseDialog, { userId: userId, onFinished: (success) => { this.props.onFinished(success); @@ -367,7 +367,7 @@ module.exports = React.createClass({ .catch(function(err) { console.error(err.stack); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to invite', err.toString(), ErrorDialog, { title: _t("Failed to invite"), description: ((err && err.message) ? err.message : _t("Operation failed")), }); @@ -380,7 +380,7 @@ module.exports = React.createClass({ .catch(function(err) { console.error(err.stack); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to invite user', err.toString(), ErrorDialog, { title: _t("Failed to invite user"), description: ((err && err.message) ? err.message : _t("Operation failed")), }); @@ -401,7 +401,7 @@ module.exports = React.createClass({ .catch(function(err) { console.error(err.stack); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to invite', err.toString(), ErrorDialog, { title: _t("Failed to invite"), description: ((err && err.message) ? err.message : _t("Operation failed")), }); @@ -448,7 +448,7 @@ module.exports = React.createClass({ if (errorList.length > 0) { var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to invite the following users to the room', '', ErrorDialog, { title: _t("Failed to invite the following users to the %(roomName)s room:", {roomName: room.name}), description: errorList.join(", "), }); diff --git a/src/components/views/dialogs/DeactivateAccountDialog.js b/src/components/views/dialogs/DeactivateAccountDialog.js index e3b7cca078..0ee264b69b 100644 --- a/src/components/views/dialogs/DeactivateAccountDialog.js +++ b/src/components/views/dialogs/DeactivateAccountDialog.js @@ -17,6 +17,7 @@ limitations under the License. import React from 'react'; import sdk from '../../../index'; +import Analytics from '../../../Analytics'; import MatrixClientPeg from '../../../MatrixClientPeg'; import * as Lifecycle from '../../../Lifecycle'; import Velocity from 'velocity-vector'; @@ -54,6 +55,7 @@ export default class DeactivateAccountDialog extends React.Component { user: MatrixClientPeg.get().credentials.userId, password: this._passwordField.value, }).done(() => { + Analytics.trackEvent('Account', 'Deactivate Account'); Lifecycle.onLoggedOut(); this.props.onFinished(false); }, (err) => { diff --git a/src/components/views/dialogs/ErrorDialog.js b/src/components/views/dialogs/ErrorDialog.js index bf48d1757b..889549369d 100644 --- a/src/components/views/dialogs/ErrorDialog.js +++ b/src/components/views/dialogs/ErrorDialog.js @@ -16,7 +16,7 @@ limitations under the License. /* * Usage: - * Modal.createDialog(ErrorDialog, { + * Modal.createTrackedDialog('An Identifier', err.toString(), ErrorDialog, { * title: "some text", (default: "Error") * description: "some more text", * button: "Button Text", diff --git a/src/components/views/dialogs/KeyShareDialog.js b/src/components/views/dialogs/KeyShareDialog.js index 61391d281c..aed8e6a5af 100644 --- a/src/components/views/dialogs/KeyShareDialog.js +++ b/src/components/views/dialogs/KeyShareDialog.js @@ -88,7 +88,7 @@ export default React.createClass({ const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog'); console.log("KeyShareDialog: Starting verify dialog"); - Modal.createDialog(DeviceVerifyDialog, { + Modal.createTrackedDialog('Key Share', 'Starting dialog', DeviceVerifyDialog, { userId: this.props.userId, device: this.state.deviceInfo, onFinished: (verified) => { diff --git a/src/components/views/dialogs/SessionRestoreErrorDialog.js b/src/components/views/dialogs/SessionRestoreErrorDialog.js index a3eb7c6962..010072e8c6 100644 --- a/src/components/views/dialogs/SessionRestoreErrorDialog.js +++ b/src/components/views/dialogs/SessionRestoreErrorDialog.js @@ -31,7 +31,7 @@ export default React.createClass({ _sendBugReport: function() { const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog"); - Modal.createDialog(BugReportDialog, {}); + Modal.createTrackedDialog('Session Restore Error', 'Send Bug Report Dialog', BugReportDialog, {}); }, _continueClicked: function() { diff --git a/src/components/views/dialogs/SetEmailDialog.js b/src/components/views/dialogs/SetEmailDialog.js index 3c38064ee1..b5efbab8b7 100644 --- a/src/components/views/dialogs/SetEmailDialog.js +++ b/src/components/views/dialogs/SetEmailDialog.js @@ -55,7 +55,7 @@ export default React.createClass({ const emailAddress = this.state.emailAddress; if (!Email.looksValid(emailAddress)) { - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Invalid Email Address', '', ErrorDialog, { title: _t("Invalid Email Address"), description: _t("This doesn't appear to be a valid email address"), }); @@ -65,7 +65,7 @@ export default React.createClass({ // we always bind emails when registering, so let's do the // same here. this._addThreepid.addEmailAddress(emailAddress, true).done(() => { - Modal.createDialog(QuestionDialog, { + Modal.createTrackedDialog('Verification Pending', '', QuestionDialog, { title: _t("Verification Pending"), description: _t( "Please check your email and click on the link it contains. Once this " + @@ -77,7 +77,7 @@ export default React.createClass({ }, (err) => { this.setState({emailBusy: false}); console.error("Unable to add email address " + emailAddress + " " + err); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Unable to add email address', err.toString(), ErrorDialog, { title: _t("Unable to add email address"), description: ((err && err.message) ? err.message : _t("Operation failed")), }); @@ -106,7 +106,7 @@ export default React.createClass({ const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const message = _t("Unable to verify email address.") + " " + _t("Please check your email and click on the link it contains. Once this is done, click continue."); - Modal.createDialog(QuestionDialog, { + Modal.createTrackedDialog('Verification Pending', 'M_THREEPID_AUTH_FAILED', QuestionDialog, { title: _t("Verification Pending"), description: message, button: _t('Continue'), @@ -115,7 +115,7 @@ export default React.createClass({ } else { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Unable to verify email address: " + err); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Unable to verify email address', err.toString(), ErrorDialog, { title: _t("Unable to verify email address."), description: ((err && err.message) ? err.message : _t("Operation failed")), }); diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 9573b9fd9f..76ab5ed989 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -95,7 +95,7 @@ export default React.createClass({ console.log("Edit widget ID ", this.props.id); const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); const src = this._scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId, 'type_' + this.props.type); - Modal.createDialog(IntegrationsManager, { + Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, { src: src, }, "mx_IntegrationsManager"); }, diff --git a/src/components/views/elements/DeviceVerifyButtons.js b/src/components/views/elements/DeviceVerifyButtons.js index dfca7e2600..bfe45905a1 100644 --- a/src/components/views/elements/DeviceVerifyButtons.js +++ b/src/components/views/elements/DeviceVerifyButtons.js @@ -52,7 +52,7 @@ export default React.createClass({ onVerifyClick: function() { const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog'); - Modal.createDialog(DeviceVerifyDialog, { + Modal.createTrackedDialog('Device Verify Dialog', '', DeviceVerifyDialog, { userId: this.props.userId, device: this.state.device, }); diff --git a/src/components/views/login/RegistrationForm.js b/src/components/views/login/RegistrationForm.js index ff07cd36e5..d5b7bcf46a 100644 --- a/src/components/views/login/RegistrationForm.js +++ b/src/components/views/login/RegistrationForm.js @@ -95,7 +95,7 @@ module.exports = React.createClass({ if (this.allFieldsValid()) { if (this.refs.email.value == '') { var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - Modal.createDialog(QuestionDialog, { + Modal.createTrackedDialog('If you don\'t specify an email address...', '', QuestionDialog, { title: _t("Warning!"), description:
diff --git a/src/components/views/login/ServerConfig.js b/src/components/views/login/ServerConfig.js index a63d02416c..0042ab5e9f 100644 --- a/src/components/views/login/ServerConfig.js +++ b/src/components/views/login/ServerConfig.js @@ -122,7 +122,7 @@ module.exports = React.createClass({ showHelpPopup: function() { var CustomServerDialog = sdk.getComponent('login.CustomServerDialog'); - Modal.createDialog(CustomServerDialog); + Modal.createTrackedDialog('Custom Server Dialog', '', CustomServerDialog); }, render: function() { diff --git a/src/components/views/messages/MFileBody.js b/src/components/views/messages/MFileBody.js index bccae923eb..b300e41e50 100644 --- a/src/components/views/messages/MFileBody.js +++ b/src/components/views/messages/MFileBody.js @@ -282,8 +282,8 @@ module.exports = React.createClass({ }); }).catch((err) => { console.warn("Unable to decrypt attachment: ", err); - Modal.createDialog(ErrorDialog, { - title: _t("Error"), + Modal.createTrackedDialog('Error decrypting attachment', err.toString(), ErrorDialog, { + title: _t("Error"), description: _t("Error decrypting attachment"), }); }).finally(() => { diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 6d4d01a196..740b0c10ca 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -299,7 +299,7 @@ module.exports = React.createClass({ let completeUrl = scalarClient.getStarterLink(starterLink); let QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); let integrationsUrl = SdkConfig.get().integrations_ui_url; - Modal.createDialog(QuestionDialog, { + Modal.createTrackedDialog('Add an integration', '', QuestionDialog, { title: _t("Add an Integration"), description:
diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index ba0663153e..f37bd4271a 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -154,7 +154,7 @@ module.exports = React.createClass({ } else { var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Invalid alias format', '', ErrorDialog, { title: _t('Invalid alias format'), description: _t('\'%(alias)s\' is not a valid format for an alias', { alias: alias }), }); @@ -170,7 +170,7 @@ module.exports = React.createClass({ } else { var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Invalid address format', '', ErrorDialog, { title: _t('Invalid address format'), description: _t('\'%(alias)s\' is not a valid format for an address', { alias: alias }), }); diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 3b8acc3f40..285d43ad6e 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -156,7 +156,7 @@ module.exports = React.createClass({ const src = (this.scalarClient !== null && this.scalarClient.hasCredentials()) ? this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId, 'add_integ') : null; - Modal.createDialog(IntegrationsManager, { + Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, { src: src, }, "mx_IntegrationsManager"); }, diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index b3831a7d0d..a2841e907f 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -367,7 +367,7 @@ module.exports = withMatrixClient(React.createClass({ onCryptoClicked: function(e) { var event = this.props.mxEvent; - Modal.createDialogAsync((cb) => { + Modal.createTrackedDialogAsync('Encrypted Event Dialog', '', (cb) => { require(['../../../async-components/views/dialogs/EncryptedEventDialog'], cb); }, { event: event, diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 290bd35483..23c5de2eaa 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -229,7 +229,7 @@ module.exports = withMatrixClient(React.createClass({ const membership = this.props.member.membership; const kickLabel = membership === "invite" ? _t("Disinvite") : _t("Kick"); const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); - Modal.createDialog(ConfirmUserActionDialog, { + Modal.createTrackedDialog('Confirm User Action Dialog', 'onKick', ConfirmUserActionDialog, { member: this.props.member, action: kickLabel, askReason: membership == "join", @@ -248,7 +248,7 @@ module.exports = withMatrixClient(React.createClass({ }, function(err) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Kick error: " + err); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to kick', err.message, ErrorDialog, { title: _t("Failed to kick"), description: ((err && err.message) ? err.message : "Operation failed"), }); @@ -262,7 +262,7 @@ module.exports = withMatrixClient(React.createClass({ onBanOrUnban: function() { const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); - Modal.createDialog(ConfirmUserActionDialog, { + Modal.createTrackedDialog('Confirm User Action Dialog', 'onBanOrUnban', ConfirmUserActionDialog, { member: this.props.member, action: this.props.member.membership == 'ban' ? _t("Unban") : _t("Ban"), askReason: this.props.member.membership != 'ban', @@ -290,7 +290,7 @@ module.exports = withMatrixClient(React.createClass({ }, function(err) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Ban error: " + err); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to ban user', err.message, ErrorDialog, { title: _t("Error"), description: _t("Failed to ban user"), }); @@ -340,7 +340,7 @@ module.exports = withMatrixClient(React.createClass({ console.log("Mute toggle success"); }, function(err) { console.error("Mute error: " + err); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to mute user', err.message, ErrorDialog, { title: _t("Error"), description: _t("Failed to mute user"), }); @@ -385,7 +385,7 @@ module.exports = withMatrixClient(React.createClass({ dis.dispatch({action: 'view_set_mxid'}); } else { console.error("Toggle moderator error:" + err); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to toggle moderator status', err.message, ErrorDialog, { title: _t("Error"), description: _t("Failed to toggle moderator status"), }); @@ -406,7 +406,7 @@ module.exports = withMatrixClient(React.createClass({ }, function(err) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Failed to change power level " + err); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to change power level', err.message, ErrorDialog, { title: _t("Error"), description: _t("Failed to change power level"), }); @@ -435,7 +435,7 @@ module.exports = withMatrixClient(React.createClass({ var myPower = powerLevelEvent.getContent().users[this.props.matrixClient.credentials.userId]; if (parseInt(myPower) === parseInt(powerLevel)) { var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - Modal.createDialog(QuestionDialog, { + Modal.createTrackedDialog('Promote to PL100 Warning', '', QuestionDialog, { title: _t("Warning!"), description:
diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 14f52706ec..51b595eab0 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -99,7 +99,7 @@ export default class MessageComposer extends React.Component { ); } - Modal.createDialog(QuestionDialog, { + Modal.createTrackedDialog('Upload Files confirmation', '', QuestionDialog, { title: _t('Upload Files'), description: (
diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index ba1673a9df..34e9e4a1e6 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -667,7 +667,7 @@ export default class MessageComposerInput extends React.Component { }, function(err) { console.error("Command failure: %s", err); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Server error', err.toString(), ErrorDialog, { title: _t("Server error"), description: ((err && err.message) ? err.message : _t("Server unavailable, overloaded, or something else went wrong.")), }); @@ -675,7 +675,7 @@ export default class MessageComposerInput extends React.Component { } else if (cmd.error) { console.error(cmd.error); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Command error', cmd.error, ErrorDialog, { title: _t("Command error"), description: cmd.error, }); diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 85aedadf64..741d7085e6 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -119,7 +119,7 @@ module.exports = React.createClass({ const errMsg = (typeof err === "string") ? err : (err.error || ""); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Failed to set avatar: " + errMsg); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to set avatar', err.toString(), ErrorDialog, { title: _t("Error"), description: _t("Failed to set avatar."), }); diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index d6a973f648..b59ad1510c 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -46,7 +46,7 @@ const BannedUser = React.createClass({ _onUnbanClick: function() { const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); - Modal.createDialog(ConfirmUserActionDialog, { + Modal.createTrackedDialog('Confirm User Action Dialog', 'onUnbanClick', ConfirmUserActionDialog, { member: this.props.member, action: _t('Unban'), danger: false, @@ -58,7 +58,7 @@ const BannedUser = React.createClass({ ).catch((err) => { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Failed to unban: " + err); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to unban', err.toString(), ErrorDialog, { title: _t('Error'), description: _t('Failed to unban'), }); @@ -423,7 +423,7 @@ module.exports = React.createClass({ ev.preventDefault(); var value = ev.target.value; - Modal.createDialog(QuestionDialog, { + Modal.createTrackedDialog('Privacy warning', '', QuestionDialog, { title: _t('Privacy warning'), description:
@@ -516,7 +516,7 @@ module.exports = React.createClass({ onManageIntegrations(ev) { ev.preventDefault(); var IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); - Modal.createDialog(IntegrationsManager, { + Modal.createTrackedDialog('Integrations Manager', 'onManageIntegrations', IntegrationsManager, { src: (this.scalarClient !== null && this.scalarClient.hasCredentials()) ? this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId) : null, @@ -549,7 +549,7 @@ module.exports = React.createClass({ }, function(err) { var errCode = err.errcode || _t('unknown error code'); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to forget room', err.toString(), ErrorDialog, { title: _t('Error'), description: _t("Failed to forget room %(errCode)s", { errCode: errCode }), }); @@ -560,7 +560,7 @@ module.exports = React.createClass({ if (!this.refs.encrypt.checked) return; var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - Modal.createDialog(QuestionDialog, { + Modal.createTrackedDialog('E2E Enable Warning', '', QuestionDialog, { title: _t('Warning!'), description: (
diff --git a/src/components/views/settings/AddPhoneNumber.js b/src/components/views/settings/AddPhoneNumber.js index 7bc551477e..bda488a412 100644 --- a/src/components/views/settings/AddPhoneNumber.js +++ b/src/components/views/settings/AddPhoneNumber.js @@ -82,7 +82,7 @@ export default withMatrixClient(React.createClass({ }).catch((err) => { console.error("Unable to add phone number: " + err); let msg = err.message; - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Add Phone Number Error', err.toString(), ErrorDialog, { title: _t("Error"), description: msg, }); @@ -107,7 +107,7 @@ export default withMatrixClient(React.createClass({ } msgElements.push(
{msg}
); } - Modal.createDialog(TextInputDialog, { + Modal.createTrackedDialog('Prompt for MSISDN Verification Code', '', TextInputDialog, { title: _t("Enter Code"), description:
{msgElements}
, button: _t("Submit"), diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js index 14ec9806b4..f3c0d9033c 100644 --- a/src/components/views/settings/ChangePassword.js +++ b/src/components/views/settings/ChangePassword.js @@ -104,7 +104,7 @@ module.exports = React.createClass({ } const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - Modal.createDialog(QuestionDialog, { + Modal.createTrackedDialog('Change Password', '', QuestionDialog, { title: _t("Warning!"), description:
@@ -164,7 +164,7 @@ module.exports = React.createClass({ const deferred = Promise.defer(); // Ask for an email otherwise the user has no way to reset their password const SetEmailDialog = sdk.getComponent("dialogs.SetEmailDialog"); - Modal.createDialog(SetEmailDialog, { + Modal.createTrackedDialog('Do you want to set an email address?', '', SetEmailDialog, { title: _t('Do you want to set an email address?'), onFinished: (confirmed) => { // ignore confirmed, setting an email is optional @@ -175,15 +175,13 @@ module.exports = React.createClass({ }, _onExportE2eKeysClicked: function() { - Modal.createDialogAsync( - (cb) => { - require.ensure(['../../../async-components/views/dialogs/ExportE2eKeysDialog'], () => { - cb(require('../../../async-components/views/dialogs/ExportE2eKeysDialog')); - }, "e2e-export"); - }, { - matrixClient: MatrixClientPeg.get(), - } - ); + Modal.createTrackedDialogAsync('Export E2E Keys', 'Change Password', (cb) => { + require.ensure(['../../../async-components/views/dialogs/ExportE2eKeysDialog'], () => { + cb(require('../../../async-components/views/dialogs/ExportE2eKeysDialog')); + }, "e2e-export"); + }, { + matrixClient: MatrixClientPeg.get(), + }); }, onClickChange: function() { diff --git a/src/components/views/settings/DevicesPanelEntry.js b/src/components/views/settings/DevicesPanelEntry.js index f295a7c2d5..69534f09b1 100644 --- a/src/components/views/settings/DevicesPanelEntry.js +++ b/src/components/views/settings/DevicesPanelEntry.js @@ -71,8 +71,8 @@ export default class DevicesPanelEntry extends React.Component { // pop up an interactive auth dialog var InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog"); - Modal.createDialog(InteractiveAuthDialog, { - title: _t("Authentication"), + Modal.createTrackedDialog('Delete Device Dialog', InteractiveAuthDialog, { + title: _t("Authentication"), matrixClient: MatrixClientPeg.get(), authData: error.data, makeRequest: this._makeDeleteRequest, diff --git a/src/createRoom.js b/src/createRoom.js index 74e4b3c2fc..2ba3bd06ef 100644 --- a/src/createRoom.js +++ b/src/createRoom.js @@ -115,7 +115,7 @@ function createRoom(opts) { action: 'join_room_error', }); console.error("Failed to create room " + roomId + " " + err); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failure to create room', err.message, ErrorDialog, { title: _t("Failure to create room"), description: _t("Server may be unavailable, overloaded, or you hit a bug."), }); diff --git a/src/stores/RoomViewStore.js b/src/stores/RoomViewStore.js index 865caa8997..e4fe1068b7 100644 --- a/src/stores/RoomViewStore.js +++ b/src/stores/RoomViewStore.js @@ -221,7 +221,7 @@ class RoomViewStore extends Store { }); const msg = err.message ? err.message : JSON.stringify(err); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createDialog(ErrorDialog, { + Modal.createTrackedDialog('Failed to join room', err.toString(), ErrorDialog, { title: _t("Failed to join room"), description: msg, }); From 25d1d21d93c9508fd3ae92f458452385e4c1bb41 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 31 Jul 2017 13:28:43 +0100 Subject: [PATCH 03/34] copy logic from RegistrationForm to detect invalid localparts --- src/components/views/dialogs/SetMxIdDialog.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/views/dialogs/SetMxIdDialog.js b/src/components/views/dialogs/SetMxIdDialog.js index 4d4f672f2b..6c3b1516d0 100644 --- a/src/components/views/dialogs/SetMxIdDialog.js +++ b/src/components/views/dialogs/SetMxIdDialog.js @@ -106,6 +106,15 @@ export default React.createClass({ }, _doUsernameCheck: function() { + // XXX: SPEC-1 + // Check if username is valid + if (encodeURIComponent(this.state.username) !== this.state.username) { + this.setState({ + usernameError: _t('User names may only contain letters, numbers, dots, hyphens and underscores.'), + }); + return Promise.resolve(); + } + // Check if username is available return this._matrixClient.isUsernameAvailable(this.state.username).then( (isAvailable) => { From f310d4446ca36be243bb92c5cfcc442a5e58aca1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 31 Jul 2017 13:31:07 +0100 Subject: [PATCH 04/34] i18n the title of the set mxid dialog --- src/components/views/dialogs/SetMxIdDialog.js | 2 +- src/i18n/strings/en_EN.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/dialogs/SetMxIdDialog.js b/src/components/views/dialogs/SetMxIdDialog.js index 6c3b1516d0..2462f24a87 100644 --- a/src/components/views/dialogs/SetMxIdDialog.js +++ b/src/components/views/dialogs/SetMxIdDialog.js @@ -251,7 +251,7 @@ export default React.createClass({ return (
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7b027b6417..fd70a49311 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -571,6 +571,7 @@ "To configure the room": "To configure the room", "to demote": "to demote", "to favourite": "to favourite", + "To get started, please pick a username!": "To get started, please pick a username!", "To invite users into the room": "To invite users into the room", "To kick users": "To kick users", "To link to a room it must have an address.": "To link to a room it must have an address.", From 62af06104dd2492b0ac5a0cf1f20b64b1ceec19e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 31 Jul 2017 14:22:05 +0100 Subject: [PATCH 05/34] resolve -> reject because semantics. --- src/components/views/dialogs/SetMxIdDialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/SetMxIdDialog.js b/src/components/views/dialogs/SetMxIdDialog.js index 2462f24a87..9fb6b838b2 100644 --- a/src/components/views/dialogs/SetMxIdDialog.js +++ b/src/components/views/dialogs/SetMxIdDialog.js @@ -112,7 +112,7 @@ export default React.createClass({ this.setState({ usernameError: _t('User names may only contain letters, numbers, dots, hyphens and underscores.'), }); - return Promise.resolve(); + return Promise.reject(); } // Check if username is available From ffdffb643d0a23c2a43e2d2b2e952fcdf7cecd06 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 4 Aug 2017 17:22:01 +0100 Subject: [PATCH 06/34] allow hiding of avatar/display name changes --- src/components/structures/UserSettings.js | 4 ++ src/i18n/strings/en_EN.json | 1 + src/shouldHideEvent.js | 48 +++++++++++------------ 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index 1e0fcff445..483aab7e58 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -85,6 +85,10 @@ const SETTINGS_LABELS = [ id: 'hideJoinLeaves', label: 'Hide join/leave messages (invites/kicks/bans unaffected)', }, + { + id: 'hideAvatarDisplaynameChanges', + label: 'Hide Avatar and Display Name changes', + }, { id: 'useCompactLayout', label: 'Use compact timeline layout', diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0402c242aa..3edebc0284 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -346,6 +346,7 @@ "Hangup": "Hangup", "Hide Apps": "Hide Apps", "Hide join/leave messages (invites/kicks/bans unaffected)": "Hide join/leave messages (invites/kicks/bans unaffected)", + "Hide Avatar and Display Name changes": "Hide Avatar and Display Name changes", "Hide read receipts": "Hide read receipts", "Hide Text Formatting Toolbar": "Hide Text Formatting Toolbar", "Historical": "Historical", diff --git a/src/shouldHideEvent.js b/src/shouldHideEvent.js index afc8fdc596..c7bb547512 100644 --- a/src/shouldHideEvent.js +++ b/src/shouldHideEvent.js @@ -14,38 +14,36 @@ limitations under the License. */ -function _isLeaveOrJoin(ev) { - const isMemberEvent = ev.getType() === 'm.room.member' && ev.getStateKey() !== undefined; - if (!isMemberEvent) { - return false; // bail early: all the checks below concern member events only - } +function memberEventDiff(ev) { + const diff = { + isMembershipEvent: ev.getType() === 'm.room.member' && ev.getStateKey() !== undefined, + }; - // TODO: These checks are done to make sure we're dealing with membership transitions not avatar changes / dupe joins - // These checks are also being done in TextForEvent and should really reside in the JS SDK as a helper function - const membership = ev.getContent().membership; - const prevMembership = ev.getPrevContent().membership; - if (membership === prevMembership && membership === 'join') { - // join -> join : This happens when display names change / avatars are set / genuine dupe joins with no changes. - // Find out which we're dealing with. - if (ev.getPrevContent().displayname !== ev.getContent().displayname) { - return false; // display name changed - } - if (ev.getPrevContent().avatar_url !== ev.getContent().avatar_url) { - return false; // avatar url changed - } - // dupe join event, fall through to hide rules - } + if (!diff.isMembershipEvent) return diff; + const content = ev.getContent(); + const prevContent = ev.getPrevContent(); - // this only applies to joins/invited joins/leaves not invites/kicks/bans - const isJoin = membership === 'join' && prevMembership !== 'ban'; - const isLeave = membership === 'leave' && ev.getStateKey() === ev.getSender(); - return isJoin || isLeave; + diff.isJoin = content.membership === 'join' && prevContent.membership !== 'ban'; + diff.isPart = content.membership === 'leave' && ev.getStateKey() === ev.getSender(); + + const isJoinToJoin = content.membership === prevContent.membership && content.membership === 'join'; + diff.isDisplaynameChange = isJoinToJoin && content.displayname !== prevContent.displayname; + diff.isAvatarChange = isJoinToJoin && content.avatar_url !== prevContent.avatar_url; + return diff; } export default function(ev, syncedSettings) { // Hide redacted events if (syncedSettings['hideRedactions'] && ev.isRedacted()) return true; - if (syncedSettings['hideJoinLeaves'] && _isLeaveOrJoin(ev)) return true; + + const eventDiff = memberEventDiff(ev); + + if (eventDiff.isMembershipEvent) { + if (syncedSettings['hideJoinLeaves'] && (eventDiff.isJoin || eventDiff.isPart)) return true; + const isMemberAvatarDisplaynameChange = eventDiff.isAvatarChange || eventDiff.isDisplaynameChange; + if (syncedSettings['hideAvatarDisplaynameChanges'] && isMemberAvatarDisplaynameChange) return true; + } + return false; } From 579090a4e307cd38403c7fed163f73b615d34c62 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@googlemail.com> Date: Wed, 9 Aug 2017 16:37:38 +0100 Subject: [PATCH 07/34] add comment --- src/components/views/dialogs/SetMxIdDialog.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/dialogs/SetMxIdDialog.js b/src/components/views/dialogs/SetMxIdDialog.js index 9fb6b838b2..554a244358 100644 --- a/src/components/views/dialogs/SetMxIdDialog.js +++ b/src/components/views/dialogs/SetMxIdDialog.js @@ -108,6 +108,7 @@ export default React.createClass({ _doUsernameCheck: function() { // XXX: SPEC-1 // Check if username is valid + // Naive impl copied from https://github.com/matrix-org/matrix-react-sdk/blob/66c3a6d9ca695780eb6b662e242e88323053ff33/src/components/views/login/RegistrationForm.js#L190 if (encodeURIComponent(this.state.username) !== this.state.username) { this.setState({ usernameError: _t('User names may only contain letters, numbers, dots, hyphens and underscores.'), From 2d47d3d2c371d5d31d1239dc5a98c2a779a07c98 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 9 Aug 2017 17:36:35 +0100 Subject: [PATCH 08/34] Hide autocomplete when RTE selection state (cursor) changes --- src/components/views/rooms/MessageComposerInput.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index b2c1436365..ab6c20684b 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -457,6 +457,19 @@ export default class MessageComposerInput extends React.Component { state.editorState = RichText.attachImmutableEntitiesToEmoji( state.editorState); + // Hide the autocomplete if the cursor location changes but the plaintext + // content stays the same. We don't hide if the pt has changed because the + // autocomplete will probably have different completions to show. + if ( + !state.editorState.getSelection().equals( + this.state.editorState.getSelection() + ) + && state.editorState.getCurrentContent().getPlainText() === + this.state.editorState.getCurrentContent().getPlainText() + ) { + this.autocomplete.hide(); + } + if (state.editorState.getCurrentContent().hasText()) { this.onTypingActivity(); } else { From e121440d05088ed7267dd40bff5ac6083974e69b Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 9 Aug 2017 18:39:06 +0100 Subject: [PATCH 09/34] Track whether the user has richtext mode enabled --- src/Analytics.js | 6 ++++++ src/components/views/rooms/MessageComposerInput.js | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/Analytics.js b/src/Analytics.js index 92691da1ea..0a31625ebc 100644 --- a/src/Analytics.js +++ b/src/Analytics.js @@ -31,6 +31,7 @@ const customVariables = { 'User Type': 3, 'Chosen Language': 4, 'Instance': 5, + 'RTE: Uses Richtext Mode': 6, }; @@ -145,6 +146,11 @@ class Analytics { if (this.disabled) return; this._setVisitVariable('User Type', guest ? 'Guest' : 'Logged In'); } + + setRichtextMode(state) { + if (this.disabled) return; + this._setVisitVariable('RTE: Uses Richtext Mode', state ? 'on' : 'off'); + } } if (!global.mxAnalytics) { diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index b2c1436365..aa4acd7655 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -31,6 +31,7 @@ import KeyCode from '../../../KeyCode'; import Modal from '../../../Modal'; import sdk from '../../../index'; import { _t } from '../../../languageHandler'; +import Analytics from '../../../Analytics'; import dis from '../../../dispatcher'; import UserSettingsStore from '../../../UserSettingsStore'; @@ -513,6 +514,8 @@ export default class MessageComposerInput extends React.Component { contentState = ContentState.createFromText(markdown); } + Analytics.setRichtextMode(enabled); + this.setState({ editorState: this.createEditorState(enabled, contentState), isRichtextEnabled: enabled, From fc6977e68d5be61a135a47d434cec36f2d61173c Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 9 Aug 2017 19:00:38 +0100 Subject: [PATCH 10/34] Track RT mode once we've retrieved the setting from account data --- src/components/views/rooms/MessageComposerInput.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index aa4acd7655..856c7aaa88 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -161,6 +161,8 @@ export default class MessageComposerInput extends React.Component { const isRichtextEnabled = UserSettingsStore.getSyncedSetting('MessageComposerInput.isRichTextEnabled', false); + Analytics.setRichtextMode(isRichtextEnabled); + this.state = { // whether we're in rich text or markdown mode isRichtextEnabled, From 678c472b753940553d68445bd10ded0be27c9295 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Thu, 10 Aug 2017 10:14:14 +0100 Subject: [PATCH 11/34] Quote by taking the innerText of eventTiles because using `body` gives inconsistent results - sometimes it will contain markdown and sometimes not, and this may not correspond with the `formatted_body`. TODO: Do quoting proper - using `in_response_to`. --- src/components/views/messages/TextualBody.js | 15 +++++++++------ .../views/rooms/MessageComposerInput.js | 3 +-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 27dba76146..18265ce559 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -275,18 +275,21 @@ module.exports = React.createClass({ }, getEventTileOps: function() { - var self = this; return { - isWidgetHidden: function() { - return self.state.widgetHidden; + isWidgetHidden: () => { + return this.state.widgetHidden; }, - unhideWidget: function() { - self.setState({ widgetHidden: false }); + unhideWidget: () => { + this.setState({ widgetHidden: false }); if (global.localStorage) { - global.localStorage.removeItem("hide_preview_" + self.props.mxEvent.getId()); + global.localStorage.removeItem("hide_preview_" + this.props.mxEvent.getId()); } }, + + getInnerText: () => { + return this.refs.content.innerText; + } }; }, diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index b2c1436365..950ccfc21f 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -280,11 +280,10 @@ export default class MessageComposerInput extends React.Component { } break; case 'quote': { - let {body} = payload.event.getContent(); /// XXX: Not doing rich-text quoting from formatted-body because draft-js /// has regressed such that when links are quoted, errors are thrown. See /// https://github.com/vector-im/riot-web/issues/4756. - body = escape(body); + let body = escape(payload.text); if (body) { let content = RichText.htmlToContentState(`
${body}
`); if (!this.state.isRichtextEnabled) { From 07633fe67f726c84b47fbc41984122f1ffd8ff61 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Thu, 10 Aug 2017 13:12:50 +0100 Subject: [PATCH 12/34] At /user, view member of current room With the fallback of existing behaviour, which is UserView (no middle panel and no avatar, display name). To improve, MemberInfo should probably track the current roomId and userId and then update the view asynchronously by re-fetching the member object when either roomId or userId change. Also, it should be hitting the profile API to get the user's avatar if a room hasn't been specified. --- src/components/structures/LoggedInView.js | 4 +-- src/components/structures/MatrixChat.js | 44 +++++++++++++++++------ 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 093fae5d7b..0790a5766e 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -301,13 +301,13 @@ export default React.createClass({ case PageTypes.UserView: page_element = null; // deliberately null for now - right_panel = ; + right_panel = ; break; case PageTypes.GroupView: page_element = ; - //right_panel = ; + //right_panel = ; break; } diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index cb6419c9e8..784526b09b 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -448,6 +448,7 @@ module.exports = React.createClass({ }); }, 0); } + this.notifyNewScreen('user/' + payload.member.userId); break; case 'view_room': // Takes either a room ID or room alias: if switching to a room the client is already @@ -1203,21 +1204,44 @@ module.exports = React.createClass({ } else if (screen.indexOf('user/') == 0) { const userId = screen.substring(5); - if (params.action === 'chat') { - this._chatCreateOrReuse(userId); - return; - } + // Wait for the first sync so that `getRoom` gives us a room object if it's + // in the sync response + const waitFor = this.firstSyncPromise ? + this.firstSyncPromise.promise : Promise.resolve(); + waitFor.then(() => { + if (params.action === 'chat') { + this._chatCreateOrReuse(userId); + return; + } - this.setState({ viewUserId: userId }); - this._setPage(PageTypes.UserView); - this.notifyNewScreen('user/' + userId); - const member = new Matrix.RoomMember(null, userId); - if (member) { + // Get the member object for the current room, if a current room is set or + // we have a last_room in localStorage. The user might not be a member of + // this room (in which case member will be falsey). + let member; + const roomId = this.state.currentRoomId || localStorage.getItem('mx_last_room_id'); + if (roomId) { + const room = MatrixClientPeg.get().getRoom(roomId); + if (room) { + member = room.getMember(userId); + } + } + + if (member) { + // This user is a member of this room, so view the room + dis.dispatch({ + action: 'view_room', + room_id: roomId, + }); + } else { + // This user is not a member of this room, show the user view + member = new Matrix.RoomMember(roomId, userId); + this._setPage(PageTypes.UserView); + } dis.dispatch({ action: 'view_user', member: member, }); - } + }); } else if (screen.indexOf('group/') == 0) { const groupId = screen.substring(6); From 60c1ba4f4d48c33eaf84a5b6ab66721903db87ea Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 10 Aug 2017 14:29:10 +0200 Subject: [PATCH 13/34] Add LanguageDropdown to LoginPage (#1284) --- src/components/structures/login/Login.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/components/structures/login/Login.js b/src/components/structures/login/Login.js index a081d2a205..a6c0a70c66 100644 --- a/src/components/structures/login/Login.js +++ b/src/components/structures/login/Login.js @@ -19,8 +19,11 @@ limitations under the License. import React from 'react'; import { _t, _tJsx } from '../../../languageHandler'; +import * as languageHandler from '../../../languageHandler'; import sdk from '../../../index'; import Login from '../../../Login'; +import UserSettingsStore from '../../../UserSettingsStore'; +import PlatformPeg from '../../../PlatformPeg'; // For validating phone numbers without country codes const PHONE_NUMBER_REGEX = /^[0-9\(\)\-\s]*$/; @@ -306,6 +309,23 @@ module.exports = React.createClass({ } }, + _onLanguageChange: function(newLang) { + if(languageHandler.getCurrentLanguage() !== newLang) { + UserSettingsStore.setLocalSetting('language', newLang); + PlatformPeg.get().reload(); + } + }, + + _renderLanguageSetting: function() { + const LanguageDropdown = sdk.getComponent('views.elements.LanguageDropdown'); + return
+ +
; + }, + render: function() { const Loader = sdk.getComponent("elements.Spinner"); const LoginHeader = sdk.getComponent("login.LoginHeader"); @@ -354,6 +374,7 @@ module.exports = React.createClass({ { loginAsGuestJsx } { returnToAppJsx } + { this._renderLanguageSetting() }
From 24599ace326fb7f9c1a182757431b45daaa279c5 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 10 Aug 2017 13:49:11 +0100 Subject: [PATCH 14/34] don't track error messages Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MatrixChat.js | 4 ++-- src/components/structures/RoomView.js | 2 +- src/components/structures/UserSettings.js | 10 +++++----- src/components/views/dialogs/ChatInviteDialog.js | 6 +++--- src/components/views/dialogs/ErrorDialog.js | 2 +- src/components/views/dialogs/SetEmailDialog.js | 4 ++-- src/components/views/messages/MFileBody.js | 2 +- src/components/views/rooms/MessageComposerInput.js | 2 +- src/components/views/rooms/RoomHeader.js | 2 +- src/components/views/rooms/RoomSettings.js | 4 ++-- src/components/views/settings/AddPhoneNumber.js | 2 +- src/stores/RoomViewStore.js | 2 +- 12 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index f042b41991..be747cc4b0 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -426,7 +426,7 @@ module.exports = React.createClass({ } }, (err) => { modal.close(); - Modal.createTrackedDialog('Failed to reject invitation', err.toString(), ErrorDialog, { + Modal.createTrackedDialog('Failed to reject invitation', '', ErrorDialog, { title: _t('Failed to reject invitation'), description: err.toString(), }); @@ -896,7 +896,7 @@ module.exports = React.createClass({ }, (err) => { modal.close(); console.error("Failed to leave room " + roomId + " " + err); - Modal.createTrackedDialog('Failed to leave room', err.toString(), ErrorDialog, { + Modal.createTrackedDialog('Failed to leave room', '', ErrorDialog, { title: _t("Failed to leave room"), description: (err && err.message ? err.message : _t("Server may be unavailable, overloaded, or you hit a bug.")), diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 40dbc93071..4aee44d0c2 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -1195,7 +1195,7 @@ module.exports = React.createClass({ }, function(err) { var errCode = err.errcode || _t("unknown error code"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to forget room', err.toString(), ErrorDialog, { + Modal.createTrackedDialog('Failed to forget room', '', ErrorDialog, { title: _t("Error"), description: _t("Failed to forget room %(errCode)s", { errCode: errCode }), }); diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index 9a0567ec30..cc385790a8 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -364,7 +364,7 @@ module.exports = React.createClass({ // const errMsg = (typeof err === "string") ? err : (err.error || ""); console.error("Failed to set avatar: " + err); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to set avatar', err.toString(), ErrorDialog, { + Modal.createTrackedDialog('Failed to set avatar', '', ErrorDialog, { title: _t("Failed to set avatar."), description: ((err && err.message) ? err.message : _t("Operation failed")), }); @@ -409,7 +409,7 @@ module.exports = React.createClass({ } const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Failed to change password: " + errMsg); - Modal.createTrackedDialog('Failed to change password', err.toString(), ErrorDialog, { + Modal.createTrackedDialog('Failed to change password', '', ErrorDialog, { title: _t("Error"), description: errMsg, }); @@ -464,7 +464,7 @@ module.exports = React.createClass({ }, (err) => { this.setState({email_add_pending: false}); console.error("Unable to add email address " + emailAddress + " " + err); - Modal.createTrackedDialog('Unable to add email address', err.toString(), ErrorDialog, { + Modal.createTrackedDialog('Unable to add email address', '', ErrorDialog, { title: _t("Unable to add email address"), description: ((err && err.message) ? err.message : _t("Operation failed")), }); @@ -489,7 +489,7 @@ module.exports = React.createClass({ }).catch((err) => { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Unable to remove contact information: " + err); - Modal.createTrackedDialog('Remove 3pid failed', err.toString(), ErrorDialog, { + Modal.createTrackedDialog('Remove 3pid failed', '', ErrorDialog, { title: _t("Unable to remove contact information"), description: ((err && err.message) ? err.message : _t("Operation failed")), }); @@ -530,7 +530,7 @@ module.exports = React.createClass({ } else { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Unable to verify email address: " + err); - Modal.createTrackedDialog('Unable to verify email address', err.toString(), ErrorDialog, { + Modal.createTrackedDialog('Unable to verify email address', '', ErrorDialog, { title: _t("Unable to verify email address."), description: ((err && err.message) ? err.message : _t("Operation failed")), }); diff --git a/src/components/views/dialogs/ChatInviteDialog.js b/src/components/views/dialogs/ChatInviteDialog.js index 156d493fee..728860edec 100644 --- a/src/components/views/dialogs/ChatInviteDialog.js +++ b/src/components/views/dialogs/ChatInviteDialog.js @@ -367,7 +367,7 @@ module.exports = React.createClass({ .catch(function(err) { console.error(err.stack); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to invite', err.toString(), ErrorDialog, { + Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, { title: _t("Failed to invite"), description: ((err && err.message) ? err.message : _t("Operation failed")), }); @@ -380,7 +380,7 @@ module.exports = React.createClass({ .catch(function(err) { console.error(err.stack); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to invite user', err.toString(), ErrorDialog, { + Modal.createTrackedDialog('Failed to invite user', '', ErrorDialog, { title: _t("Failed to invite user"), description: ((err && err.message) ? err.message : _t("Operation failed")), }); @@ -401,7 +401,7 @@ module.exports = React.createClass({ .catch(function(err) { console.error(err.stack); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to invite', err.toString(), ErrorDialog, { + Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, { title: _t("Failed to invite"), description: ((err && err.message) ? err.message : _t("Operation failed")), }); diff --git a/src/components/views/dialogs/ErrorDialog.js b/src/components/views/dialogs/ErrorDialog.js index 889549369d..beca107252 100644 --- a/src/components/views/dialogs/ErrorDialog.js +++ b/src/components/views/dialogs/ErrorDialog.js @@ -16,7 +16,7 @@ limitations under the License. /* * Usage: - * Modal.createTrackedDialog('An Identifier', err.toString(), ErrorDialog, { + * Modal.createTrackedDialog('An Identifier', 'some detail', ErrorDialog, { * title: "some text", (default: "Error") * description: "some more text", * button: "Button Text", diff --git a/src/components/views/dialogs/SetEmailDialog.js b/src/components/views/dialogs/SetEmailDialog.js index b5efbab8b7..a16b32d128 100644 --- a/src/components/views/dialogs/SetEmailDialog.js +++ b/src/components/views/dialogs/SetEmailDialog.js @@ -77,7 +77,7 @@ export default React.createClass({ }, (err) => { this.setState({emailBusy: false}); console.error("Unable to add email address " + emailAddress + " " + err); - Modal.createTrackedDialog('Unable to add email address', err.toString(), ErrorDialog, { + Modal.createTrackedDialog('Unable to add email address', '', ErrorDialog, { title: _t("Unable to add email address"), description: ((err && err.message) ? err.message : _t("Operation failed")), }); @@ -115,7 +115,7 @@ export default React.createClass({ } else { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Unable to verify email address: " + err); - Modal.createTrackedDialog('Unable to verify email address', err.toString(), ErrorDialog, { + Modal.createTrackedDialog('Unable to verify email address', '', ErrorDialog, { title: _t("Unable to verify email address."), description: ((err && err.message) ? err.message : _t("Operation failed")), }); diff --git a/src/components/views/messages/MFileBody.js b/src/components/views/messages/MFileBody.js index b300e41e50..53c36f234c 100644 --- a/src/components/views/messages/MFileBody.js +++ b/src/components/views/messages/MFileBody.js @@ -282,7 +282,7 @@ module.exports = React.createClass({ }); }).catch((err) => { console.warn("Unable to decrypt attachment: ", err); - Modal.createTrackedDialog('Error decrypting attachment', err.toString(), ErrorDialog, { + Modal.createTrackedDialog('Error decrypting attachment', '', ErrorDialog, { title: _t("Error"), description: _t("Error decrypting attachment"), }); diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index c14b9ebf23..c6601f41b1 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -673,7 +673,7 @@ export default class MessageComposerInput extends React.Component { }, function(err) { console.error("Command failure: %s", err); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Server error', err.toString(), ErrorDialog, { + Modal.createTrackedDialog('Server error', '', ErrorDialog, { title: _t("Server error"), description: ((err && err.message) ? err.message : _t("Server unavailable, overloaded, or something else went wrong.")), }); diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 741d7085e6..edd89e4a35 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -119,7 +119,7 @@ module.exports = React.createClass({ const errMsg = (typeof err === "string") ? err : (err.error || ""); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Failed to set avatar: " + errMsg); - Modal.createTrackedDialog('Failed to set avatar', err.toString(), ErrorDialog, { + Modal.createTrackedDialog('Failed to set avatar', '', ErrorDialog, { title: _t("Error"), description: _t("Failed to set avatar."), }); diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index b59ad1510c..58473f1fb3 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -58,7 +58,7 @@ const BannedUser = React.createClass({ ).catch((err) => { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Failed to unban: " + err); - Modal.createTrackedDialog('Failed to unban', err.toString(), ErrorDialog, { + Modal.createTrackedDialog('Failed to unban', '', ErrorDialog, { title: _t('Error'), description: _t('Failed to unban'), }); @@ -549,7 +549,7 @@ module.exports = React.createClass({ }, function(err) { var errCode = err.errcode || _t('unknown error code'); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to forget room', err.toString(), ErrorDialog, { + Modal.createTrackedDialog('Failed to forget room', '', ErrorDialog, { title: _t('Error'), description: _t("Failed to forget room %(errCode)s", { errCode: errCode }), }); diff --git a/src/components/views/settings/AddPhoneNumber.js b/src/components/views/settings/AddPhoneNumber.js index bda488a412..16e768a23f 100644 --- a/src/components/views/settings/AddPhoneNumber.js +++ b/src/components/views/settings/AddPhoneNumber.js @@ -82,7 +82,7 @@ export default withMatrixClient(React.createClass({ }).catch((err) => { console.error("Unable to add phone number: " + err); let msg = err.message; - Modal.createTrackedDialog('Add Phone Number Error', err.toString(), ErrorDialog, { + Modal.createTrackedDialog('Add Phone Number Error', '', ErrorDialog, { title: _t("Error"), description: msg, }); diff --git a/src/stores/RoomViewStore.js b/src/stores/RoomViewStore.js index e4fe1068b7..bd9d3ea0fa 100644 --- a/src/stores/RoomViewStore.js +++ b/src/stores/RoomViewStore.js @@ -221,7 +221,7 @@ class RoomViewStore extends Store { }); const msg = err.message ? err.message : JSON.stringify(err); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to join room', err.toString(), ErrorDialog, { + Modal.createTrackedDialog('Failed to join room', '', ErrorDialog, { title: _t("Failed to join room"), description: msg, }); From 67f9c3774df15d59eb583e74ff870ee23372e02e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 10 Aug 2017 13:51:47 +0100 Subject: [PATCH 15/34] make string more human-friendly Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/dialogs/SetEmailDialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/SetEmailDialog.js b/src/components/views/dialogs/SetEmailDialog.js index a16b32d128..ed5cef2f67 100644 --- a/src/components/views/dialogs/SetEmailDialog.js +++ b/src/components/views/dialogs/SetEmailDialog.js @@ -106,7 +106,7 @@ export default React.createClass({ const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const message = _t("Unable to verify email address.") + " " + _t("Please check your email and click on the link it contains. Once this is done, click continue."); - Modal.createTrackedDialog('Verification Pending', 'M_THREEPID_AUTH_FAILED', QuestionDialog, { + Modal.createTrackedDialog('Verification Pending', '3pid Auth Failed', QuestionDialog, { title: _t("Verification Pending"), description: message, button: _t('Continue'), From 1603360c138262a7e20c11b138b0150f328cdaa3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 10 Aug 2017 13:58:19 +0100 Subject: [PATCH 16/34] s/Membership/Member/ Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/shouldHideEvent.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/shouldHideEvent.js b/src/shouldHideEvent.js index c7bb547512..8554557b38 100644 --- a/src/shouldHideEvent.js +++ b/src/shouldHideEvent.js @@ -16,10 +16,10 @@ function memberEventDiff(ev) { const diff = { - isMembershipEvent: ev.getType() === 'm.room.member' && ev.getStateKey() !== undefined, + isMemberEvent: ev.getType() === 'm.room.member' && ev.getStateKey() !== undefined, }; - if (!diff.isMembershipEvent) return diff; + if (!diff.isMemberEvent) return diff; const content = ev.getContent(); const prevContent = ev.getPrevContent(); @@ -39,7 +39,7 @@ export default function(ev, syncedSettings) { const eventDiff = memberEventDiff(ev); - if (eventDiff.isMembershipEvent) { + if (eventDiff.isMemberEvent) { if (syncedSettings['hideJoinLeaves'] && (eventDiff.isJoin || eventDiff.isPart)) return true; const isMemberAvatarDisplaynameChange = eventDiff.isAvatarChange || eventDiff.isDisplaynameChange; if (syncedSettings['hideAvatarDisplaynameChanges'] && isMemberAvatarDisplaynameChange) return true; From ab3abd2f7f5a1b42c2dd5873b8f92e68922b63bd Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 10 Aug 2017 13:59:55 +0100 Subject: [PATCH 17/34] fix string casing Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/UserSettings.js | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index 483aab7e58..3fd4a70c52 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -87,7 +87,7 @@ const SETTINGS_LABELS = [ }, { id: 'hideAvatarDisplaynameChanges', - label: 'Hide Avatar and Display Name changes', + label: 'Hide avatar and display name changes', }, { id: 'useCompactLayout', diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3edebc0284..c5cd6b516d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -346,7 +346,7 @@ "Hangup": "Hangup", "Hide Apps": "Hide Apps", "Hide join/leave messages (invites/kicks/bans unaffected)": "Hide join/leave messages (invites/kicks/bans unaffected)", - "Hide Avatar and Display Name changes": "Hide Avatar and Display Name changes", + "Hide avatar and display name changes": "Hide avatar and display name changes", "Hide read receipts": "Hide read receipts", "Hide Text Formatting Toolbar": "Hide Text Formatting Toolbar", "Historical": "Historical", From a6064c53d3985027f5d8fedad284077889949df3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 10 Aug 2017 14:00:26 +0100 Subject: [PATCH 18/34] export shouldHideEvent fn named Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/shouldHideEvent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shouldHideEvent.js b/src/shouldHideEvent.js index 8554557b38..36aa2ae17d 100644 --- a/src/shouldHideEvent.js +++ b/src/shouldHideEvent.js @@ -33,7 +33,7 @@ function memberEventDiff(ev) { return diff; } -export default function(ev, syncedSettings) { +export default function shouldHideEvent(ev, syncedSettings) { // Hide redacted events if (syncedSettings['hideRedactions'] && ev.isRedacted()) return true; From 625ca96d864c1da6e4c054680e65c8f34f8996cc Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 10 Aug 2017 14:01:24 +0100 Subject: [PATCH 19/34] add comment about why state key must be not undefined Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/shouldHideEvent.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shouldHideEvent.js b/src/shouldHideEvent.js index 36aa2ae17d..0eec756390 100644 --- a/src/shouldHideEvent.js +++ b/src/shouldHideEvent.js @@ -16,6 +16,7 @@ function memberEventDiff(ev) { const diff = { + // a Member Event is a State Event and so its State Key must not be undefined. isMemberEvent: ev.getType() === 'm.room.member' && ev.getStateKey() !== undefined, }; From 56ea528f43fc2c7eb9fd530f6c19d55d3fe607ba Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 10 Aug 2017 15:17:52 +0100 Subject: [PATCH 20/34] don't track error messages .2 Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/ContentMessages.js | 2 +- src/Lifecycle.js | 2 +- src/components/structures/GroupView.js | 4 ++-- src/components/structures/RoomView.js | 6 +++--- src/components/structures/TimelinePanel.js | 2 +- src/components/structures/UserSettings.js | 2 +- src/components/views/rooms/MemberInfo.js | 10 +++++----- src/createRoom.js | 2 +- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/ContentMessages.js b/src/ContentMessages.js index 1bd1332ab3..93057fafed 100644 --- a/src/ContentMessages.js +++ b/src/ContentMessages.js @@ -360,7 +360,7 @@ class ContentMessages { desc = _t('The file \'%(fileName)s\' exceeds this home server\'s size limit for uploads', {fileName: upload.fileName}); } var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Upload failed', err.message, ErrorDialog, { + Modal.createTrackedDialog('Upload failed', '', ErrorDialog, { title: _t('Upload Failed'), description: desc, }); diff --git a/src/Lifecycle.js b/src/Lifecycle.js index f4d1eeef08..4d8911f7a6 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -240,7 +240,7 @@ function _handleRestoreFailure(e) { const SessionRestoreErrorDialog = sdk.getComponent('views.dialogs.SessionRestoreErrorDialog'); - Modal.createTrackedDialog('Session Restore Error', e.message, SessionRestoreErrorDialog, { + Modal.createTrackedDialog('Session Restore Error', '', SessionRestoreErrorDialog, { error: e.message, onFinished: (success) => { def.resolve(success); diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index fff61b1c24..20fc4841ba 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -266,7 +266,7 @@ export default React.createClass({ this.setState({uploadingAvatar: false}); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Failed to upload avatar image", e); - Modal.createTrackedDialog('Failed to upload image', e.toString(), ErrorDialog, { + Modal.createTrackedDialog('Failed to upload image', '', ErrorDialog, { title: _t('Error'), description: _t('Failed to upload image'), }); @@ -288,7 +288,7 @@ export default React.createClass({ }); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Failed to save group profile", e); - Modal.createTrackedDialog('Failed to update group', e.toString(), ErrorDialog, { + Modal.createTrackedDialog('Failed to update group', '', ErrorDialog, { title: _t('Error'), description: _t('Failed to update group'), }); diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 4aee44d0c2..f825d1efbb 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -934,7 +934,7 @@ module.exports = React.createClass({ } const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Failed to upload file " + file + " " + error); - Modal.createTrackedDialog('Failed to upload file', error.toString(), ErrorDialog, { + Modal.createTrackedDialog('Failed to upload file', '', ErrorDialog, { title: _t('Failed to upload file'), description: ((error && error.message) ? error.message : _t("Server may be unavailable, overloaded, or the file too big")), }); @@ -1021,7 +1021,7 @@ module.exports = React.createClass({ }, function(error) { var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Search failed: " + error); - Modal.createTrackedDialog('Search failed', error.toString(), ErrorDialog, { + Modal.createTrackedDialog('Search failed', '', ErrorDialog, { title: _t("Search failed"), description: ((error && error.message) ? error.message : _t("Server may be unavailable, overloaded, or search timed out :(")), }); @@ -1217,7 +1217,7 @@ module.exports = React.createClass({ var msg = error.message ? error.message : JSON.stringify(error); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to reject invite', error.toString(), ErrorDialog, { + Modal.createTrackedDialog('Failed to reject invite', '', ErrorDialog, { title: _t("Failed to reject invite"), description: msg, }); diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 7cd67f3da8..6be31361dd 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -923,7 +923,7 @@ var TimelinePanel = React.createClass({ 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.createTrackedDialog('Failed to load timeline position', error.toString(), ErrorDialog, { + Modal.createTrackedDialog('Failed to load timeline position', '', ErrorDialog, { title: _t("Failed to load timeline position"), description: message, onFinished: onFinished, diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index 513d754277..e6568f85ec 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -335,7 +335,7 @@ module.exports = React.createClass({ }, function(error) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Failed to load user settings: " + error); - Modal.createTrackedDialog('Can\'t load user settings', error.toString(), ErrorDialog, { + Modal.createTrackedDialog('Can\'t load user settings', '', ErrorDialog, { title: _t("Can't load user settings"), description: ((error && error.message) ? error.message : _t("Server may be unavailable or overloaded")), }); diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 23c5de2eaa..64eeddb406 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -248,7 +248,7 @@ module.exports = withMatrixClient(React.createClass({ }, function(err) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Kick error: " + err); - Modal.createTrackedDialog('Failed to kick', err.message, ErrorDialog, { + Modal.createTrackedDialog('Failed to kick', '', ErrorDialog, { title: _t("Failed to kick"), description: ((err && err.message) ? err.message : "Operation failed"), }); @@ -290,7 +290,7 @@ module.exports = withMatrixClient(React.createClass({ }, function(err) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Ban error: " + err); - Modal.createTrackedDialog('Failed to ban user', err.message, ErrorDialog, { + Modal.createTrackedDialog('Failed to ban user', '', ErrorDialog, { title: _t("Error"), description: _t("Failed to ban user"), }); @@ -340,7 +340,7 @@ module.exports = withMatrixClient(React.createClass({ console.log("Mute toggle success"); }, function(err) { console.error("Mute error: " + err); - Modal.createTrackedDialog('Failed to mute user', err.message, ErrorDialog, { + Modal.createTrackedDialog('Failed to mute user', '', ErrorDialog, { title: _t("Error"), description: _t("Failed to mute user"), }); @@ -385,7 +385,7 @@ module.exports = withMatrixClient(React.createClass({ dis.dispatch({action: 'view_set_mxid'}); } else { console.error("Toggle moderator error:" + err); - Modal.createTrackedDialog('Failed to toggle moderator status', err.message, ErrorDialog, { + Modal.createTrackedDialog('Failed to toggle moderator status', '', ErrorDialog, { title: _t("Error"), description: _t("Failed to toggle moderator status"), }); @@ -406,7 +406,7 @@ module.exports = withMatrixClient(React.createClass({ }, function(err) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Failed to change power level " + err); - Modal.createTrackedDialog('Failed to change power level', err.message, ErrorDialog, { + Modal.createTrackedDialog('Failed to change power level', '', ErrorDialog, { title: _t("Error"), description: _t("Failed to change power level"), }); diff --git a/src/createRoom.js b/src/createRoom.js index 2ba3bd06ef..944c6a70a1 100644 --- a/src/createRoom.js +++ b/src/createRoom.js @@ -115,7 +115,7 @@ function createRoom(opts) { action: 'join_room_error', }); console.error("Failed to create room " + roomId + " " + err); - Modal.createTrackedDialog('Failure to create room', err.message, ErrorDialog, { + Modal.createTrackedDialog('Failure to create room', '', ErrorDialog, { title: _t("Failure to create room"), description: _t("Server may be unavailable, overloaded, or you hit a bug."), }); From 41843f021daae720441a834a05f2861504d7b425 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 10 Aug 2017 15:21:01 +0100 Subject: [PATCH 21/34] don't track two more potential risks Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/login/ForgotPassword.js | 3 +-- src/components/views/rooms/MessageComposerInput.js | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/structures/login/ForgotPassword.js b/src/components/structures/login/ForgotPassword.js index 2a6d5042dd..320d21f5b4 100644 --- a/src/components/structures/login/ForgotPassword.js +++ b/src/components/structures/login/ForgotPassword.js @@ -150,8 +150,7 @@ module.exports = React.createClass({ showErrorDialog: function(body, title) { var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - // TODO this will still lead to i18n in Analytics. - Modal.createTrackedDialog('Forgot Password Error', body, ErrorDialog, { + Modal.createTrackedDialog('Forgot Password Error', '', ErrorDialog, { title: title, description: body, }); diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index 662d13ecfc..63be026608 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -729,7 +729,8 @@ export default class MessageComposerInput extends React.Component { } else if (cmd.error) { console.error(cmd.error); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Command error', cmd.error, ErrorDialog, { + // TODO possibly track which command they ran (not its Arguments) here + Modal.createTrackedDialog('Command error', '', ErrorDialog, { title: _t("Command error"), description: cmd.error, }); From 5450d6b9cafb2eba2ebb5bdba7e3d03676124a82 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 10 Aug 2017 15:22:53 +0100 Subject: [PATCH 22/34] remove redundant check and add comment Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/shouldHideEvent.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shouldHideEvent.js b/src/shouldHideEvent.js index 0eec756390..1501e28875 100644 --- a/src/shouldHideEvent.js +++ b/src/shouldHideEvent.js @@ -16,10 +16,10 @@ function memberEventDiff(ev) { const diff = { - // a Member Event is a State Event and so its State Key must not be undefined. - isMemberEvent: ev.getType() === 'm.room.member' && ev.getStateKey() !== undefined, + isMemberEvent: ev.getType() === 'm.room.member', }; + // If is not a Member Event then the other checks do not apply, so bail early. if (!diff.isMemberEvent) return diff; const content = ev.getContent(); From 89254e77f45998c8b701f19163934cd16ccb534c Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Thu, 10 Aug 2017 15:23:14 +0100 Subject: [PATCH 23/34] When no member, use `null` `roomId` --- src/components/structures/MatrixChat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 784526b09b..755109c5de 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1234,7 +1234,7 @@ module.exports = React.createClass({ }); } else { // This user is not a member of this room, show the user view - member = new Matrix.RoomMember(roomId, userId); + member = new Matrix.RoomMember(null, userId); this._setPage(PageTypes.UserView); } dis.dispatch({ From cbd8018ac83863b28a83701c0f0c742b6bc9399f Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 14 Aug 2017 12:26:31 +0100 Subject: [PATCH 24/34] Support semi-perma-disabling of lab features Adding `override: true` will remove the feature from the labs section, and force Riot to always use the default value (i.e. ignoring localStorage). This is useful removing features entirely when they might be deliberately not working but we still want to do a release. --- src/UserSettingsStore.js | 36 ++++++++++++++++------- src/components/structures/UserSettings.js | 16 ++++++++-- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js index 4c66c90598..bf6d7e93cf 100644 --- a/src/UserSettingsStore.js +++ b/src/UserSettingsStore.js @@ -171,22 +171,36 @@ export default { localStorage.setItem('mx_local_settings', JSON.stringify(settings)); }, - isFeatureEnabled: function(feature: string): boolean { + getFeatureById(feature: string) { + for (let i = 0; i < this.LABS_FEATURES.length; i++) { + const f = this.LABS_FEATURES[i]; + if (f.id === feature) { + return f; + } + } + return null; + }, + + isFeatureEnabled: function(featureId: string): boolean { // Disable labs for guests. if (MatrixClientPeg.get().isGuest()) return false; - if (localStorage.getItem(`mx_labs_feature_${feature}`) === null) { - for (let i = 0; i < this.LABS_FEATURES.length; i++) { - const f = this.LABS_FEATURES[i]; - if (f.id === feature) { - return f.default; - } - } + const feature = this.getFeatureById(featureId); + if (!feature) { + console.warn('Unknown feature'); + return false; } - return localStorage.getItem(`mx_labs_feature_${feature}`) === 'true'; + // Return the default if this feature has an override to be the default value or + // if the feature has never been toggled and is therefore not in localStorage + if (Object.keys(feature).includes('override') || + localStorage.getItem(`mx_labs_feature_${featureId}`) === null + ) { + return feature.default; + } + return localStorage.getItem(`mx_labs_feature_${featureId}`) === 'true'; }, - setFeatureEnabled: function(feature: string, enabled: boolean) { - localStorage.setItem(`mx_labs_feature_${feature}`, enabled); + setFeatureEnabled: function(featureId: string, enabled: boolean) { + localStorage.setItem(`mx_labs_feature_${featureId}`, enabled); }, }; diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index e2463a3ac7..c33410e857 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -859,7 +859,13 @@ module.exports = React.createClass({ if (this.props.enableLabs === false) return null; UserSettingsStore.doTranslations(); - const features = UserSettingsStore.LABS_FEATURES.map((feature) => { + const features = []; + UserSettingsStore.LABS_FEATURES.forEach((feature) => { + // This feature has an override and will be set to the default, so do not + // show it here. + if (feature.override) { + return; + } // TODO: this ought to be a separate component so that we don't need // to rebind the onChange each time we render const onChange = (e) => { @@ -867,7 +873,7 @@ module.exports = React.createClass({ this.forceUpdate(); }; - return ( + features.push(
); }); + + // No labs section when there are no features in labs + if (features.length === 0) { + return null; + } + return (

{ _t("Labs") }

From c97ae5c2e15581ae98f102ecc3e4863684e413b5 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 14 Aug 2017 12:29:42 +0100 Subject: [PATCH 25/34] Override matrix-apps to be disabled - ignores localStorage setting - hides feature from labs section in user settings --- src/UserSettingsStore.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js index bf6d7e93cf..ea060ff8d6 100644 --- a/src/UserSettingsStore.js +++ b/src/UserSettingsStore.js @@ -29,6 +29,9 @@ export default { name: "-", id: 'matrix_apps', default: false, + + // XXX: Always use default, ignore localStorage and remove from labs + override: true, }, ], From 3d5d7fa9c8cd4bb62fe8fcd14b7fb2afa51deccd Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 14 Aug 2017 13:59:12 +0100 Subject: [PATCH 26/34] Lint --- src/components/structures/UserSettings.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index c33410e857..3c139f77a6 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -883,8 +883,7 @@ module.exports = React.createClass({ onChange={ onChange } /> -
- ); +
); }); // No labs section when there are no features in labs From d9e8292a5e1fe8805e3e07718fef957588902ebd Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 14 Aug 2017 14:06:54 +0100 Subject: [PATCH 27/34] Revert "At /user, view member of current room" --- src/components/structures/LoggedInView.js | 4 +-- src/components/structures/MatrixChat.js | 44 ++++++----------------- 2 files changed, 12 insertions(+), 36 deletions(-) diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 0790a5766e..093fae5d7b 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -301,13 +301,13 @@ export default React.createClass({ case PageTypes.UserView: page_element = null; // deliberately null for now - right_panel = ; + right_panel = ; break; case PageTypes.GroupView: page_element = ; - //right_panel = ; + //right_panel = ; break; } diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 930c618f7a..6fdec80f38 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -448,7 +448,6 @@ module.exports = React.createClass({ }); }, 0); } - this.notifyNewScreen('user/' + payload.member.userId); break; case 'view_room': // Takes either a room ID or room alias: if switching to a room the client is already @@ -1204,44 +1203,21 @@ module.exports = React.createClass({ } else if (screen.indexOf('user/') == 0) { const userId = screen.substring(5); - // Wait for the first sync so that `getRoom` gives us a room object if it's - // in the sync response - const waitFor = this.firstSyncPromise ? - this.firstSyncPromise.promise : Promise.resolve(); - waitFor.then(() => { - if (params.action === 'chat') { - this._chatCreateOrReuse(userId); - return; - } + if (params.action === 'chat') { + this._chatCreateOrReuse(userId); + return; + } - // Get the member object for the current room, if a current room is set or - // we have a last_room in localStorage. The user might not be a member of - // this room (in which case member will be falsey). - let member; - const roomId = this.state.currentRoomId || localStorage.getItem('mx_last_room_id'); - if (roomId) { - const room = MatrixClientPeg.get().getRoom(roomId); - if (room) { - member = room.getMember(userId); - } - } - - if (member) { - // This user is a member of this room, so view the room - dis.dispatch({ - action: 'view_room', - room_id: roomId, - }); - } else { - // This user is not a member of this room, show the user view - member = new Matrix.RoomMember(null, userId); - this._setPage(PageTypes.UserView); - } + this.setState({ viewUserId: userId }); + this._setPage(PageTypes.UserView); + this.notifyNewScreen('user/' + userId); + const member = new Matrix.RoomMember(null, userId); + if (member) { dis.dispatch({ action: 'view_user', member: member, }); - }); + } } else if (screen.indexOf('group/') == 0) { const groupId = screen.substring(6); From b59de7964cd06f8b846e1b50f6f41384f319494b Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 14 Aug 2017 14:37:49 +0100 Subject: [PATCH 28/34] Wait for first sync before dispatching view_user on /user the RightPanel will be mounted once we're done doing the first sync, so wait until then and then dispatch a view_user. This is not very nice but it's what we do for view_room. --- src/components/structures/LoggedInView.js | 4 ++-- src/components/structures/MatrixChat.js | 21 +++++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 093fae5d7b..0790a5766e 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -301,13 +301,13 @@ export default React.createClass({ case PageTypes.UserView: page_element = null; // deliberately null for now - right_panel = ; + right_panel = ; break; case PageTypes.GroupView: page_element = ; - //right_panel = ; + //right_panel = ; break; } diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 6fdec80f38..a512d60509 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1203,21 +1203,22 @@ module.exports = React.createClass({ } else if (screen.indexOf('user/') == 0) { const userId = screen.substring(5); - if (params.action === 'chat') { - this._chatCreateOrReuse(userId); - return; - } + const waitFor = this.firstSyncPromise ? + this.firstSyncPromise.promise : Promise.resolve(); + waitFor.then(() => { + if (params.action === 'chat') { + this._chatCreateOrReuse(userId); + return; + } - this.setState({ viewUserId: userId }); - this._setPage(PageTypes.UserView); - this.notifyNewScreen('user/' + userId); - const member = new Matrix.RoomMember(null, userId); - if (member) { + this._setPage(PageTypes.UserView); + this.notifyNewScreen('user/' + userId); + const member = new Matrix.RoomMember(null, userId); dis.dispatch({ action: 'view_user', member: member, }); - } + }); } else if (screen.indexOf('group/') == 0) { const groupId = screen.substring(6); From 2844b574d59124d3957d20da75a08024ef5d2dbd Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 14 Aug 2017 14:41:03 +0100 Subject: [PATCH 29/34] Re-add useful comment --- src/components/structures/MatrixChat.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index a512d60509..8cb111bf82 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1203,6 +1203,8 @@ module.exports = React.createClass({ } else if (screen.indexOf('user/') == 0) { const userId = screen.substring(5); + // Wait for the first sync so that `getRoom` gives us a room object if it's + // in the sync response const waitFor = this.firstSyncPromise ? this.firstSyncPromise.promise : Promise.resolve(); waitFor.then(() => { From bb229d33a4f4bdd9db99ef13904a2fc95bfeb84f Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 14 Aug 2017 14:44:08 +0100 Subject: [PATCH 30/34] Add onClick to user pills instead of using the converted matrix.to href. This is undesirable and a better solution would be to fix routing in Riot in general and then change user pills to do something like `/room/../member/@userId`. --- src/components/views/elements/Pill.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/views/elements/Pill.js b/src/components/views/elements/Pill.js index b5fa163608..7dee5f6a40 100644 --- a/src/components/views/elements/Pill.js +++ b/src/components/views/elements/Pill.js @@ -15,6 +15,7 @@ limitations under the License. */ import React from 'react'; import sdk from '../../../index'; +import dis from '../../../dispatcher'; import classNames from 'classnames'; import { Room, RoomMember } from 'matrix-js-sdk'; import PropTypes from 'prop-types'; @@ -140,6 +141,12 @@ const Pill = React.createClass({ }); }, + onUserPillClicked: function() { + dis.dispatch({ + action: 'view_user', + member: this.state.member, + }); + }, render: function() { const MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); const RoomAvatar = sdk.getComponent('avatars.RoomAvatar'); @@ -150,6 +157,8 @@ const Pill = React.createClass({ let linkText = resource; let pillClass; let userId; + let href = this.props.url; + let onClick; switch (this.state.pillType) { case Pill.TYPE_USER_MENTION: { // If this user is not a member of this room, default to the empty member @@ -161,6 +170,8 @@ const Pill = React.createClass({ avatar = ; } pillClass = 'mx_UserPill'; + href = null; + onClick = this.onUserPillClicked.bind(this); } } break; @@ -183,7 +194,7 @@ const Pill = React.createClass({ if (this.state.pillType) { return this.props.inMessage ? - + {avatar} {linkText} : From 867e83edffd3b04f606085bd8e775f5dddde5eb1 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 14 Aug 2017 14:47:05 +0100 Subject: [PATCH 31/34] Fix log to include some useful info --- src/UserSettingsStore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js index ea060ff8d6..796dab4d60 100644 --- a/src/UserSettingsStore.js +++ b/src/UserSettingsStore.js @@ -190,7 +190,7 @@ export default { const feature = this.getFeatureById(featureId); if (!feature) { - console.warn('Unknown feature'); + console.warn(`Unknown feature "${featureId}"`); return false; } // Return the default if this feature has an override to be the default value or From 9742962d6136e30775887ab96d45a17990a56be3 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 14 Aug 2017 15:16:13 +0100 Subject: [PATCH 32/34] preventDefault when moving autocomplete selection with vertical arrow Otherwise the composer selection updates, in turn hiding the autocomplete box - fixes vector-im/riot-web#4790 --- src/components/views/rooms/MessageComposerInput.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index 63be026608..636addb79c 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -890,6 +890,7 @@ export default class MessageComposerInput extends React.Component { } } else { this.moveAutocompleteSelection(up); + e.preventDefault(); } }; From 0079e70006dd0f72238c3b664155f0b11f8cdbd1 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 14 Aug 2017 15:40:49 +0100 Subject: [PATCH 33/34] Display RM when it = a standalone member event Standalone = not next to any member events in the timeline. fixes vector-im/riot-web#4694 --- src/components/structures/MessagePanel.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 6f4a5460f6..02f224e942 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -307,13 +307,13 @@ module.exports = React.createClass({ for (i = 0; i < this.props.events.length; i++) { let mxEv = this.props.events[i]; let eventId = mxEv.getId(); - let readMarkerInMels = false; let last = (mxEv === lastShownEvent); const wantTile = this._shouldShowEvent(mxEv); // Wrap consecutive member events in a ListSummary, ignore if redacted if (isMembershipChange(mxEv) && wantTile) { + let readMarkerInMels = false; let ts1 = mxEv.getTs(); // Ensure that the key of the MemberEventListSummary does not change with new // member events. This will prevent it from being re-created unnecessarily, and @@ -330,6 +330,11 @@ module.exports = React.createClass({ ret.push(dateSeparator); } + // If RM event is the first in the MELS, append the RM after MELS + if (mxEv.getId() === this.props.readMarkerEventId) { + readMarkerInMels = true; + } + let summarisedEvents = [mxEv]; for (;i + 1 < this.props.events.length; i++) { const collapsedMxEv = this.props.events[i + 1]; From ecef9cf22f229be7209335640e395cea467abbae Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 14 Aug 2017 16:48:29 +0100 Subject: [PATCH 34/34] Use plaintext when loading a markdown history item into RT mode --- src/ComposerHistoryManager.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ComposerHistoryManager.js b/src/ComposerHistoryManager.js index ee2c748dc6..2fff3882b4 100644 --- a/src/ComposerHistoryManager.js +++ b/src/ComposerHistoryManager.js @@ -38,13 +38,11 @@ class HistoryItem { const contentState = convertFromRaw(this.rawContentState); if (outputFormat === 'markdown') { if (this.format === 'html') { - console.info(outputFormat, 'to other format'); return ContentState.createFromText(RichText.stateToMarkdown(contentState)); } } else { if (this.format === 'markdown') { - console.info(outputFormat, 'to other format'); - return RichText.htmlToContentState(new Markdown(contentState).toHTML()); + return RichText.htmlToContentState(new Markdown(contentState.getPlainText()).toHTML()); } } // history item has format === outputFormat