diff --git a/README.md b/README.md index 811f52f2b8..a15917a475 100644 --- a/README.md +++ b/README.md @@ -22,14 +22,27 @@ into the `vector` directory and run your own server. Development =========== -You can work on any of the source files within Vector with the setup above, -and your changes will cause an instant rebuild. If you also need to make -changes to the react sdk, you can: -1. Link the react sdk package into the example: +For simple tweaks, you can work on any of the source files within Vector with the +setup above, and your changes will cause an instant rebuild. + +However, all serious development on Vector happens on the `develop` branch. This typically +depends on the `develop` snapshot versions of `matrix-react-sdk` and `matrix-js-sdk` +too, which isn't expressed in Vector's `package.json`. To do this, check out +the `develop` branches of these libraries and then use `npm link` to tell Vector +about them: + +1. `git clone git@github.com:matrix-org/matrix-react-sdk.git` +2. `cd matrix-react-sdk` +3. `git checkout develop` +4. `npm install` +5. `npm start` (to start the dev rebuilder) +6. `cd ../vector-web` +7. Link the react sdk package into the example: `npm link path/to/your/react/sdk` -2. Start the development rebuilder in your react SDK directory: - `npm start` + +Similarly, you may need to `npm link path/to/your/js/sdk` in your `matrix-react-sdk` +directory. If you add or remove any components from the Vector skin, you will need to rebuild the skin's index by running, `npm run reskindex`. diff --git a/src/ContextualMenu.js b/src/ContextualMenu.js index cdfff952bb..7865e45a75 100644 --- a/src/ContextualMenu.js +++ b/src/ContextualMenu.js @@ -49,15 +49,25 @@ module.exports = { var position = { top: props.top - 20, - right: props.right + 8, }; + var chevron = null; + if (props.left) { + chevron = + position.left = props.left + 8; + } else { + chevron = + position.right = props.right + 8; + } + + var className = 'mx_ContextualMenu_wrapper'; + // FIXME: If a menu uses getDefaultProps it clobbers the onFinished // property set here so you can't close the menu from a button click! var menu = ( -
+
- + {chevron}
diff --git a/src/controllers/organisms/RoomList.js b/src/controllers/organisms/RoomList.js index c715faade5..03a1bfcd18 100644 --- a/src/controllers/organisms/RoomList.js +++ b/src/controllers/organisms/RoomList.js @@ -33,6 +33,7 @@ module.exports = { cli.on("Room", this.onRoom); cli.on("Room.timeline", this.onRoomTimeline); cli.on("Room.name", this.onRoomName); + cli.on("RoomState.events", this.onRoomStateEvents); var rooms = this.getRoomList(); this.setState({ @@ -66,6 +67,7 @@ module.exports = { MatrixClientPeg.get().removeListener("Room", this.onRoom); MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline); MatrixClientPeg.get().removeListener("Room.name", this.onRoomName); + MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents); } }, @@ -110,6 +112,11 @@ module.exports = { this.refreshRoomList(); }, + onRoomStateEvents: function(ev, state) { + setTimeout(this.refreshRoomList, 0); + }, + + refreshRoomList: function() { var rooms = this.getRoomList(); this.setState({ diff --git a/src/controllers/organisms/RoomView.js b/src/controllers/organisms/RoomView.js index 29933d4ad8..21027cbfa8 100644 --- a/src/controllers/organisms/RoomView.js +++ b/src/controllers/organisms/RoomView.js @@ -63,6 +63,7 @@ module.exports = { MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline); MatrixClientPeg.get().removeListener("Room.name", this.onRoomName); MatrixClientPeg.get().removeListener("RoomMember.typing", this.onRoomMemberTyping); + MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember); } }, @@ -356,23 +357,20 @@ module.exports = { }, getEventTiles: function() { - var tileTypes = { - 'm.room.message': sdk.getComponent('molecules.MessageTile'), - 'm.room.member' : sdk.getComponent('molecules.EventAsTextTile'), - 'm.call.invite' : sdk.getComponent('molecules.EventAsTextTile'), - 'm.call.answer' : sdk.getComponent('molecules.EventAsTextTile'), - 'm.call.hangup' : sdk.getComponent('molecules.EventAsTextTile'), - 'm.room.topic' : sdk.getComponent('molecules.EventAsTextTile'), - }; - var DateSeparator = sdk.getComponent('molecules.DateSeparator'); var ret = []; var count = 0; + var EventTile = sdk.getComponent('molecules.EventTile'); + for (var i = this.state.room.timeline.length-1; i >= 0 && count < this.state.messageCap; --i) { var mxEv = this.state.room.timeline[i]; - var TileType = tileTypes[mxEv.getType()]; + + if (!EventTile.supportsEventType(mxEv.getType())) { + continue; + } + var continuation = false; var last = false; var dateSeparator = null; @@ -401,13 +399,12 @@ module.exports = { if (i === 1) { // n.b. 1, not 0, as the 0th event is an m.room.create and so doesn't show on the timeline var ts1 = this.state.room.timeline[i].getTs(); - dateSeparator = ; + dateSeparator =
  • ; continuation = false; } - if (!TileType) continue; ret.unshift( -
  • +
  • ); if (dateSeparator) { ret.unshift(dateSeparator); diff --git a/src/skins/vector/css/common.css b/src/skins/vector/css/common.css index 879561f912..aff9666e91 100644 --- a/src/skins/vector/css/common.css +++ b/src/skins/vector/css/common.css @@ -67,13 +67,20 @@ a:visited { padding: 6px; } -.mx_ContextualMenu_chevron { +.mx_ContextualMenu_chevron_right { padding: 12px; position: absolute; right: -21px; top: 0px; } +.mx_ContextualMenu_chevron_left { + padding: 12px; + position: absolute; + left: -21px; + top: 0px; +} + .mx_ContextualMenu_field { padding: 3px 6px 3px 6px; cursor: pointer; diff --git a/src/skins/vector/css/molecules/MessageTile.css b/src/skins/vector/css/molecules/EventTile.css similarity index 70% rename from src/skins/vector/css/molecules/MessageTile.css rename to src/skins/vector/css/molecules/EventTile.css index 0732481941..1cd2fa465f 100644 --- a/src/skins/vector/css/molecules/MessageTile.css +++ b/src/skins/vector/css/molecules/EventTile.css @@ -14,14 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_MessageTile { +.mx_EventTile { max-width: 100%; clear: both; margin-top: 32px; margin-left: 56px; } -.mx_MessageTile_avatar { +.mx_EventTile_avatar { padding-left: 12px; padding-right: 12px; margin-left: -64px; @@ -29,17 +29,17 @@ limitations under the License. float: left; } -.mx_MessageTile_avatar img { +.mx_EventTile_avatar img { background-color: #dbdbdb; border-radius: 20px; border: 0px; } -.mx_MessageTile_continuation { +.mx_EventTile_continuation { margin-top: 8px ! important; } -.mx_MessageTile .mx_SenderProfile { +.mx_EventTile .mx_SenderProfile { color: #454545; opacity: 0.5; font-size: 14px; @@ -47,35 +47,35 @@ limitations under the License. display: block; } -.mx_MessageTile .mx_MessageTimestamp { +.mx_EventTile .mx_MessageTimestamp { color: #454545; opacity: 0.5; font-size: 14px; float: right; } -.mx_MessageTile_content { +.mx_EventTile_content { padding-right: 100px; display: block; } -.mx_MessageTile_notice .mx_MessageTile_content { +.mx_EventTile_notice .mx_MessageTile_content { opacity: 0.5; } -.mx_MessageTile_sending { +.mx_EventTile_sending { color: #ddd; } -.mx_MessageTile_notSent { +.mx_EventTile_notSent { color: #f11; } -.mx_MessageTile_highlight { +.mx_EventTile_highlight { color: #FF0064; } -.mx_MessageTile_msgOption { +.mx_EventTile_msgOption { float: right; } @@ -83,29 +83,30 @@ limitations under the License. display: none; } -.mx_MessageTile_last .mx_MessageTimestamp { +.mx_EventTile_last .mx_MessageTimestamp { display: block; } -.mx_MessageTile:hover .mx_MessageTimestamp { +.mx_EventTile:hover .mx_MessageTimestamp { display: block; } -.mx_MessageTile_editButton { +.mx_EventTile_editButton { float: right; display: none; border: 0px; outline: none; + margin-right: 3px; } -.mx_MessageTile:hover .mx_MessageTile_editButton { +.mx_EventTile:hover .mx_EventTile_editButton { display: inline-block; } -.mx_MessageTile.menu .mx_MessageTile_editButton { +.mx_EventTile.menu .mx_EventTile_editButton { display: inline-block; } -.mx_MessageTile.menu .mx_MessageTimestamp { +.mx_EventTile.menu .mx_MessageTimestamp { display: inline-block; } diff --git a/src/skins/vector/img/50e2c2.png b/src/skins/vector/img/50e2c2.png new file mode 100644 index 0000000000..ee0f855895 Binary files /dev/null and b/src/skins/vector/img/50e2c2.png differ diff --git a/src/skins/vector/img/80cef4.png b/src/skins/vector/img/80cef4.png new file mode 100644 index 0000000000..637d03f63c Binary files /dev/null and b/src/skins/vector/img/80cef4.png differ diff --git a/src/skins/vector/img/f4c371.png b/src/skins/vector/img/f4c371.png new file mode 100644 index 0000000000..ad3b8f1616 Binary files /dev/null and b/src/skins/vector/img/f4c371.png differ diff --git a/src/skins/vector/skindex.js b/src/skins/vector/skindex.js index 7cd8361850..8dba10cf30 100644 --- a/src/skins/vector/skindex.js +++ b/src/skins/vector/skindex.js @@ -41,6 +41,7 @@ skin['molecules.ChangeDisplayName'] = require('./views/molecules/ChangeDisplayNa skin['molecules.ChangePassword'] = require('./views/molecules/ChangePassword'); skin['molecules.DateSeparator'] = require('./views/molecules/DateSeparator'); skin['molecules.EventAsTextTile'] = require('./views/molecules/EventAsTextTile'); +skin['molecules.EventTile'] = require('./views/molecules/EventTile'); skin['molecules.MatrixToolbar'] = require('./views/molecules/MatrixToolbar'); skin['molecules.MemberInfo'] = require('./views/molecules/MemberInfo'); skin['molecules.MemberTile'] = require('./views/molecules/MemberTile'); diff --git a/src/skins/vector/views/atoms/MessageTimestamp.js b/src/skins/vector/views/atoms/MessageTimestamp.js index 3247631485..98cfe4a10b 100644 --- a/src/skins/vector/views/atoms/MessageTimestamp.js +++ b/src/skins/vector/views/atoms/MessageTimestamp.js @@ -42,7 +42,7 @@ module.exports = React.createClass({ return days[date.getDay()] + ", " + months[date.getMonth()] + " " + (date.getDay()+1) + " " + pad(date.getHours()) + ':' + pad(date.getMinutes()); } else { - return days[date.getDay()] + ", " + months[date.getMonth()] + " " + (date.getDay()+1) + " " + date().getFullYear() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes()); + return days[date.getDay()] + ", " + months[date.getMonth()] + " " + (date.getDay()+1) + " " + date.getFullYear() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes()); } }, diff --git a/src/skins/vector/views/atoms/RoomAvatar.js b/src/skins/vector/views/atoms/RoomAvatar.js index ec2bf5ec21..e57b3b7ae5 100644 --- a/src/skins/vector/views/atoms/RoomAvatar.js +++ b/src/skins/vector/views/atoms/RoomAvatar.js @@ -17,6 +17,7 @@ limitations under the License. 'use strict'; var React = require('react'); +var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); var RoomAvatarController = require('matrix-react-sdk/lib/controllers/atoms/RoomAvatar') @@ -24,10 +25,31 @@ module.exports = React.createClass({ displayName: 'RoomAvatar', mixins: [RoomAvatarController], + getUrlList: function() { + return [ + this.roomAvatarUrl(), + this.getOneToOneAvatar(), + this.getFallbackAvatar() + ]; + }, + + getFallbackAvatar: function() { + var images = [ '80cef4', '50e2c2', 'f4c371' ]; + var total = 0; + for (var i = 0; i < this.props.room.roomId.length; ++i) { + total += this.props.room.roomId.charCodeAt(i); + } + return 'img/' + images[total % images.length] + '.png'; + }, + render: function() { + var style = { + maxWidth: this.props.width, + maxHeight: this.props.height, + }; return ( ); } diff --git a/src/skins/vector/views/molecules/ChangeAvatar.js b/src/skins/vector/views/molecules/ChangeAvatar.js index 52a59e3f8b..42c2d1fd45 100644 --- a/src/skins/vector/views/molecules/ChangeAvatar.js +++ b/src/skins/vector/views/molecules/ChangeAvatar.js @@ -18,6 +18,7 @@ limitations under the License. var React = require('react'); +var sdk = require('matrix-react-sdk') var ChangeAvatarController = require('matrix-react-sdk/lib/controllers/molecules/ChangeAvatar') var Loader = require("react-loader"); @@ -28,6 +29,7 @@ module.exports = React.createClass({ mixins: [ChangeAvatarController], onFileSelected: function(ev) { + this.avatarSet = true; this.setAvatarFromFile(ev.target.files[0]); }, @@ -38,13 +40,27 @@ module.exports = React.createClass({ }, render: function() { + var RoomAvatar = sdk.getComponent('atoms.RoomAvatar'); + var avatarImg; + // Having just set an avatar we just display that since it will take a little + // time to propagate through to the RoomAvatar. + if (this.props.room && !this.avatarSet) { + avatarImg = ; + } else { + var style = { + maxWidth: 320, + maxHeight: 240, + }; + avatarImg = ; + } + switch (this.state.phase) { case this.Phases.Display: case this.Phases.Error: return (
    - + {avatarImg}
    Upload new: diff --git a/src/skins/vector/views/molecules/EventAsTextTile.js b/src/skins/vector/views/molecules/EventAsTextTile.js index bf757f2f26..db9df9f49b 100644 --- a/src/skins/vector/views/molecules/EventAsTextTile.js +++ b/src/skins/vector/views/molecules/EventAsTextTile.js @@ -24,25 +24,19 @@ var TextForEvent = require('matrix-react-sdk/lib/TextForEvent'); module.exports = React.createClass({ displayName: 'EventAsTextTile', - render: function() { - var MessageTimestamp = sdk.getComponent('atoms.MessageTimestamp'); - var MemberAvatar = sdk.getComponent('atoms.MemberAvatar'); + statics: { + needsSenderProfile: function() { + return false; + } + }, + render: function() { var text = TextForEvent.textForEvent(this.props.mxEvent); if (text == null || text.length == 0) return null; - var timestamp = this.props.last ? : null; - var avatar = this.props.mxEvent.sender ? : null; return ( -
    -
    - { avatar } -
    - { timestamp } - - - {TextForEvent.textForEvent(this.props.mxEvent)} - +
    + {TextForEvent.textForEvent(this.props.mxEvent)}
    ); }, diff --git a/src/skins/vector/views/molecules/EventTile.js b/src/skins/vector/views/molecules/EventTile.js new file mode 100644 index 0000000000..1b1642bde4 --- /dev/null +++ b/src/skins/vector/views/molecules/EventTile.js @@ -0,0 +1,133 @@ +/* +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. +*/ + +'use strict'; + +var React = require('react'); +var classNames = require("classnames"); + +var sdk = require('matrix-react-sdk') + +var EventTileController = require('matrix-react-sdk/lib/controllers/molecules/EventTile') +var ContextualMenu = require('../../../../ContextualMenu'); + +var eventTileTypes = { + 'm.room.message': 'molecules.MessageTile', + 'm.room.member' : 'molecules.EventAsTextTile', + 'm.call.invite' : 'molecules.EventAsTextTile', + 'm.call.answer' : 'molecules.EventAsTextTile', + 'm.call.hangup' : 'molecules.EventAsTextTile', + 'm.room.topic' : 'molecules.EventAsTextTile', +}; + +module.exports = React.createClass({ + displayName: 'EventTile', + mixins: [EventTileController], + + statics: { + supportsEventType: function(et) { + return eventTileTypes[et] !== undefined; + } + }, + + getInitialState: function() { + return {menu: false}; + }, + + onEditClicked: function(e) { + var MessageContextMenu = sdk.getComponent('molecules.MessageContextMenu'); + var buttonRect = e.target.getBoundingClientRect() + var x = buttonRect.right; + var y = buttonRect.top + (e.target.height / 2); + var self = this; + ContextualMenu.createMenu(MessageContextMenu, { + mxEvent: this.props.mxEvent, + left: x, + top: y, + onFinished: function() { + self.setState({menu: false}); + } + }); + this.setState({menu: true}); + }, + + render: function() { + var MessageTimestamp = sdk.getComponent('atoms.MessageTimestamp'); + var SenderProfile = sdk.getComponent('molecules.SenderProfile'); + var MemberAvatar = sdk.getComponent('atoms.MemberAvatar'); + + var UnknownMessageTile = sdk.getComponent('molecules.UnknownMessageTile'); + + var content = this.props.mxEvent.getContent(); + var msgtype = content.msgtype; + + var EventTileType = sdk.getComponent(eventTileTypes[this.props.mxEvent.getType()]); + // This shouldn't happen: the caller should check we support this type + // before trying to instantiate us + if (!EventTileType) { + return null; + } + + var classes = classNames({ + mx_EventTile: true, + mx_EventTile_sending: ['sending', 'queued'].indexOf( + this.props.mxEvent.status + ) !== -1, + mx_EventTile_notSent: this.props.mxEvent.status == 'not_sent', + mx_EventTile_highlight: this.shouldHighlight(), + mx_EventTile_continuation: this.props.continuation, + mx_EventTile_last: this.props.last, + menu: this.state.menu + }); + var timestamp = + var editButton = ( + + ); + + var aux = null; + if (msgtype === 'm.image') aux = "sent an image"; + else if (msgtype === 'm.video') aux = "sent a video"; + else if (msgtype === 'm.file') aux = "uploaded a file"; + + var avatar, sender; + if (!this.props.continuation) { + if (this.props.mxEvent.sender) { + avatar = ( +
    + +
    + ); + } + if (EventTileType.needsSenderProfile()) { + sender = ; + } + } + return ( +
    + { avatar } + { sender } +
    + { timestamp } + { editButton } + +
    +
    + ); + }, +}); diff --git a/src/skins/vector/views/molecules/MessageContextMenu.js b/src/skins/vector/views/molecules/MessageContextMenu.js index 66f1d6d4c8..249d9a3437 100644 --- a/src/skins/vector/views/molecules/MessageContextMenu.js +++ b/src/skins/vector/views/molecules/MessageContextMenu.js @@ -52,9 +52,27 @@ module.exports = React.createClass({ if (this.props.onFinished) this.props.onFinished(); }, + onRedactClick: function() { + MatrixClientPeg.get().redactEvent( + this.props.mxEvent.getRoomId(), this.props.mxEvent.getId() + ).done(function() { + // message should disappear by itself + }, function(e) { + var ErrorDialog = sdk.getComponent("organisms.ErrorDialog"); + // display error message stating you couldn't delete this. + var code = e.errcode || e.statusCode; + Modal.createDialog(ErrorDialog, { + title: "Error", + description: "You cannot delete this message. (" + code + ")" + }); + }); + if (this.props.onFinished) this.props.onFinished(); + }, + render: function() { var resendButton; var viewSourceButton; + var redactButton; if (this.props.mxEvent.status == 'not_sent') { resendButton = ( @@ -63,6 +81,13 @@ module.exports = React.createClass({
    ); } + else { + redactButton = ( +
    + Delete +
    + ); + } viewSourceButton = (
    View Source @@ -72,6 +97,7 @@ module.exports = React.createClass({ return (
    {resendButton} + {redactButton} {viewSourceButton}
    ); diff --git a/src/skins/vector/views/molecules/MessageTile.js b/src/skins/vector/views/molecules/MessageTile.js index 425cc44b5e..44f5b63564 100644 --- a/src/skins/vector/views/molecules/MessageTile.js +++ b/src/skins/vector/views/molecules/MessageTile.js @@ -28,28 +28,13 @@ module.exports = React.createClass({ displayName: 'MessageTile', mixins: [MessageTileController], - onEditClicked: function(e) { - var MessageContextMenu = sdk.getComponent('molecules.MessageContextMenu'); - var buttonRect = e.target.getBoundingClientRect() - var x = window.innerWidth - buttonRect.left; - var y = buttonRect.top + (e.target.height / 2); - var self = this; - ContextualMenu.createMenu(MessageContextMenu, { - mxEvent: this.props.mxEvent, - right: x, - top: y, - onFinished: function() { - self.setState({menu: false}); - } - }); - this.setState({menu: true}); + statics: { + needsSenderProfile: function() { + return true; + } }, render: function() { - var MessageTimestamp = sdk.getComponent('atoms.MessageTimestamp'); - var SenderProfile = sdk.getComponent('molecules.SenderProfile'); - var MemberAvatar = sdk.getComponent('atoms.MemberAvatar'); - var UnknownMessageTile = sdk.getComponent('molecules.UnknownMessageTile'); var tileTypes = { @@ -66,56 +51,7 @@ module.exports = React.createClass({ if (msgtype && tileTypes[msgtype]) { TileType = tileTypes[msgtype]; } - var classes = classNames({ - mx_MessageTile: true, - mx_MessageTile_sending: ['sending', 'queued'].indexOf( - this.props.mxEvent.status - ) !== -1, - mx_MessageTile_notSent: this.props.mxEvent.status == 'not_sent', - mx_MessageTile_highlight: this.shouldHighlight(), - mx_MessageTile_continuation: this.props.continuation, - mx_MessageTile_last: this.props.last, - menu: this.state.menu - }); - var timestamp = - var editButton = ( - - ); - var aux = null; - if (msgtype === 'm.image') aux = "sent an image"; - else if (msgtype === 'm.video') aux = "sent a video"; - else if (msgtype === 'm.file') aux = "uploaded a file"; - - var avatar, sender, resend; - if (!this.props.continuation) { - if (this.props.mxEvent.sender) { - avatar = ( -
    - -
    - ); - } - sender = ; - } - if (this.props.mxEvent.status === "not_sent" && !this.state.resending) { - resend = ; - } - return ( -
    - { avatar } - { sender } -
    - { timestamp } - { editButton } - -
    -
    - ); + return ; }, }); diff --git a/src/skins/vector/views/molecules/RoomSettings.js b/src/skins/vector/views/molecules/RoomSettings.js index d6d36a13b3..c5e08ff966 100644 --- a/src/skins/vector/views/molecules/RoomSettings.js +++ b/src/skins/vector/views/molecules/RoomSettings.js @@ -18,6 +18,7 @@ limitations under the License. var React = require('react'); var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); +var sdk = require('matrix-react-sdk'); var RoomSettingsController = require('matrix-react-sdk/lib/controllers/molecules/RoomSettings') @@ -65,6 +66,8 @@ module.exports = React.createClass({ }, render: function() { + var ChangeAvatar = sdk.getComponent('molecules.ChangeAvatar'); + var topic = this.props.room.currentState.getStateEvents('m.room.topic', ''); if (topic) topic = topic.getContent().topic; @@ -76,6 +79,8 @@ module.exports = React.createClass({ var power_levels = this.props.room.currentState.getStateEvents('m.room.power_levels', ''); + var events_levels = power_levels.events || {}; + if (power_levels) { power_levels = power_levels.getContent(); @@ -91,8 +96,7 @@ module.exports = React.createClass({ if (power_levels.kick == undefined) kick_level = 50; if (power_levels.redact == undefined) redact_level = 50; - var user_levels = power_levels.users || []; - var events_levels = power_levels.events || []; + var user_levels = power_levels.users || {}; var user_id = MatrixClientPeg.get().credentials.userId; @@ -124,7 +128,21 @@ module.exports = React.createClass({ var can_change_levels = false; } - var banned = this.props.room.getMembersWithMemership("ban"); + var room_avatar_level = parseInt(power_levels.state_default || 0); + if (events_levels['m.room.avatar'] !== undefined) { + room_avatar_level = events_levels['m.room.avatar']; + } + var can_set_room_avatar = current_user_level >= room_avatar_level; + + var change_avatar; + if (can_set_room_avatar) { + change_avatar =
    +

    Room Icon

    + +
    ; + } + + var banned = this.props.room.getMembersWithMembership("ban"); return (
    @@ -207,6 +225,7 @@ module.exports = React.createClass({ ); })}
    + {change_avatar}
    ); } diff --git a/src/skins/vector/views/organisms/Notifier.js b/src/skins/vector/views/organisms/Notifier.js index bbd2563db1..b214b4cd16 100644 --- a/src/skins/vector/views/organisms/Notifier.js +++ b/src/skins/vector/views/organisms/Notifier.js @@ -58,15 +58,16 @@ var NotifierView = { if (ev.getContent().body) msg = ev.getContent().body; } - var avatarUrl = Avatar.avatarUrlForMember( + var avatarUrl = ev.sender ? Avatar.avatarUrlForMember( ev.sender, 40, 40, 'crop' - ); + ) : null; var notification = new global.Notification( title, { "body": msg, - "icon": avatarUrl + "icon": avatarUrl, + "tag": "vector" } ); diff --git a/src/skins/vector/views/organisms/RoomView.js b/src/skins/vector/views/organisms/RoomView.js index d1556b6586..f62eb3c3cf 100644 --- a/src/skins/vector/views/organisms/RoomView.js +++ b/src/skins/vector/views/organisms/RoomView.js @@ -149,6 +149,11 @@ module.exports = React.createClass({ var innerProgressStyle = { width: ((this.state.upload.uploadedBytes / this.state.upload.totalBytes) * 100) + '%' }; + var uploadedSize = filesize(this.state.upload.uploadedBytes); + var totalSize = filesize(this.state.upload.totalBytes); + if (uploadedSize.replace(/^.* /,'') === totalSize.replace(/^.* /,'')) { + uploadedSize = uploadedSize.replace(/ .*/, ''); + } statusBar = (
    @@ -157,7 +162,7 @@ module.exports = React.createClass({
    - {filesize(this.state.upload.uploadedBytes).replace(/ .*/, '')} / {filesize(this.state.upload.totalBytes)} + { uploadedSize } / { totalSize }
    Uploading {this.state.upload.fileName}
    diff --git a/src/skins/vector/views/pages/MatrixChat.js b/src/skins/vector/views/pages/MatrixChat.js index e15584599a..c045416bb2 100644 --- a/src/skins/vector/views/pages/MatrixChat.js +++ b/src/skins/vector/views/pages/MatrixChat.js @@ -26,7 +26,6 @@ var Loader = require("react-loader"); var dis = require('matrix-react-sdk/lib/dispatcher'); - module.exports = React.createClass({ displayName: 'MatrixChat', mixins: [MatrixChatController], @@ -40,6 +39,23 @@ module.exports = React.createClass({ componentDidMount: function() { window.addEventListener('resize', this.handleResize); this.handleResize(); + + matrixLinkify.onUserClick = function(e, userId) { + // this can really go anywhere.. + // sprout a MemberInfo context menu. + console.log("Context => %s => %s", userId, e.target); + + var MemberInfo = sdk.getComponent('molecules.MemberInfo'); + var member = new RoomMember(null, href); + ContextualMenu.createMenu(MemberInfo, { + member: member, + right: window.innerWidth - e.pageX, + top: e.pageY, + onFinished: function() { + console.log("^_^ All done!"); + } + }); + }; }, componentWillUnmount: function() { diff --git a/src/skins/vector/views/templates/Register.js b/src/skins/vector/views/templates/Register.js index 638dac1515..24f88b05e2 100644 --- a/src/skins/vector/views/templates/Register.js +++ b/src/skins/vector/views/templates/Register.js @@ -25,10 +25,9 @@ var Loader = require("react-loader"); var RegisterController = require('../../../../controllers/templates/Register') -module.exports = React.createClass({ - DEFAULT_HS_URL: 'https://matrix.org', - DEFAULT_IS_URL: 'https://vector.im', +var config = require('../../../../../config.json'); +module.exports = React.createClass({ displayName: 'Register', mixins: [RegisterController], @@ -39,8 +38,8 @@ module.exports = React.createClass({ }, componentWillMount: function() { - this.customHsUrl = this.DEFAULT_HS_URL; - this.customIsUrl = this.DEFAULT_IS_URL; + this.customHsUrl = config.default_hs_url; + this.customIsUrl = config.default_is_url; }, getRegFormVals: function() { @@ -56,7 +55,7 @@ module.exports = React.createClass({ if (this.state.serverConfigVisible) { return this.customHsUrl; } else { - return this.DEFAULT_HS_URL; + return config.default_hs_url; } }, @@ -64,7 +63,7 @@ module.exports = React.createClass({ if (this.state.serverConfigVisible) { return this.customIsUrl; } else { - return this.DEFAULT_IS_URL; + return config.default_is_url; } }, @@ -173,6 +172,12 @@ module.exports = React.createClass({ case this.FieldErrors.InUse: strings.push(keys[i]+" is already taken"); break; + case this.FieldErrors.Length: + strings.push(keys[i] + " is not long enough."); + break; + default: + console.error("Unhandled FieldError: %s", bad[keys[i]]); + break; } } var errtxt = strings.join(', '); diff --git a/webpack.config.js b/webpack.config.js index 7868699dab..929e57d71b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -20,7 +20,8 @@ module.exports = { // removed which gives a tree with matrix-react-sdk and vector // trees smashed together, but this fixes everything being under // various levels of '.' and '..' - return info.resourcePath.replace(/^[\/\.]*/, ''); + // Also, sometimes the resource path is absolute. + return path.relative(process.cwd(), info.resourcePath).replace(/^[\/\.]*/, ''); } }, resolve: {