diff --git a/src/MatrixTools.js b/src/MatrixTools.js new file mode 100644 index 0000000000..343459b6c2 --- /dev/null +++ b/src/MatrixTools.js @@ -0,0 +1,38 @@ +/* +Copyright 2015 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +var MatrixClientPeg = require('./MatrixClientPeg'); + +module.exports = { + /** + * Given a room object, return the canonical alias for it + * if there is one. Otherwise return null; + */ + getCanonicalAliasForRoom: function(room) { + var aliasEvents = room.currentState.getStateEvents( + "m.room.aliases" + ); + // Canonical aliases aren't implemented yet, so just return the first + for (var j = 0; j < aliasEvents.length; j++) { + var aliases = aliasEvents[j].getContent().aliases; + if (aliases && aliases.length) { + return aliases[0]; + } + } + return null; + } +} + diff --git a/src/Presence.js b/src/Presence.js new file mode 100644 index 0000000000..d77058abd8 --- /dev/null +++ b/src/Presence.js @@ -0,0 +1,107 @@ +/* +Copyright 2015 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +var MatrixClientPeg = require("./MatrixClientPeg"); + + // Time in ms after that a user is considered as unavailable/away +var UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins +var PRESENCE_STATES = ["online", "offline", "unavailable"]; + +// The current presence state +var state, timer; + +module.exports = { + + /** + * Start listening the user activity to evaluate his presence state. + * Any state change will be sent to the Home Server. + */ + start: function() { + var self = this; + this.running = true; + if (undefined === state) { + // The user is online if they move the mouse or press a key + document.onmousemove = function() { self._resetTimer(); }; + document.onkeypress = function() { self._resetTimer(); }; + this._resetTimer(); + } + }, + + /** + * Stop tracking user activity + */ + stop: function() { + this.running = false; + if (timer) { + clearTimeout(timer); + timer = undefined; + } + state = undefined; + }, + + /** + * Get the current presence state. + * @returns {string} the presence state (see PRESENCE enum) + */ + getState: function() { + return state; + }, + + /** + * Set the presence state. + * If the state has changed, the Home Server will be notified. + * @param {string} newState the new presence state (see PRESENCE enum) + */ + setState: function(newState) { + if (newState === state) { + return; + } + if (PRESENCE_STATES.indexOf(newState) === -1) { + throw new Error("Bad presence state: " + newState); + } + if (!this.running) { + return; + } + state = newState; + MatrixClientPeg.get().setPresence(state).done(function() { + console.log("Presence: %s", newState); + }, function(err) { + console.error("Failed to set presence: %s", err); + }); + }, + + /** + * Callback called when the user made no action on the page for UNAVAILABLE_TIME ms. + * @private + */ + _onUnavailableTimerFire: function() { + this.setState("unavailable"); + }, + + /** + * Callback called when the user made an action on the page + * @private + */ + _resetTimer: function() { + var self = this; + this.setState("online"); + // Re-arm the timer + clearTimeout(timer); + timer = setTimeout(function() { + self._onUnavailableTimerFire(); + }, UNAVAILABLE_TIME_MS); + } +}; diff --git a/src/controllers/molecules/MemberInfo.js b/src/controllers/molecules/MemberInfo.js new file mode 100644 index 0000000000..8c34127b73 --- /dev/null +++ b/src/controllers/molecules/MemberInfo.js @@ -0,0 +1,323 @@ +/* +Copyright 2015 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* + * State vars: + * 'presence' : string (online|offline|unavailable etc) + * 'active' : number (ms ago; can be -1) + * 'can': { + * kick: boolean, + * ban: boolean, + * mute: boolean, + * modifyLevel: boolean + * }, + * 'muted': boolean, + * 'isTargetMod': boolean + */ + +var MatrixClientPeg = require("../../MatrixClientPeg"); +var dis = require("../../dispatcher"); +var Modal = require("../../Modal"); +var sdk = require('../../index'); + +module.exports = { + componentDidMount: function() { + var self = this; + // listen for presence changes + function updateUserState(event, user) { + if (!self.props.member) { return; } + + if (user.userId === self.props.member.userId) { + self.setState({ + presence: user.presence, + active: user.lastActiveAgo + }); + } + } + MatrixClientPeg.get().on("User.presence", updateUserState); + this.userPresenceFn = updateUserState; + + // listen for power level changes + function updatePowerLevel(event, member) { + if (!self.props.member) { return; } + + if (member.roomId !== self.props.member.roomId) { + return; + } + // only interested in changes to us or them + var myUserId = MatrixClientPeg.get().credentials.userId; + if ([myUserId, self.props.member.userId].indexOf(member.userId) === -1) { + return; + } + self.setState(self._calculateOpsPermissions()); + } + MatrixClientPeg.get().on("RoomMember.powerLevel", updatePowerLevel); + this.updatePowerLevelFn = updatePowerLevel; + + // work out the current state + if (this.props.member) { + var usr = MatrixClientPeg.get().getUser(this.props.member.userId) || {}; + var memberState = this._calculateOpsPermissions(); + memberState.presence = usr.presence || "offline"; + memberState.active = usr.lastActiveAgo || -1; + this.setState(memberState); + } + }, + + componentWillUnmount: function() { + MatrixClientPeg.get().removeListener("User.presence", this.userPresenceFn); + MatrixClientPeg.get().removeListener( + "RoomMember.powerLevel", this.updatePowerLevelFn + ); + }, + + onKick: function() { + var roomId = this.props.member.roomId; + var target = this.props.member.userId; + var self = this; + MatrixClientPeg.get().kick(roomId, target).done(function() { + // NO-OP; rely on the m.room.member event coming down else we could + // get out of sync if we force setState here! + console.log("Kick success"); + }, function(err) { + var ErrorDialog = sdk.getComponent("organisms.ErrorDialog"); + Modal.createDialog(ErrorDialog, { + title: "Kick error", + description: err.message + }); + }); + }, + + onBan: function() { + var roomId = this.props.member.roomId; + var target = this.props.member.userId; + var self = this; + MatrixClientPeg.get().ban(roomId, target).done(function() { + // NO-OP; rely on the m.room.member event coming down else we could + // get out of sync if we force setState here! + console.log("Ban success"); + }, function(err) { + var ErrorDialog = sdk.getComponent("organisms.ErrorDialog"); + Modal.createDialog(ErrorDialog, { + title: "Ban error", + description: err.message + }); + }); + }, + + onMuteToggle: function() { + var roomId = this.props.member.roomId; + var target = this.props.member.userId; + var self = this; + var room = MatrixClientPeg.get().getRoom(roomId); + if (!room) { + return; + } + var powerLevelEvent = room.currentState.getStateEvents( + "m.room.power_levels", "" + ); + if (!powerLevelEvent) { + return; + } + var isMuted = this.state.muted; + var powerLevels = powerLevelEvent.getContent(); + var levelToSend = ( + (powerLevels.events ? powerLevels.events["m.room.message"] : null) || + powerLevels.events_default + ); + var level; + if (isMuted) { // unmute + level = levelToSend; + } + else { // mute + level = levelToSend - 1; + } + + MatrixClientPeg.get().setPowerLevel(roomId, target, level, powerLevelEvent).done( + function() { + // NO-OP; rely on the m.room.member event coming down else we could + // get out of sync if we force setState here! + console.log("Mute toggle success"); + }, function(err) { + var ErrorDialog = sdk.getComponent("organisms.ErrorDialog"); + Modal.createDialog(ErrorDialog, { + title: "Mute error", + description: err.message + }); + }); + }, + + onModToggle: function() { + var roomId = this.props.member.roomId; + var target = this.props.member.userId; + var room = MatrixClientPeg.get().getRoom(roomId); + if (!room) { + return; + } + var powerLevelEvent = room.currentState.getStateEvents( + "m.room.power_levels", "" + ); + if (!powerLevelEvent) { + return; + } + var me = room.getMember(MatrixClientPeg.get().credentials.userId); + if (!me) { + return; + } + var defaultLevel = powerLevelEvent.getContent().users_default; + var modLevel = me.powerLevel - 1; + // toggle the level + var newLevel = this.state.isTargetMod ? defaultLevel : modLevel; + MatrixClientPeg.get().setPowerLevel(roomId, target, newLevel, powerLevelEvent).done( + function() { + // NO-OP; rely on the m.room.member event coming down else we could + // get out of sync if we force setState here! + console.log("Mod toggle success"); + }, function(err) { + var ErrorDialog = sdk.getComponent("organisms.ErrorDialog"); + Modal.createDialog(ErrorDialog, { + title: "Mod error", + description: err.message + }); + }); + }, + + onChatClick: function() { + // check if there are any existing rooms with just us and them (1:1) + // If so, just view that room. If not, create a private room with them. + var rooms = MatrixClientPeg.get().getRooms(); + var userIds = [ + this.props.member.userId, + MatrixClientPeg.get().credentials.userId + ]; + var existingRoomId = null; + for (var i = 0; i < rooms.length; i++) { + var members = rooms[i].getJoinedMembers(); + if (members.length === 2) { + var hasTargetUsers = true; + for (var j = 0; j < members.length; j++) { + if (userIds.indexOf(members[j].userId) === -1) { + hasTargetUsers = false; + break; + } + } + if (hasTargetUsers) { + existingRoomId = rooms[i].roomId; + break; + } + } + } + + if (existingRoomId) { + dis.dispatch({ + action: 'view_room', + room_id: existingRoomId + }); + } + else { + MatrixClientPeg.get().createRoom({ + invite: [this.props.member.userId], + preset: "private_chat" + }).done(function(res) { + dis.dispatch({ + action: 'view_room', + room_id: res.room_id + }); + }, function(err) { + console.error( + "Failed to create room: %s", JSON.stringify(err) + ); + }); + } + }, + + getInitialState: function() { + return { + presence: "offline", + active: -1, + can: { + kick: false, + ban: false, + mute: false, + modifyLevel: false + }, + muted: false, + isTargetMod: false + } + }, + + _calculateOpsPermissions: function() { + var defaultPerms = { + can: {}, + muted: false, + modifyLevel: false + }; + var room = MatrixClientPeg.get().getRoom(this.props.member.roomId); + if (!room) { + return defaultPerms; + } + var powerLevels = room.currentState.getStateEvents( + "m.room.power_levels", "" + ); + if (!powerLevels) { + return defaultPerms; + } + var me = room.getMember(MatrixClientPeg.get().credentials.userId); + var them = this.props.member; + return { + can: this._calculateCanPermissions( + me, them, powerLevels.getContent() + ), + muted: this._isMuted(them, powerLevels.getContent()), + isTargetMod: them.powerLevel > powerLevels.getContent().users_default + }; + }, + + _calculateCanPermissions: function(me, them, powerLevels) { + var can = { + kick: false, + ban: false, + mute: false, + modifyLevel: false + }; + var canAffectUser = them.powerLevel < me.powerLevel; + if (!canAffectUser) { + //console.log("Cannot affect user: %s >= %s", them.powerLevel, me.powerLevel); + return can; + } + var editPowerLevel = ( + (powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) || + powerLevels.state_default + ); + can.kick = me.powerLevel >= powerLevels.kick; + can.ban = me.powerLevel >= powerLevels.ban; + can.mute = me.powerLevel >= editPowerLevel; + can.modifyLevel = me.powerLevel > them.powerLevel; + return can; + }, + + _isMuted: function(member, powerLevelContent) { + if (!powerLevelContent || !member) { + return false; + } + var levelToSend = ( + (powerLevelContent.events ? powerLevelContent.events["m.room.message"] : null) || + powerLevelContent.events_default + ); + return member.powerLevel < levelToSend; + } +}; + diff --git a/src/controllers/molecules/MemberTile.js b/src/controllers/molecules/MemberTile.js index a914fc6279..b2e7c411ee 100644 --- a/src/controllers/molecules/MemberTile.js +++ b/src/controllers/molecules/MemberTile.js @@ -24,6 +24,10 @@ var Loader = require("react-loader"); var MatrixClientPeg = require("../../MatrixClientPeg"); module.exports = { + getInitialState: function() { + return {}; + }, + onClick: function() { dis.dispatch({ action: 'view_user', diff --git a/src/controllers/organisms/ErrorDialog.js b/src/controllers/organisms/ErrorDialog.js new file mode 100644 index 0000000000..32914bb156 --- /dev/null +++ b/src/controllers/organisms/ErrorDialog.js @@ -0,0 +1,37 @@ +/* +Copyright 2015 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +var React = require("react"); + +module.exports = { + propTypes: { + title: React.PropTypes.string, + description: React.PropTypes.string, + button: React.PropTypes.string, + focus: React.PropTypes.bool, + onFinished: React.PropTypes.func.isRequired, + }, + + getDefaultProps: function() { + var self = this; + return { + title: "Error", + description: "An error has occurred.", + button: "OK", + focus: true, + }; + }, +}; diff --git a/src/controllers/organisms/MemberList.js b/src/controllers/organisms/MemberList.js index a511816d53..758ba0716f 100644 --- a/src/controllers/organisms/MemberList.js +++ b/src/controllers/organisms/MemberList.js @@ -14,10 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -'use strict'; - var React = require("react"); var MatrixClientPeg = require("../../MatrixClientPeg"); +var Modal = require("../../Modal"); +var sdk = require('../../index'); var INITIAL_LOAD_NUM_MEMBERS = 50; @@ -32,39 +32,117 @@ module.exports = { componentWillMount: function() { var cli = MatrixClientPeg.get(); cli.on("RoomState.members", this.onRoomStateMember); + cli.on("Room", this.onRoom); // invites }, componentWillUnmount: function() { if (MatrixClientPeg.get()) { + MatrixClientPeg.get().removeListener("Room", this.onRoom); MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember); + MatrixClientPeg.get().removeListener("User.presence", this.userPresenceFn); } }, componentDidMount: function() { - var that = this; + var self = this; setTimeout(function() { - if (!that.isMounted()) return; - that.setState({ - memberDict: that.roomMembers() + if (!self.isMounted()) return; + self.setState({ + memberDict: self.roomMembers() }); }, 50); - }, + // Attach a SINGLE listener for global presence changes then locate the + // member tile and re-render it. This is more efficient than every tile + // evar attaching their own listener. + function updateUserState(event, user) { + var tile = self.refs[user.userId]; + if (tile) { + tile.forceUpdate(); + } + } + MatrixClientPeg.get().on("User.presence", updateUserState); + this.userPresenceFn = updateUserState; + }, // Remember to set 'key' on a MemberList to the ID of the room it's for /*componentWillReceiveProps: function(newProps) { },*/ + onRoom: function(room) { + if (room.roomId !== this.props.roomId) { + return; + } + // We listen for room events because when we accept an invite + // we need to wait till the room is fully populated with state + // before refreshing the member list else we get a stale list. + this._updateList(); + }, + onRoomStateMember: function(ev, state, member) { + this._updateList(); + }, + + _updateList: function() { var members = this.roomMembers(); this.setState({ memberDict: members }); }, + onInvite: function(inputText) { + var ErrorDialog = sdk.getComponent("organisms.ErrorDialog"); + var self = this; + // sanity check the input + inputText = inputText.trim(); // react requires es5-shim so we know trim() exists + if (inputText[0] !== '@' || inputText.indexOf(":") === -1) { + console.error("Bad user ID to invite: %s", inputText); + Modal.createDialog(ErrorDialog, { + title: "Invite Error", + description: "Malformed user ID. Should look like '@localpart:domain'" + }); + return; + } + self.setState({ + inviting: true + }); + console.log("Invite %s to %s", inputText, this.props.roomId); + MatrixClientPeg.get().invite(this.props.roomId, inputText).done( + function(res) { + console.log("Invited"); + self.setState({ + inviting: false + }); + }, function(err) { + console.error("Failed to invite: %s", JSON.stringify(err)); + Modal.createDialog(ErrorDialog, { + title: "Server error whilst inviting", + description: err.message + }); + self.setState({ + inviting: false + }); + }); + }, + roomMembers: function(limit) { + if (!this.props.roomId) return {}; var cli = MatrixClientPeg.get(); - var all_members = cli.getRoom(this.props.roomId).currentState.members; + var room = cli.getRoom(this.props.roomId); + if (!room) return {}; + var all_members = room.currentState.members; var all_user_ids = Object.keys(all_members); + + all_user_ids.sort(function(userIdA, userIdB) { + var userA = all_members[userIdA].user; + var userB = all_members[userIdB].user; + + var latA = userA ? userA.lastActiveAgo || Number.MAX_VALUE : Number.MAX_VALUE; + var latB = userB ? userB.lastActiveAgo || Number.MAX_VALUE : Number.MAX_VALUE; + + return latA - latB; + }); + + var to_display = {}; var count = 0; for (var i = 0; i < all_user_ids.length && (limit === undefined || count < limit); ++i) { diff --git a/src/controllers/pages/MatrixChat.js b/src/controllers/pages/MatrixChat.js index 68da9f4e12..d4c75170d6 100644 --- a/src/controllers/pages/MatrixChat.js +++ b/src/controllers/pages/MatrixChat.js @@ -16,19 +16,44 @@ limitations under the License. 'use strict'; +// should be atomised +var Loader = require("react-loader"); + var MatrixClientPeg = require("../../MatrixClientPeg"); var RoomListSorter = require("../../RoomListSorter"); - +var Presence = require("../../Presence"); var dis = require("../../dispatcher"); +var q = require("q"); var sdk = require('../../index'); +var MatrixTools = require('../../MatrixTools'); module.exports = { + PageTypes: { + RoomView: "room_view", + UserSettings: "user_settings", + CreateRoom: "create_room", + RoomDirectory: "room_directory", + }, + + AuxPanel: { + RoomSettings: "room_settings", + }, + getInitialState: function() { - return { + var s = { logged_in: !!(MatrixClientPeg.get() && MatrixClientPeg.get().credentials), - ready: false + ready: false, + aux_panel: null, }; + if (s.logged_in) { + if (MatrixClientPeg.get().getRooms().length) { + s.page_type = this.PageTypes.RoomView; + } else { + s.page_type = this.PageTypes.RoomDirectory; + } + } + return s; }, componentDidMount: function() { @@ -49,6 +74,7 @@ module.exports = { componentWillUnmount: function() { dis.unregister(this.dispatcherRef); document.removeEventListener("keydown", this.onKeyDown); + window.removeEventListener("focus", this.onFocus); }, componentDidUpdate: function() { @@ -72,8 +98,11 @@ module.exports = { window.localStorage.clear(); } Notifier.stop(); + Presence.stop(); + MatrixClientPeg.get().stopClient(); MatrixClientPeg.get().removeAllListeners(); - MatrixClientPeg.replace(null); + MatrixClientPeg.unset(); + this.notifyNewScreen(''); break; case 'start_registration': if (this.state.logged_in) return; @@ -106,8 +135,23 @@ module.exports = { case 'view_room': this.focusComposer = true; this.setState({ - currentRoom: payload.room_id + currentRoom: payload.room_id, + page_type: this.PageTypes.RoomView, }); + if (this.sdkReady) { + // if the SDK is not ready yet, remember what room + // we're supposed to be on but don't notify about + // the new screen yet (we won't be showing it yet) + // The normal case where this happens is navigating + // to the room in the URL bar on page load. + var presentedId = payload.room_id; + var room = MatrixClientPeg.get().getRoom(payload.room_id); + if (room) { + var theAlias = MatrixTools.getCanonicalAliasForRoom(room); + if (theAlias) presentedId = theAlias; + } + this.notifyNewScreen('room/'+presentedId); + } break; case 'view_prev_room': roomIndexDelta = -1; @@ -123,9 +167,43 @@ module.exports = { } } roomIndex = (roomIndex + roomIndexDelta) % allRooms.length; + if (roomIndex < 0) roomIndex = allRooms.length - 1; + this.focusComposer = true; this.setState({ currentRoom: allRooms[roomIndex].roomId }); + this.notifyNewScreen('room/'+allRooms[roomIndex].roomId); + break; + case 'view_indexed_room': + var allRooms = RoomListSorter.mostRecentActivityFirst( + MatrixClientPeg.get().getRooms() + ); + var roomIndex = payload.roomIndex; + if (allRooms[roomIndex]) { + this.focusComposer = true; + this.setState({ + currentRoom: allRooms[roomIndex].roomId + }); + this.notifyNewScreen('room/'+allRooms[roomIndex].roomId); + } + break; + case 'view_user_settings': + this.setState({ + page_type: this.PageTypes.UserSettings, + }); + break; + case 'view_create_room': + this.setState({ + page_type: this.PageTypes.CreateRoom, + }); + break; + case 'view_room_directory': + this.setState({ + page_type: this.PageTypes.RoomDirectory, + }); + break; + case 'notifier_enabled': + this.forceUpdate(); break; } }, @@ -141,33 +219,67 @@ module.exports = { startMatrixClient: function() { var Notifier = sdk.getComponent('organisms.Notifier'); - var cli = MatrixClientPeg.get(); var self = this; cli.on('syncComplete', function() { - var firstRoom = null; - if (cli.getRooms() && cli.getRooms().length) { - firstRoom = RoomListSorter.mostRecentActivityFirst( - cli.getRooms() - )[0].roomId; + self.sdkReady = true; + if (!self.state.currentRoom) { + var firstRoom = null; + if (cli.getRooms() && cli.getRooms().length) { + firstRoom = RoomListSorter.mostRecentActivityFirst( + cli.getRooms() + )[0].roomId; + self.setState({ready: true, currentRoom: firstRoom, page_type: self.PageTypes.RoomView}); + } else { + self.setState({ready: true, page_type: self.PageTypes.RoomDirectory}); + } + } else { + self.setState({ready: true, currentRoom: self.state.currentRoom}); } - self.setState({ready: true, currentRoom: firstRoom}); + + // we notifyNewScreen now because now the room will actually be displayed, + // and (mostly) now we can get the correct alias. + var presentedId = self.state.currentRoom; + var room = MatrixClientPeg.get().getRoom(self.state.currentRoom); + if (room) { + var theAlias = MatrixTools.getCanonicalAliasForRoom(room); + if (theAlias) presentedId = theAlias; + } + self.notifyNewScreen('room/'+presentedId); dis.dispatch({action: 'focus_composer'}); }); + cli.on('Call.incoming', function(call) { + dis.dispatch({ + action: 'incoming_call', + call: call + }); + }); Notifier.start(); + Presence.start(); cli.startClient(); }, onKeyDown: function(ev) { if (ev.altKey) { + if (ev.ctrlKey && ev.keyCode > 48 && ev.keyCode < 58) { + dis.dispatch({ + action: 'view_indexed_room', + roomIndex: ev.keyCode - 49, + }); + ev.stopPropagation(); + ev.preventDefault(); + return; + } switch (ev.keyCode) { case 38: dis.dispatch({action: 'view_prev_room'}); ev.stopPropagation(); + ev.preventDefault(); break; case 40: dis.dispatch({action: 'view_next_room'}); ev.stopPropagation(); + ev.preventDefault(); break; } } @@ -188,6 +300,28 @@ module.exports = { action: 'start_login', params: params }); + } else if (screen.indexOf('room/') == 0) { + var roomString = screen.split('/')[1]; + var defer = q.defer(); + if (roomString[0] == '#') { + var self = this; + MatrixClientPeg.get().getRoomIdForAlias(roomString).done(function(result) { + if (self.sdkReady) self.setState({ready: true}); + defer.resolve(result.room_id); + }, function() { + if (self.sdkReady) self.setState({ready: true}); + defer.resolve(null); + }); + this.setState({ready: false}); + } else { + defer.resolve(roomString); + } + defer.promise.done(function(roomId) { + dis.dispatch({ + action: 'view_room', + room_id: roomId + }); + }); } }, @@ -197,4 +331,3 @@ module.exports = { } } }; -