Merge branch 'develop' into kegan/delete-empty-files

Conflicts:
	src/skins/vector/views/molecules/EventAsTextTile.js
This commit is contained in:
Kegan Dougal 2015-10-27 09:19:08 +00:00
commit c91b642a8b
23 changed files with 353 additions and 143 deletions

View File

@ -22,14 +22,27 @@ into the `vector` directory and run your own server.
Development 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` `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 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`. the skin's index by running, `npm run reskindex`.

View File

@ -49,15 +49,25 @@ module.exports = {
var position = { var position = {
top: props.top - 20, top: props.top - 20,
right: props.right + 8,
}; };
var chevron = null;
if (props.left) {
chevron = <img className="mx_ContextualMenu_chevron_left" src="img/chevron-left.png" width="9" height="16" />
position.left = props.left + 8;
} else {
chevron = <img className="mx_ContextualMenu_chevron_right" src="img/chevron-right.png" width="9" height="16" />
position.right = props.right + 8;
}
var className = 'mx_ContextualMenu_wrapper';
// FIXME: If a menu uses getDefaultProps it clobbers the onFinished // FIXME: If a menu uses getDefaultProps it clobbers the onFinished
// property set here so you can't close the menu from a button click! // property set here so you can't close the menu from a button click!
var menu = ( var menu = (
<div className="mx_ContextualMenu_wrapper"> <div className={className}>
<div className="mx_ContextualMenu" style={position}> <div className="mx_ContextualMenu" style={position}>
<img className="mx_ContextualMenu_chevron" src="img/chevron-right.png" width="9" height="16" /> {chevron}
<Element {...props} onFinished={closeMenu}/> <Element {...props} onFinished={closeMenu}/>
</div> </div>
<div className="mx_ContextualMenu_background" onClick={closeMenu}></div> <div className="mx_ContextualMenu_background" onClick={closeMenu}></div>

View File

@ -33,6 +33,7 @@ module.exports = {
cli.on("Room", this.onRoom); cli.on("Room", this.onRoom);
cli.on("Room.timeline", this.onRoomTimeline); cli.on("Room.timeline", this.onRoomTimeline);
cli.on("Room.name", this.onRoomName); cli.on("Room.name", this.onRoomName);
cli.on("RoomState.events", this.onRoomStateEvents);
var rooms = this.getRoomList(); var rooms = this.getRoomList();
this.setState({ this.setState({
@ -66,6 +67,7 @@ module.exports = {
MatrixClientPeg.get().removeListener("Room", this.onRoom); MatrixClientPeg.get().removeListener("Room", this.onRoom);
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline); MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName); MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents);
} }
}, },
@ -110,6 +112,11 @@ module.exports = {
this.refreshRoomList(); this.refreshRoomList();
}, },
onRoomStateEvents: function(ev, state) {
setTimeout(this.refreshRoomList, 0);
},
refreshRoomList: function() { refreshRoomList: function() {
var rooms = this.getRoomList(); var rooms = this.getRoomList();
this.setState({ this.setState({

View File

@ -63,6 +63,7 @@ module.exports = {
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline); MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName); MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
MatrixClientPeg.get().removeListener("RoomMember.typing", this.onRoomMemberTyping); MatrixClientPeg.get().removeListener("RoomMember.typing", this.onRoomMemberTyping);
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
} }
}, },
@ -356,23 +357,20 @@ module.exports = {
}, },
getEventTiles: function() { 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 DateSeparator = sdk.getComponent('molecules.DateSeparator');
var ret = []; var ret = [];
var count = 0; 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) { for (var i = this.state.room.timeline.length-1; i >= 0 && count < this.state.messageCap; --i) {
var mxEv = this.state.room.timeline[i]; var mxEv = this.state.room.timeline[i];
var TileType = tileTypes[mxEv.getType()];
if (!EventTile.supportsEventType(mxEv.getType())) {
continue;
}
var continuation = false; var continuation = false;
var last = false; var last = false;
var dateSeparator = null; 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 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(); var ts1 = this.state.room.timeline[i].getTs();
dateSeparator = <DateSeparator key={ts1} ts={ts1}/>; dateSeparator = <li key={ts1}><DateSeparator ts={ts1}/></li>;
continuation = false; continuation = false;
} }
if (!TileType) continue;
ret.unshift( ret.unshift(
<li key={mxEv.getId()}><TileType mxEvent={mxEv} continuation={continuation} last={last}/></li> <li key={mxEv.getId()}><EventTile mxEvent={mxEv} continuation={continuation} last={last}/></li>
); );
if (dateSeparator) { if (dateSeparator) {
ret.unshift(dateSeparator); ret.unshift(dateSeparator);

View File

@ -67,13 +67,20 @@ a:visited {
padding: 6px; padding: 6px;
} }
.mx_ContextualMenu_chevron { .mx_ContextualMenu_chevron_right {
padding: 12px; padding: 12px;
position: absolute; position: absolute;
right: -21px; right: -21px;
top: 0px; top: 0px;
} }
.mx_ContextualMenu_chevron_left {
padding: 12px;
position: absolute;
left: -21px;
top: 0px;
}
.mx_ContextualMenu_field { .mx_ContextualMenu_field {
padding: 3px 6px 3px 6px; padding: 3px 6px 3px 6px;
cursor: pointer; cursor: pointer;

View File

@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
.mx_MessageTile { .mx_EventTile {
max-width: 100%; max-width: 100%;
clear: both; clear: both;
margin-top: 32px; margin-top: 32px;
margin-left: 56px; margin-left: 56px;
} }
.mx_MessageTile_avatar { .mx_EventTile_avatar {
padding-left: 12px; padding-left: 12px;
padding-right: 12px; padding-right: 12px;
margin-left: -64px; margin-left: -64px;
@ -29,17 +29,17 @@ limitations under the License.
float: left; float: left;
} }
.mx_MessageTile_avatar img { .mx_EventTile_avatar img {
background-color: #dbdbdb; background-color: #dbdbdb;
border-radius: 20px; border-radius: 20px;
border: 0px; border: 0px;
} }
.mx_MessageTile_continuation { .mx_EventTile_continuation {
margin-top: 8px ! important; margin-top: 8px ! important;
} }
.mx_MessageTile .mx_SenderProfile { .mx_EventTile .mx_SenderProfile {
color: #454545; color: #454545;
opacity: 0.5; opacity: 0.5;
font-size: 14px; font-size: 14px;
@ -47,35 +47,35 @@ limitations under the License.
display: block; display: block;
} }
.mx_MessageTile .mx_MessageTimestamp { .mx_EventTile .mx_MessageTimestamp {
color: #454545; color: #454545;
opacity: 0.5; opacity: 0.5;
font-size: 14px; font-size: 14px;
float: right; float: right;
} }
.mx_MessageTile_content { .mx_EventTile_content {
padding-right: 100px; padding-right: 100px;
display: block; display: block;
} }
.mx_MessageTile_notice .mx_MessageTile_content { .mx_EventTile_notice .mx_MessageTile_content {
opacity: 0.5; opacity: 0.5;
} }
.mx_MessageTile_sending { .mx_EventTile_sending {
color: #ddd; color: #ddd;
} }
.mx_MessageTile_notSent { .mx_EventTile_notSent {
color: #f11; color: #f11;
} }
.mx_MessageTile_highlight { .mx_EventTile_highlight {
color: #FF0064; color: #FF0064;
} }
.mx_MessageTile_msgOption { .mx_EventTile_msgOption {
float: right; float: right;
} }
@ -83,29 +83,30 @@ limitations under the License.
display: none; display: none;
} }
.mx_MessageTile_last .mx_MessageTimestamp { .mx_EventTile_last .mx_MessageTimestamp {
display: block; display: block;
} }
.mx_MessageTile:hover .mx_MessageTimestamp { .mx_EventTile:hover .mx_MessageTimestamp {
display: block; display: block;
} }
.mx_MessageTile_editButton { .mx_EventTile_editButton {
float: right; float: right;
display: none; display: none;
border: 0px; border: 0px;
outline: none; outline: none;
margin-right: 3px;
} }
.mx_MessageTile:hover .mx_MessageTile_editButton { .mx_EventTile:hover .mx_EventTile_editButton {
display: inline-block; display: inline-block;
} }
.mx_MessageTile.menu .mx_MessageTile_editButton { .mx_EventTile.menu .mx_EventTile_editButton {
display: inline-block; display: inline-block;
} }
.mx_MessageTile.menu .mx_MessageTimestamp { .mx_EventTile.menu .mx_MessageTimestamp {
display: inline-block; display: inline-block;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

View File

@ -41,6 +41,7 @@ skin['molecules.ChangeDisplayName'] = require('./views/molecules/ChangeDisplayNa
skin['molecules.ChangePassword'] = require('./views/molecules/ChangePassword'); skin['molecules.ChangePassword'] = require('./views/molecules/ChangePassword');
skin['molecules.DateSeparator'] = require('./views/molecules/DateSeparator'); skin['molecules.DateSeparator'] = require('./views/molecules/DateSeparator');
skin['molecules.EventAsTextTile'] = require('./views/molecules/EventAsTextTile'); skin['molecules.EventAsTextTile'] = require('./views/molecules/EventAsTextTile');
skin['molecules.EventTile'] = require('./views/molecules/EventTile');
skin['molecules.MatrixToolbar'] = require('./views/molecules/MatrixToolbar'); skin['molecules.MatrixToolbar'] = require('./views/molecules/MatrixToolbar');
skin['molecules.MemberInfo'] = require('./views/molecules/MemberInfo'); skin['molecules.MemberInfo'] = require('./views/molecules/MemberInfo');
skin['molecules.MemberTile'] = require('./views/molecules/MemberTile'); skin['molecules.MemberTile'] = require('./views/molecules/MemberTile');

View File

@ -42,7 +42,7 @@ module.exports = React.createClass({
return days[date.getDay()] + ", " + months[date.getMonth()] + " " + (date.getDay()+1) + " " + pad(date.getHours()) + ':' + pad(date.getMinutes()); return days[date.getDay()] + ", " + months[date.getMonth()] + " " + (date.getDay()+1) + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
} }
else { 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());
} }
}, },

View File

@ -17,6 +17,7 @@ limitations under the License.
'use strict'; 'use strict';
var React = require('react'); var React = require('react');
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var RoomAvatarController = require('matrix-react-sdk/lib/controllers/atoms/RoomAvatar') var RoomAvatarController = require('matrix-react-sdk/lib/controllers/atoms/RoomAvatar')
@ -24,10 +25,31 @@ module.exports = React.createClass({
displayName: 'RoomAvatar', displayName: 'RoomAvatar',
mixins: [RoomAvatarController], 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() { render: function() {
var style = {
maxWidth: this.props.width,
maxHeight: this.props.height,
};
return ( return (
<img className="mx_RoomAvatar" src={this.state.imageUrl} onError={this.onError} <img className="mx_RoomAvatar" src={this.state.imageUrl} onError={this.onError}
width={this.props.width} height={this.props.height} style={style}
/> />
); );
} }

View File

@ -18,6 +18,7 @@ limitations under the License.
var React = require('react'); var React = require('react');
var sdk = require('matrix-react-sdk')
var ChangeAvatarController = require('matrix-react-sdk/lib/controllers/molecules/ChangeAvatar') var ChangeAvatarController = require('matrix-react-sdk/lib/controllers/molecules/ChangeAvatar')
var Loader = require("react-loader"); var Loader = require("react-loader");
@ -28,6 +29,7 @@ module.exports = React.createClass({
mixins: [ChangeAvatarController], mixins: [ChangeAvatarController],
onFileSelected: function(ev) { onFileSelected: function(ev) {
this.avatarSet = true;
this.setAvatarFromFile(ev.target.files[0]); this.setAvatarFromFile(ev.target.files[0]);
}, },
@ -38,13 +40,27 @@ module.exports = React.createClass({
}, },
render: function() { 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 = <RoomAvatar room={this.props.room} width='320' height='240' resizeMethod='scale' />;
} else {
var style = {
maxWidth: 320,
maxHeight: 240,
};
avatarImg = <img src={this.state.avatarUrl} style={style} />;
}
switch (this.state.phase) { switch (this.state.phase) {
case this.Phases.Display: case this.Phases.Display:
case this.Phases.Error: case this.Phases.Error:
return ( return (
<div> <div>
<div className="mx_Dialog_content"> <div className="mx_Dialog_content">
<img src={this.state.avatarUrl}/> {avatarImg}
</div> </div>
<div className="mx_Dialog_content"> <div className="mx_Dialog_content">
Upload new: Upload new:

View File

@ -24,25 +24,19 @@ var TextForEvent = require('matrix-react-sdk/lib/TextForEvent');
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'EventAsTextTile', displayName: 'EventAsTextTile',
render: function() { statics: {
var MessageTimestamp = sdk.getComponent('atoms.MessageTimestamp'); needsSenderProfile: function() {
var MemberAvatar = sdk.getComponent('atoms.MemberAvatar'); return false;
}
},
render: function() {
var text = TextForEvent.textForEvent(this.props.mxEvent); var text = TextForEvent.textForEvent(this.props.mxEvent);
if (text == null || text.length == 0) return null; if (text == null || text.length == 0) return null;
var timestamp = this.props.last ? <MessageTimestamp ts={this.props.mxEvent.getTs()} /> : null;
var avatar = this.props.mxEvent.sender ? <MemberAvatar member={this.props.mxEvent.sender} /> : null;
return ( return (
<div className="mx_MessageTile mx_MessageTile_notice"> <div className="mx_EventAsTextTile">
<div className="mx_MessageTile_avatar"> {TextForEvent.textForEvent(this.props.mxEvent)}
{ avatar }
</div>
{ timestamp }
<span className="mx_SenderProfile"></span>
<span className="mx_MessageTile_content">
{TextForEvent.textForEvent(this.props.mxEvent)}
</span>
</div> </div>
); );
}, },

View File

@ -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 = <MessageTimestamp ts={this.props.mxEvent.getTs()} />
var editButton = (
<input
type="image" src="img/edit.png" alt="Edit"
className="mx_EventTile_editButton" onClick={this.onEditClicked}
/>
);
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 = (
<div className="mx_EventTile_avatar">
<MemberAvatar member={this.props.mxEvent.sender} />
</div>
);
}
if (EventTileType.needsSenderProfile()) {
sender = <SenderProfile mxEvent={this.props.mxEvent} aux={aux} />;
}
}
return (
<div className={classes}>
{ avatar }
{ sender }
<div>
{ timestamp }
{ editButton }
<EventTileType mxEvent={this.props.mxEvent} />
</div>
</div>
);
},
});

View File

@ -52,9 +52,27 @@ module.exports = React.createClass({
if (this.props.onFinished) this.props.onFinished(); 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() { render: function() {
var resendButton; var resendButton;
var viewSourceButton; var viewSourceButton;
var redactButton;
if (this.props.mxEvent.status == 'not_sent') { if (this.props.mxEvent.status == 'not_sent') {
resendButton = ( resendButton = (
@ -63,6 +81,13 @@ module.exports = React.createClass({
</div> </div>
); );
} }
else {
redactButton = (
<div className="mx_ContextualMenu_field" onClick={this.onRedactClick}>
Delete
</div>
);
}
viewSourceButton = ( viewSourceButton = (
<div className="mx_ContextualMenu_field" onClick={this.onViewSourceClick}> <div className="mx_ContextualMenu_field" onClick={this.onViewSourceClick}>
View Source View Source
@ -72,6 +97,7 @@ module.exports = React.createClass({
return ( return (
<div> <div>
{resendButton} {resendButton}
{redactButton}
{viewSourceButton} {viewSourceButton}
</div> </div>
); );

View File

@ -28,28 +28,13 @@ module.exports = React.createClass({
displayName: 'MessageTile', displayName: 'MessageTile',
mixins: [MessageTileController], mixins: [MessageTileController],
onEditClicked: function(e) { statics: {
var MessageContextMenu = sdk.getComponent('molecules.MessageContextMenu'); needsSenderProfile: function() {
var buttonRect = e.target.getBoundingClientRect() return true;
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});
}, },
render: function() { 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 UnknownMessageTile = sdk.getComponent('molecules.UnknownMessageTile');
var tileTypes = { var tileTypes = {
@ -66,56 +51,7 @@ module.exports = React.createClass({
if (msgtype && tileTypes[msgtype]) { if (msgtype && tileTypes[msgtype]) {
TileType = 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 = <MessageTimestamp ts={this.props.mxEvent.getTs()} />
var editButton = (
<input
type="image" src="img/edit.png" alt="Edit"
className="mx_MessageTile_editButton" onClick={this.onEditClicked}
/>
);
var aux = null; return <TileType mxEvent={this.props.mxEvent} />;
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 = (
<div className="mx_MessageTile_avatar">
<MemberAvatar member={this.props.mxEvent.sender} />
</div>
);
}
sender = <SenderProfile mxEvent={this.props.mxEvent} aux={aux} />;
}
if (this.props.mxEvent.status === "not_sent" && !this.state.resending) {
resend = <button className="mx_MessageTile_msgOption" onClick={this.onResend}>
Resend
</button>;
}
return (
<div className={classes}>
{ avatar }
{ sender }
<div>
{ timestamp }
{ editButton }
<TileType mxEvent={this.props.mxEvent} />
</div>
</div>
);
}, },
}); });

View File

@ -18,6 +18,7 @@ limitations under the License.
var React = require('react'); var React = require('react');
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var sdk = require('matrix-react-sdk');
var RoomSettingsController = require('matrix-react-sdk/lib/controllers/molecules/RoomSettings') var RoomSettingsController = require('matrix-react-sdk/lib/controllers/molecules/RoomSettings')
@ -65,6 +66,8 @@ module.exports = React.createClass({
}, },
render: function() { render: function() {
var ChangeAvatar = sdk.getComponent('molecules.ChangeAvatar');
var topic = this.props.room.currentState.getStateEvents('m.room.topic', ''); var topic = this.props.room.currentState.getStateEvents('m.room.topic', '');
if (topic) topic = topic.getContent().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 power_levels = this.props.room.currentState.getStateEvents('m.room.power_levels', '');
var events_levels = power_levels.events || {};
if (power_levels) { if (power_levels) {
power_levels = power_levels.getContent(); power_levels = power_levels.getContent();
@ -91,8 +96,7 @@ module.exports = React.createClass({
if (power_levels.kick == undefined) kick_level = 50; if (power_levels.kick == undefined) kick_level = 50;
if (power_levels.redact == undefined) redact_level = 50; if (power_levels.redact == undefined) redact_level = 50;
var user_levels = power_levels.users || []; var user_levels = power_levels.users || {};
var events_levels = power_levels.events || [];
var user_id = MatrixClientPeg.get().credentials.userId; var user_id = MatrixClientPeg.get().credentials.userId;
@ -124,7 +128,21 @@ module.exports = React.createClass({
var can_change_levels = false; 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 = <div>
<h3>Room Icon</h3>
<ChangeAvatar room={this.props.room} />
</div>;
}
var banned = this.props.room.getMembersWithMembership("ban");
return ( return (
<div className="mx_RoomSettings"> <div className="mx_RoomSettings">
@ -207,6 +225,7 @@ module.exports = React.createClass({
); );
})} })}
</div> </div>
{change_avatar}
</div> </div>
); );
} }

View File

@ -58,15 +58,16 @@ var NotifierView = {
if (ev.getContent().body) msg = ev.getContent().body; if (ev.getContent().body) msg = ev.getContent().body;
} }
var avatarUrl = Avatar.avatarUrlForMember( var avatarUrl = ev.sender ? Avatar.avatarUrlForMember(
ev.sender, 40, 40, 'crop' ev.sender, 40, 40, 'crop'
); ) : null;
var notification = new global.Notification( var notification = new global.Notification(
title, title,
{ {
"body": msg, "body": msg,
"icon": avatarUrl "icon": avatarUrl,
"tag": "vector"
} }
); );

View File

@ -149,6 +149,11 @@ module.exports = React.createClass({
var innerProgressStyle = { var innerProgressStyle = {
width: ((this.state.upload.uploadedBytes / this.state.upload.totalBytes) * 100) + '%' 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 = ( statusBar = (
<div className="mx_RoomView_uploadBar"> <div className="mx_RoomView_uploadBar">
<div className="mx_RoomView_uploadProgressOuter"> <div className="mx_RoomView_uploadProgressOuter">
@ -157,7 +162,7 @@ module.exports = React.createClass({
<img className="mx_RoomView_uploadIcon" src="img/fileicon.png" width="40" height="40"/> <img className="mx_RoomView_uploadIcon" src="img/fileicon.png" width="40" height="40"/>
<img className="mx_RoomView_uploadCancel" src="img/cancel.png" width="40" height="40"/> <img className="mx_RoomView_uploadCancel" src="img/cancel.png" width="40" height="40"/>
<div className="mx_RoomView_uploadBytes"> <div className="mx_RoomView_uploadBytes">
{filesize(this.state.upload.uploadedBytes).replace(/ .*/, '')} / {filesize(this.state.upload.totalBytes)} { uploadedSize } / { totalSize }
</div> </div>
<div className="mx_RoomView_uploadFilename">Uploading {this.state.upload.fileName}</div> <div className="mx_RoomView_uploadFilename">Uploading {this.state.upload.fileName}</div>
</div> </div>

View File

@ -26,7 +26,6 @@ var Loader = require("react-loader");
var dis = require('matrix-react-sdk/lib/dispatcher'); var dis = require('matrix-react-sdk/lib/dispatcher');
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'MatrixChat', displayName: 'MatrixChat',
mixins: [MatrixChatController], mixins: [MatrixChatController],
@ -40,6 +39,23 @@ module.exports = React.createClass({
componentDidMount: function() { componentDidMount: function() {
window.addEventListener('resize', this.handleResize); window.addEventListener('resize', this.handleResize);
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() { componentWillUnmount: function() {

View File

@ -25,10 +25,9 @@ var Loader = require("react-loader");
var RegisterController = require('../../../../controllers/templates/Register') var RegisterController = require('../../../../controllers/templates/Register')
module.exports = React.createClass({ var config = require('../../../../../config.json');
DEFAULT_HS_URL: 'https://matrix.org',
DEFAULT_IS_URL: 'https://vector.im',
module.exports = React.createClass({
displayName: 'Register', displayName: 'Register',
mixins: [RegisterController], mixins: [RegisterController],
@ -39,8 +38,8 @@ module.exports = React.createClass({
}, },
componentWillMount: function() { componentWillMount: function() {
this.customHsUrl = this.DEFAULT_HS_URL; this.customHsUrl = config.default_hs_url;
this.customIsUrl = this.DEFAULT_IS_URL; this.customIsUrl = config.default_is_url;
}, },
getRegFormVals: function() { getRegFormVals: function() {
@ -56,7 +55,7 @@ module.exports = React.createClass({
if (this.state.serverConfigVisible) { if (this.state.serverConfigVisible) {
return this.customHsUrl; return this.customHsUrl;
} else { } else {
return this.DEFAULT_HS_URL; return config.default_hs_url;
} }
}, },
@ -64,7 +63,7 @@ module.exports = React.createClass({
if (this.state.serverConfigVisible) { if (this.state.serverConfigVisible) {
return this.customIsUrl; return this.customIsUrl;
} else { } else {
return this.DEFAULT_IS_URL; return config.default_is_url;
} }
}, },
@ -173,6 +172,12 @@ module.exports = React.createClass({
case this.FieldErrors.InUse: case this.FieldErrors.InUse:
strings.push(keys[i]+" is already taken"); strings.push(keys[i]+" is already taken");
break; 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(', '); var errtxt = strings.join(', ');

View File

@ -20,7 +20,8 @@ module.exports = {
// removed which gives a tree with matrix-react-sdk and vector // removed which gives a tree with matrix-react-sdk and vector
// trees smashed together, but this fixes everything being under // trees smashed together, but this fixes everything being under
// various levels of '.' and '..' // various levels of '.' and '..'
return info.resourcePath.replace(/^[\/\.]*/, ''); // Also, sometimes the resource path is absolute.
return path.relative(process.cwd(), info.resourcePath).replace(/^[\/\.]*/, '');
} }
}, },
resolve: { resolve: {