Merge remote-tracking branch 'origin/develop' into read_receipts

This commit is contained in:
David Baker 2015-11-10 11:26:42 +00:00
commit 450036a6ed
56 changed files with 1245 additions and 294 deletions

View File

@ -31,10 +31,15 @@
"modernizr": "^3.1.0",
"matrix-js-sdk": "https://github.com/matrix-org/matrix-js-sdk.git#develop",
"matrix-react-sdk": "^0.0.2",
"modernizr": "^3.1.0",
"q": "^1.4.1",
"react": "^0.13.3",
"react": "^0.14.2",
"react-dnd": "^2.0.2",
"react-dnd-html5-backend": "^2.0.0",
"react-dom": "^0.14.2",
"react-gemini-scrollbar": "^2.0.1",
"react-loader": "^1.4.0",
"sanitize-html": "^1.11.1"
"sanitize-html": "^1.0.0"
},
"devDependencies": {
"babel": "^5.8.23",

View File

@ -18,6 +18,7 @@ limitations under the License.
'use strict';
var React = require('react');
var ReactDOM = require('react-dom');
// Shamelessly ripped off Modal.js. There's probably a better way
// of doing reusable widgets like dialog boxes & menus where we go and
@ -74,7 +75,7 @@ module.exports = {
</div>
);
React.render(menu, this.getOrCreateContainer());
ReactDOM.render(menu, this.getOrCreateContainer());
return {close: closeMenu};
},

24
src/Resend.js Normal file
View File

@ -0,0 +1,24 @@
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var dis = require('matrix-react-sdk/lib/dispatcher');
module.exports = {
resend: function(event) {
MatrixClientPeg.get().resendEvent(
event, MatrixClientPeg.get().getRoom(event.getRoomId())
).done(function() {
dis.dispatch({
action: 'message_sent',
event: event
});
}, function() {
dis.dispatch({
action: 'message_send_failed',
event: event
});
});
dis.dispatch({
action: 'message_resend_started',
event: event
});
},
};

View File

@ -17,22 +17,31 @@ limitations under the License.
'use strict';
var React = require("react");
var ReactDOM = require("react-dom");
var MatrixClientPeg = require("matrix-react-sdk/lib/MatrixClientPeg");
var RoomListSorter = require("matrix-react-sdk/lib/RoomListSorter");
var dis = require("matrix-react-sdk/lib/dispatcher");
var sdk = require('matrix-react-sdk');
var VectorConferenceHandler = require("../../modules/VectorConferenceHandler");
var CallHandler = require("matrix-react-sdk/lib/CallHandler");
var HIDE_CONFERENCE_CHANS = true;
module.exports = {
getInitialState: function() {
return {
activityMap: null,
lists: {},
}
},
componentWillMount: function() {
var cli = MatrixClientPeg.get();
cli.on("Room", this.onRoom);
cli.on("Room.timeline", this.onRoomTimeline);
cli.on("Room.name", this.onRoomName);
cli.on("Room.tags", this.onRoomTags);
cli.on("RoomState.events", this.onRoomStateEvents);
cli.on("RoomMember.name", this.onRoomMemberName);
@ -47,11 +56,6 @@ module.exports = {
onAction: function(payload) {
switch (payload.action) {
// listen for call state changes to prod the render method, which
// may hide the global CallView if the call it is tracking is dead
case 'call_state':
this._recheckCallElement(this.props.selectedRoom);
break;
case 'view_tooltip':
this.tooltip = payload.tooltip;
this._repositionTooltip();
@ -72,7 +76,6 @@ module.exports = {
componentWillReceiveProps: function(newProps) {
this.state.activityMap[newProps.selectedRoom] = undefined;
this._recheckCallElement(newProps.selectedRoom);
this.setState({
activityMap: this.state.activityMap
});
@ -109,6 +112,10 @@ module.exports = {
this.refreshRoomList();
},
onRoomTags: function(event, room) {
this.refreshRoomList();
},
onRoomStateEvents: function(ev, state) {
setTimeout(this.refreshRoomList, 0);
},
@ -117,26 +124,36 @@ module.exports = {
setTimeout(this.refreshRoomList, 0);
},
refreshRoomList: function() {
// TODO: rather than bluntly regenerating and re-sorting everything
// every time we see any kind of room change from the JS SDK
// we could do incremental updates on our copy of the state
// based on the room which has actually changed. This would stop
// us re-rendering all the sublists every time anything changes anywhere
// in the state of the client.
this.setState(this.getRoomLists());
},
getRoomLists: function() {
var s = {};
var inviteList = [];
s.roomList = RoomListSorter.mostRecentActivityFirst(
MatrixClientPeg.get().getRooms().filter(function(room) {
var s = { lists: {} };
s.lists["m.invite"] = [];
s.lists["m.favourite"] = [];
s.lists["m.recent"] = [];
s.lists["m.lowpriority"] = [];
s.lists["m.archived"] = [];
MatrixClientPeg.get().getRooms().forEach(function(room) {
var me = room.getMember(MatrixClientPeg.get().credentials.userId);
if (me && me.membership == "invite") {
inviteList.push(room);
return false;
s.lists["m.invite"].push(room);
}
else {
var shouldShowRoom = (
me && (me.membership == "join")
);
// hiding conf rooms only ever toggles shouldShowRoom to false
if (shouldShowRoom && HIDE_CONFERENCE_CHANS) {
// we want to hide the 1:1 conf<->user room and not the group chat
@ -151,48 +168,34 @@ module.exports = {
}
}
}
return shouldShowRoom;
})
);
s.inviteList = RoomListSorter.mostRecentActivityFirst(inviteList);
return s;
},
_recheckCallElement: function(selectedRoomId) {
// if we aren't viewing a room with an ongoing call, but there is an
// active call, show the call element - we need to do this to make
// audio/video not crap out
var activeCall = CallHandler.getAnyActiveCall();
var callForRoom = CallHandler.getCallForRoom(selectedRoomId);
var showCall = (activeCall && !callForRoom);
this.setState({
show_call_element: showCall
if (shouldShowRoom) {
var tagNames = Object.keys(room.tags);
if (tagNames.length) {
for (var i = 0; i < tagNames.length; i++) {
var tagName = tagNames[i];
s.lists[tagName] = s.lists[tagName] || [];
s.lists[tagNames[i]].push(room);
}
}
else {
s.lists["m.recent"].push(room);
}
}
}
});
//console.log("calculated new roomLists; m.recent = " + s.lists["m.recent"]);
// we actually apply the sorting to this when receiving the prop in RoomSubLists.
return s;
},
_repositionTooltip: function(e) {
if (this.tooltip && this.tooltip.parentElement) {
var scroll = this.getDOMNode();
this.tooltip.style.top = (scroll.parentElement.offsetTop + this.tooltip.parentElement.offsetTop - scroll.scrollTop) + "px";
var scroll = ReactDOM.findDOMNode(this);
this.tooltip.style.top = (scroll.parentElement.offsetTop + this.tooltip.parentElement.offsetTop - scroll.children[2].scrollTop) + "px";
}
},
makeRoomTiles: function(list, isInvite) {
var self = this;
var RoomTile = sdk.getComponent("molecules.RoomTile");
return list.map(function(room) {
var selected = room.roomId == self.props.selectedRoom;
return (
<RoomTile
room={room}
key={room.roomId}
collapsed={self.props.collapsed}
selected={selected}
unread={self.state.activityMap[room.roomId] === 1}
highlight={self.state.activityMap[room.roomId] === 2}
isInvite={isInvite}
/>
);
});
}
};

View File

@ -17,6 +17,7 @@ limitations under the License.
var Matrix = require("matrix-js-sdk");
var MatrixClientPeg = require("matrix-react-sdk/lib/MatrixClientPeg");
var React = require("react");
var ReactDOM = require("react-dom");
var q = require("q");
var ContentMessages = require("matrix-react-sdk/lib//ContentMessages");
var WhoIsTyping = require("matrix-react-sdk/lib/WhoIsTyping");
@ -24,6 +25,7 @@ var Modal = require("matrix-react-sdk/lib/Modal");
var sdk = require('matrix-react-sdk/lib/index');
var CallHandler = require('matrix-react-sdk/lib/CallHandler');
var VectorConferenceHandler = require('../../modules/VectorConferenceHandler');
var Resend = require("../../Resend");
var dis = require("matrix-react-sdk/lib/dispatcher");
@ -32,8 +34,9 @@ var INITIAL_SIZE = 20;
module.exports = {
getInitialState: function() {
var room = this.props.roomId ? MatrixClientPeg.get().getRoom(this.props.roomId) : null;
return {
room: this.props.roomId ? MatrixClientPeg.get().getRoom(this.props.roomId) : null,
room: room,
messageCap: INITIAL_SIZE,
editingRoomSettings: false,
uploadingRoomSettings: false,
@ -41,6 +44,8 @@ module.exports = {
draggingFile: false,
searching: false,
searchResults: null,
syncState: MatrixClientPeg.get().getSyncState(),
hasUnsentMessages: this._hasUnsentMessages(room)
}
},
@ -51,12 +56,13 @@ module.exports = {
MatrixClientPeg.get().on("Room.receipt", this.onRoomReceipt);
MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping);
MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember);
MatrixClientPeg.get().on("sync", this.onSyncStateChange);
this.atBottom = true;
},
componentWillUnmount: function() {
if (this.refs.messageWrapper) {
var messageWrapper = this.refs.messageWrapper.getDOMNode();
var messageWrapper = ReactDOM.findDOMNode(this.refs.messageWrapper);
messageWrapper.removeEventListener('drop', this.onDrop);
messageWrapper.removeEventListener('dragover', this.onDragOver);
messageWrapper.removeEventListener('dragleave', this.onDragLeaveOrEnd);
@ -69,6 +75,7 @@ module.exports = {
MatrixClientPeg.get().removeListener("Room.receipt", this.onRoomReceipt);
MatrixClientPeg.get().removeListener("RoomMember.typing", this.onRoomMemberTyping);
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
MatrixClientPeg.get().removeListener("sync", this.onSyncStateChange);
}
},
@ -76,6 +83,9 @@ module.exports = {
switch (payload.action) {
case 'message_send_failed':
case 'message_sent':
this.setState({
hasUnsentMessages: this._hasUnsentMessages(this.state.room)
});
case 'message_resend_started':
this.setState({
room: MatrixClientPeg.get().getRoom(this.props.roomId)
@ -92,8 +102,8 @@ module.exports = {
// scroll to bottom
var messageWrapper = this.refs.messageWrapper;
if (messageWrapper) {
messageWrapper = messageWrapper.getDOMNode();
messageWrapper.scrollTop = messageWrapper.scrollHeight;
var messageWrapperScroll = ReactDOM.findDOMNode(messageWrapper).children[2];
messageWrapperScroll.scrollTop = messageWrapperScroll.scrollHeight;
}
}
@ -107,6 +117,12 @@ module.exports = {
}
},
onSyncStateChange: function(state) {
this.setState({
syncState: state
});
},
// MatrixRoom still showing the messages from the old room?
// Set the key to the room_id. Sadly you can no longer get at
// the key from inside the component, or we'd check this in code.
@ -116,7 +132,7 @@ module.exports = {
onRoomTimeline: function(ev, room, toStartOfTimeline) {
if (!this.isMounted()) return;
// ignore anything that comes in whilst pagingating: we get one
// ignore anything that comes in whilst paginating: we get one
// event for each new matrix event so this would cause a huge
// number of UI updates. Just update the UI when the paginate
// call returns.
@ -128,10 +144,10 @@ module.exports = {
if (room.roomId != this.props.roomId) return;
if (this.refs.messageWrapper) {
var messageWrapper = this.refs.messageWrapper.getDOMNode();
var messageWrapperScroll = ReactDOM.findDOMNode(this.refs.messageWrapper).children[2];
this.atBottom = (
messageWrapper.scrollHeight - messageWrapper.scrollTop <=
(messageWrapper.clientHeight + 150)
messageWrapperScroll.scrollHeight - messageWrapperScroll.scrollTop <=
(messageWrapperScroll.clientHeight + 150)
);
}
@ -184,6 +200,19 @@ module.exports = {
this._updateConfCallNotification();
},
_hasUnsentMessages: function(room) {
return this._getUnsentMessages(room).length > 0;
},
_getUnsentMessages: function(room) {
if (!room) { return []; }
// TODO: It would be nice if the JS SDK provided nicer constant-time
// constructs rather than O(N) (N=num msgs) on this.
return room.timeline.filter(function(ev) {
return ev.status === Matrix.EventStatus.NOT_SENT;
});
},
_updateConfCallNotification: function() {
var room = MatrixClientPeg.get().getRoom(this.props.roomId);
if (!room) return;
@ -208,14 +237,16 @@ module.exports = {
componentDidMount: function() {
if (this.refs.messageWrapper) {
var messageWrapper = this.refs.messageWrapper.getDOMNode();
var messageWrapper = ReactDOM.findDOMNode(this.refs.messageWrapper);
messageWrapper.addEventListener('drop', this.onDrop);
messageWrapper.addEventListener('dragover', this.onDragOver);
messageWrapper.addEventListener('dragleave', this.onDragLeaveOrEnd);
messageWrapper.addEventListener('dragend', this.onDragLeaveOrEnd);
messageWrapper.scrollTop = messageWrapper.scrollHeight;
var messageWrapperScroll = messageWrapper.children[2];
messageWrapperScroll.scrollTop = messageWrapperScroll.scrollHeight;
this.sendReadReceipt();
@ -228,17 +259,17 @@ module.exports = {
componentDidUpdate: function() {
if (!this.refs.messageWrapper) return;
var messageWrapper = this.refs.messageWrapper.getDOMNode();
var messageWrapperScroll = ReactDOM.findDOMNode(this.refs.messageWrapper).children[2];
if (this.state.paginating && !this.waiting_for_paginate) {
var heightGained = messageWrapper.scrollHeight - this.oldScrollHeight;
messageWrapper.scrollTop += heightGained;
var heightGained = messageWrapperScroll.scrollHeight - this.oldScrollHeight;
messageWrapperScroll.scrollTop += heightGained;
this.oldScrollHeight = undefined;
if (!this.fillSpace()) {
this.setState({paginating: false});
}
} else if (this.atBottom) {
messageWrapper.scrollTop = messageWrapper.scrollHeight;
messageWrapperScroll.scrollTop = messageWrapperScroll.scrollHeight;
if (this.state.numUnreadMessages !== 0) {
this.setState({numUnreadMessages: 0});
}
@ -247,11 +278,11 @@ module.exports = {
fillSpace: function() {
if (!this.refs.messageWrapper) return;
var messageWrapper = this.refs.messageWrapper.getDOMNode();
if (messageWrapper.scrollTop < messageWrapper.clientHeight && this.state.room.oldState.paginationToken) {
var messageWrapperScroll = ReactDOM.findDOMNode(this.refs.messageWrapper).children[2];
if (messageWrapperScroll.scrollTop < messageWrapperScroll.clientHeight && this.state.room.oldState.paginationToken) {
this.setState({paginating: true});
this.oldScrollHeight = messageWrapper.scrollHeight;
this.oldScrollHeight = messageWrapperScroll.scrollHeight;
if (this.state.messageCap < this.state.room.timeline.length) {
this.waiting_for_paginate = false;
@ -278,6 +309,13 @@ module.exports = {
return false;
},
onResendAllClick: function() {
var eventsToResend = this._getUnsentMessages(this.state.room);
eventsToResend.forEach(function(event) {
Resend.resend(event);
});
},
onJoinButtonClicked: function(ev) {
var self = this;
MatrixClientPeg.get().joinRoom(this.props.roomId).then(function() {
@ -298,9 +336,9 @@ module.exports = {
onMessageListScroll: function(ev) {
if (this.refs.messageWrapper) {
var messageWrapper = this.refs.messageWrapper.getDOMNode();
var messageWrapperScroll = ReactDOM.findDOMNode(this.refs.messageWrapper).children[2];
var wasAtBottom = this.atBottom;
this.atBottom = messageWrapper.scrollHeight - messageWrapper.scrollTop <= messageWrapper.clientHeight;
this.atBottom = messageWrapperScroll.scrollHeight - messageWrapperScroll.scrollTop <= messageWrapperScroll.clientHeight;
if (this.atBottom && !wasAtBottom) {
this.forceUpdate(); // remove unread msg count
}
@ -363,8 +401,12 @@ module.exports = {
self.setState({
upload: undefined
});
}).done(undefined, function() {
// display error message
}).done(undefined, function(error) {
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to upload file",
description: error.toString()
});
});
},
@ -390,6 +432,7 @@ module.exports = {
room_events: {
search_term: term,
filter: filter,
order_by: "recent",
event_context: {
before_limit: 1,
after_limit: 1,
@ -403,7 +446,11 @@ module.exports = {
searchResults: data,
});
}, function(error) {
// TODO: show dialog or something
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Search failed",
description: error.toString()
});
});
},
@ -421,7 +468,7 @@ module.exports = {
var eventIds = Object.keys(results);
// XXX: todo: merge overlapping results somehow?
// XXX: why doesn't searching on name work?
var resultList = eventIds.map(function(key) { return results[key]; }).sort(function(a, b) { b.rank - a.rank });
var resultList = eventIds.map(function(key) { return results[key]; }); // .sort(function(a, b) { b.rank - a.rank });
for (var i = 0; i < resultList.length; i++) {
var ts1 = resultList[i].result.origin_server_ts;
ret.push(<li key={ts1 + "-search"}><DateSeparator ts={ts1}/></li>); // Rank: {resultList[i].rank}

View File

@ -19,3 +19,12 @@ limitations under the License.
border-radius: 20px;
}
.mx_MemberAvatar_initial {
position: absolute;
color: #fff;
text-align: center;
}
.mx_MemberAvatar_wrapper {
position: relative;
}

View File

@ -0,0 +1,25 @@
/*
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.
*/
.mx_RoomAvatar {
}
.mx_RoomAvatar_initial {
position: absolute;
color: #fff;
text-align: center;
font-weight: normal ! important;
}

View File

@ -0,0 +1,25 @@
/*
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.
*/
.mx_Spinner {
display: -webkit-flex;
display: flex;
-webkit-align-items: center;
-webkit-justify-content: center;
align-items: center;
justify-content: center;
height: 100%;
}

View File

@ -0,0 +1 @@
../../../../node_modules/react-gemini-scrollbar/node_modules/gemini-scrollbar/gemini-scrollbar.css

View File

@ -1,4 +1,3 @@
.mx_RoomDropTarget,
.mx_RoomSettings_encrypt,
.mx_CreateRoom_encrypt,
.mx_RightPanel_filebutton

View File

@ -18,13 +18,13 @@ limitations under the License.
max-width: 100%;
clear: both;
margin-top: 24px;
margin-left: 56px;
margin-left: 65px;
}
.mx_EventTile_avatar {
padding-left: 18px;
padding-right: 12px;
margin-left: -64px;
margin-left: -73px;
margin-top: -4px;
float: left;
}
@ -78,7 +78,7 @@ limitations under the License.
}
.mx_EventTile_notSent {
color: #f11;
color: #ddd;
}
.mx_EventTile_highlight {

View File

@ -15,20 +15,40 @@ limitations under the License.
*/
.mx_MatrixToolbar {
text-align: center;
background-color: #ff0064;
background-color: #76cfa6;
color: #fff;
font-weight: bold;
padding: 6px;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-webkit-align-items: center;
align-items: center;
}
.mx_MatrixToolbar button {
margin-left: 12px;
.mx_MatrixToolbar_warning {
margin-left: 16px;
margin-right: 8px;
margin-top: -2px;
}
.mx_MatrixToolbar_link
{
color: #fff ! important;
text-decoration: underline ! important;
cursor: pointer;
}
.mx_MatrixToolbar_close {
float: right;
margin-top: 3px;
margin-right: 12px;
-webkit-flex: 1;
flex: 1;
cursor: pointer;
text-align: right;
}
.mx_MatrixToolbar_close img {
display: block;
float: right;
margin-right: 10px;
}

View File

@ -32,7 +32,7 @@ limitations under the License.
.mx_MessageComposer .mx_MessageComposer_avatar {
display: table-cell;
padding-left: 10px;
padding-right: 20px;
padding-right: 28px;
height: 70px;
}

View File

@ -16,12 +16,46 @@ limitations under the License.
.mx_RoomDropTarget {
font-size: 14px;
text-align: center;
margin-left: 8px;
margin-right: 8px;
padding-top: 16px;
padding-bottom: 16px;
background-color: #fbfbfb;
border: 1px dashed #d7d7d7;
border-radius: 8px;
margin-left: 10px;
margin-right: 15px;
padding-top: 5px;
padding-bottom: 5px;
border: 1px dashed #76cfa6;
color: #454545;
background-color: rgba(255,255,255,0.5);
border-radius: 4px;
}
.collapsed .mx_RoomDropTarget {
margin-right: 10px;
}
.mx_RoomDropTarget_placeholder {
padding-top: 1px;
padding-bottom: 1px;
}
.mx_RoomDropTarget_avatar {
background-color: #fff;
border-radius: 24px;
width: 24px;
height: 24px;
float: left;
margin-left: 7px;
margin-right: 7px;
}
.mx_RoomDropTarget_label {
position: relative;
margin-top: 3px;
line-height: 21px;
z-index: 1;
}
.collapsed .mx_RoomDropTarget_avatar {
float: none;
}
.collapsed .mx_RoomDropTarget_label {
display: none;
}

View File

@ -33,6 +33,7 @@ limitations under the License.
.mx_RoomHeader_leftRow {
height: 48px;
margin-top: 18px;
margin-left: -2px;
-webkit-box-ordinal-group: 1;
-moz-box-ordinal-group: 1;
@ -103,7 +104,7 @@ limitations under the License.
color: #454545;
font-weight: 800;
font-size: 24px;
padding-left: 8px;
padding-left: 19px;
padding-right: 16px;
text-overflow: ellipsis;
}
@ -153,7 +154,7 @@ limitations under the License.
max-height: 38px;
color: #454545;
font-weight: 300;
padding-left: 8px;
padding-left: 19px;
padding-right: 16px;
overflow: hidden;
text-overflow: ellipsis;

View File

@ -16,13 +16,13 @@ limitations under the License.
.mx_RoomTile {
cursor: pointer;
display: table-row;
/* This fixes wrapping of long room names, but breaks drag & drop previews */
/* display: table-row; */
font-size: 14px;
}
.mx_RoomTile_avatar {
display: table-cell;
background: #eaf5f0;
padding-right: 8px;
padding-top: 4px;
padding-bottom: 2px;
@ -39,17 +39,16 @@ limitations under the License.
.mx_RoomTile_name {
display: table-cell;
width: 100%;
vertical-align: middle;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 16px;
color: #454545;
opacity: 0.8;
color: rgba(69, 69, 69, 0.8);
}
.mx_RoomTile_invite {
opacity: 0.5;
font-weight: normal;
color: rgba(69, 69, 69, 0.5);
}
.collapsed .mx_RoomTile_name {
@ -106,15 +105,16 @@ limitations under the License.
.mx_RoomTile_unread,
.mx_RoomTile_highlight,
.mx_RoomTile_invited
.mx_RoomTile_selected
{
font-weight: bold;
}
.mx_RoomTile_selected {
.mx_RoomTile_selected .mx_RoomTile_name {
color: #76cfa6 ! important;
}
.mx_RoomTile.mx_RoomTile_selected {
.mx_RoomTile.mx_RoomTile_selected .mx_RoomTile_name {
background: url('img/selected.png');
background-repeat: no-repeat;
background-position: right center;

View File

@ -21,7 +21,6 @@ limitations under the License.
border-radius: 8px;
background-color: #fff;
z-index: 1000;
margin-top: 6px;
left: 64px;
padding: 6px;
}

View File

@ -34,16 +34,21 @@ limitations under the License.
cursor: pointer;
}
.mx_LeftPanel .mx_RoomList {
.mx_LeftPanel_callView {
}
.mx_LeftPanel .mx_RoomList_scrollbar {
-webkit-box-ordinal-group: 1;
-moz-box-ordinal-group: 1;
-ms-flex-order: 1;
-webkit-order: 1;
order: 1;
overflow-y: auto;
-webkit-flex: 1 1 0;
flex: 1 1 0;
overflow-y: auto;
}
.mx_LeftPanel .mx_BottomLeftMenu {
@ -53,8 +58,10 @@ limitations under the License.
-webkit-order: 3;
order: 3;
-webkit-flex: 0 0 126px;
flex: 0 0 126px;
-webkit-flex: 0 0 140px;
flex: 0 0 140px;
background-color: rgba(118,207,166,0.19);
}
.mx_LeftPanel .mx_BottomLeftMenu .mx_RoomTile {
@ -62,7 +69,7 @@ limitations under the License.
}
.mx_LeftPanel .mx_BottomLeftMenu .mx_BottomLeftMenu_options {
margin-top: 12px;
margin-top: 17px;
width: 100%;
}

View File

@ -16,13 +16,7 @@ limitations under the License.
.mx_RoomList {
padding-top: 24px;
}
.mx_RoomList_invites,
.mx_RoomList_recents {
display: table;
table-layout: fixed;
width: 100%;
padding-bottom: 12px;
}
.mx_RoomList_expandButton {
@ -31,14 +25,3 @@ limitations under the License.
padding-left: 12px;
padding-right: 12px;
}
.mx_RoomList h2 {
text-transform: uppercase;
color: #3d3b39;
font-weight: 600;
font-size: 14px;
padding-left: 12px;
padding-right: 12px;
margin-top: 8px;
margin-bottom: 4px;
}

View File

@ -0,0 +1,45 @@
/*
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.
*/
.mx_RoomSubList {
display: table;
table-layout: fixed;
width: 100%;
}
.mx_RoomSubList_bottommost {
/* XXX: this should really be 100% of the RoomList height, but can't seem to get at it */
min-height: 400px;
}
.mx_RoomSubList_label {
text-transform: uppercase;
color: #3d3b39;
font-weight: 600;
font-size: 14px;
padding-left: 12px;
padding-right: 12px;
margin-top: 8px;
margin-bottom: 4px;
}
.mx_RoomSubList_chevron {
padding-left: 5px;
}
.collapsed .mx_RoomSubList_chevron {
padding-left: 13px;
}

View File

@ -129,7 +129,7 @@ limitations under the License.
clear: both;
margin-top: 32px;
margin-bottom: 8px;
margin-left: 54px;
margin-left: 63px;
padding-bottom: 6px;
border-bottom: 1px solid #eee;
}
@ -158,18 +158,19 @@ limitations under the License.
order: 4;
width: 100%;
-webkit-flex: 0 0 36px;
flex: 0 0 36px;
-webkit-flex: 0 0 auto;
flex: 0 0 auto;
}
.mx_RoomView_statusAreaBox {
max-width: 960px;
margin: auto;
min-height: 36px;
}
.mx_RoomView_statusAreaBox_line {
border-top: 1px solid #eee;
margin-left: 54px;
margin-left: 63px;
height: 1px;
}
@ -185,16 +186,44 @@ limitations under the License.
vertical-align: middle;
}
.mx_RoomView_connectionLostBar {
margin-top: 19px;
height: 58px;
}
.mx_RoomView_connectionLostBar img {
padding-left: 10px;
padding-right: 22px;
vertical-align: middle;
float: left;
}
.mx_RoomView_connectionLostBar_title {
color: #ff0064;
}
.mx_RoomView_connectionLostBar_desc {
color: #454545;
font-size: 14px;
opacity: 0.5;
}
.mx_RoomView_resend_link {
color: #454545 ! important;
text-decoration: underline ! important;
cursor: pointer;
}
.mx_RoomView_typingBar {
margin-top: 10px;
margin-left: 54px;
margin-left: 63px;
color: #4a4a4a;
opacity: 0.5;
}
.mx_RoomView_typingImage {
display: inline;
margin-left: -38px;
margin-left: -47px;
margin-top: -4px;
float: left;
}
@ -214,7 +243,7 @@ limitations under the License.
.mx_RoomView_uploadProgressOuter {
height: 4px;
margin-left: 54px;
margin-left: 63px;
margin-top: -1px;
}
@ -225,7 +254,7 @@ limitations under the License.
.mx_RoomView_uploadFilename {
margin-top: 5px;
margin-left: 56px;
margin-left: 65px;
opacity: 0.5;
color: #4a4a4a;
}

View File

@ -35,7 +35,7 @@ limitations under the License.
-webkit-order: 1;
order: 1;
height: 21px;
height: 40px;
}
.mx_MatrixChat_toolbarShowing {
@ -71,8 +71,8 @@ limitations under the License.
background-color: #eaf5f0;
-webkit-flex: 0 0 230px;
flex: 0 0 230px;
-webkit-flex: 0 0 210px;
flex: 0 0 210px;
}
.mx_MatrixChat .mx_LeftPanel.collapsed {
@ -87,8 +87,8 @@ limitations under the License.
-webkit-order: 2;
order: 2;
padding-left: 12px;
padding-right: 12px;
padding-left: 25px;
padding-right: 22px;
background-color: #fff;
-webkit-flex: 1;
@ -116,8 +116,8 @@ limitations under the License.
-webkit-order: 3;
order: 3;
-webkit-flex: 0 0 230px;
flex: 0 0 230px;
-webkit-flex: 0 0 235px;
flex: 0 0 235px;
}
.mx_MatrixChat .mx_RightPanel.collapsed {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -81,6 +81,7 @@ skin['organisms.QuestionDialog'] = require('./views/organisms/QuestionDialog');
skin['organisms.RightPanel'] = require('./views/organisms/RightPanel');
skin['organisms.RoomDirectory'] = require('./views/organisms/RoomDirectory');
skin['organisms.RoomList'] = require('./views/organisms/RoomList');
skin['organisms.RoomSubList'] = require('./views/organisms/RoomSubList');
skin['organisms.RoomView'] = require('./views/organisms/RoomView');
skin['organisms.UserSettings'] = require('./views/organisms/UserSettings');
skin['organisms.ViewSource'] = require('./views/organisms/ViewSource');

View File

@ -40,6 +40,25 @@ module.exports = React.createClass({
},
render: function() {
// XXX: recalculates default avatar url constantly
if (this.state.imageUrl === this.defaultAvatarUrl(this.props.member)) {
var initial;
if (this.props.member.name[0])
initial = this.props.member.name[0].toUpperCase();
if (initial === '@' && this.props.member.name[1])
initial = this.props.member.name[1].toUpperCase();
return (
<span className="mx_MemberAvatar_wrapper">
<span className="mx_MemberAvatar_initial"
style={{ fontSize: (this.props.width * 0.75) + "px",
width: this.props.width + "px",
lineHeight: this.props.height*1.2 + "px" }}>{ initial }</span>
<img className="mx_MemberAvatar" src={this.state.imageUrl}
onError={this.onError} width={this.props.width} height={this.props.height} />
</span>
);
}
return (
<img className="mx_MemberAvatar" src={this.state.imageUrl}
onError={this.onError}

View File

@ -43,13 +43,33 @@ module.exports = React.createClass({
render: function() {
var style = {
maxWidth: this.props.width,
maxHeight: this.props.height,
width: this.props.width,
height: this.props.height,
};
// XXX: recalculates fallback avatar constantly
if (this.state.imageUrl === this.getFallbackAvatar()) {
var initial;
if (this.props.room.name[0])
initial = this.props.room.name[0].toUpperCase();
if ((initial === '@' || initial === '#') && this.props.room.name[1])
initial = this.props.room.name[1].toUpperCase();
return (
<img className="mx_RoomAvatar" src={this.state.imageUrl} onError={this.onError}
style={style}
/>
<span>
<span className="mx_RoomAvatar_initial"
style={{ fontSize: (this.props.width * 0.75) + "px",
width: this.props.width + "px",
lineHeight: this.props.height*1.2 + "px" }}>{ initial }</span>
<img className="mx_RoomAvatar" src={this.state.imageUrl}
onError={this.onError} style={style} />
</span>
);
}
else {
return <img className="mx_RoomAvatar" src={this.state.imageUrl}
onError={this.onError} style={style} />
}
}
});

View File

@ -26,7 +26,7 @@ module.exports = React.createClass({
var h = this.props.h || 32;
var imgClass = this.props.imgClassName || "";
return (
<div>
<div className="mx_Spinner">
<img src="img/spinner.gif" width={w} height={h} className={imgClass}/>
</div>
);

View File

@ -27,9 +27,9 @@ module.exports = React.createClass({
mixins: [ChangePasswordController],
onClickChange: function() {
var old_password = this.refs.old_input.getDOMNode().value;
var new_password = this.refs.new_input.getDOMNode().value;
var confirm_password = this.refs.confirm_input.getDOMNode().value;
var old_password = this.refs.old_input.value;
var new_password = this.refs.new_input.value;
var confirm_password = this.refs.confirm_input.value;
if (new_password != confirm_password) {
this.setState({
state: this.Phases.Error,

View File

@ -28,12 +28,20 @@ module.exports = React.createClass({
Notifier.setToolbarHidden(true);
},
onClick: function() {
var Notifier = sdk.getComponent('organisms.Notifier');
Notifier.setEnabled(true);
},
render: function() {
var EnableNotificationsButton = sdk.getComponent("atoms.EnableNotificationsButton");
return (
<div className="mx_MatrixToolbar">
You are not receiving desktop notifications. <EnableNotificationsButton />
<div className="mx_MatrixToolbar_close"><img src="img/close-white.png" width="16" height="16" onClick={ this.hideToolbar } /></div>
<img className="mx_MatrixToolbar_warning" src="img/warning.png" width="28" height="28" alt="/!\"/>
<div>
You are not receiving desktop notifications. <a className="mx_MatrixToolbar_link" onClick={ this.onClick }>Enable them now</a>
</div>
<div className="mx_MatrixToolbar_close"><img src="img/cancel-black2.png" width="23" height="23" onClick={ this.hideToolbar } /></div>
</div>
);
}

View File

@ -29,7 +29,7 @@ module.exports = React.createClass({
mixins: [MessageComposerController],
onUploadClick: function(ev) {
this.refs.uploadInput.getDOMNode().click();
this.refs.uploadInput.click();
},
onUploadFileSelected: function(ev) {
@ -38,7 +38,7 @@ module.exports = React.createClass({
if (files && files.length > 0) {
this.props.uploadFile(files[0]);
}
this.refs.uploadInput.getDOMNode().value = null;
this.refs.uploadInput.value = null;
},
onCallClick: function(ev) {

View File

@ -22,25 +22,13 @@ var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var dis = require('matrix-react-sdk/lib/dispatcher');
var sdk = require('matrix-react-sdk')
var Modal = require('matrix-react-sdk/lib/Modal');
var Resend = require("../../../../Resend");
module.exports = React.createClass({
displayName: 'MessageContextMenu',
onResendClick: function() {
MatrixClientPeg.get().resendEvent(
this.props.mxEvent, MatrixClientPeg.get().getRoom(
this.props.mxEvent.getRoomId()
)
).done(function() {
dis.dispatch({
action: 'message_sent'
});
}, function() {
dis.dispatch({
action: 'message_send_failed'
});
});
dis.dispatch({action: 'message_resend_started'});
Resend.resend(this.props.mxEvent);
if (this.props.onFinished) this.props.onFinished();
},

View File

@ -18,16 +18,25 @@ limitations under the License.
var React = require('react');
//var RoomDropTargetController = require('matrix-react-sdk/lib/controllers/molecules/RoomDropTargetController')
module.exports = React.createClass({
displayName: 'RoomDropTarget',
// mixins: [RoomDropTargetController],
render: function() {
if (this.props.placeholder) {
return (
<div className="mx_RoomDropTarget">
{this.props.text}
<div className="mx_RoomDropTarget mx_RoomDropTarget_placeholder">
</div>
);
}
else {
return (
<div className="mx_RoomDropTarget">
<div className="mx_RoomDropTarget_avatar"></div>
<div className="mx_RoomDropTarget_label">
{ this.props.label }
</div>
</div>
);
}
}
});

View File

@ -35,7 +35,7 @@ module.exports = React.createClass({
},
getRoomName: function() {
return this.refs.name_edit.getDOMNode().value;
return this.refs.name_edit.value;
},
onFullscreenClick: function() {

View File

@ -27,15 +27,15 @@ module.exports = React.createClass({
mixins: [RoomSettingsController],
getTopic: function() {
return this.refs.topic.getDOMNode().value;
return this.refs.topic.value;
},
getJoinRules: function() {
return this.refs.is_private.getDOMNode().checked ? "invite" : "public";
return this.refs.is_private.checked ? "invite" : "public";
},
getHistoryVisibility: function() {
return this.refs.share_history.getDOMNode().checked ? "shared" : "invited";
return this.refs.share_history.checked ? "shared" : "invited";
},
getPowerLevels: function() {
@ -45,13 +45,13 @@ module.exports = React.createClass({
power_levels = power_levels.getContent();
var new_power_levels = {
ban: parseInt(this.refs.ban.getDOMNode().value),
kick: parseInt(this.refs.kick.getDOMNode().value),
redact: parseInt(this.refs.redact.getDOMNode().value),
invite: parseInt(this.refs.invite.getDOMNode().value),
events_default: parseInt(this.refs.events_default.getDOMNode().value),
state_default: parseInt(this.refs.state_default.getDOMNode().value),
users_default: parseInt(this.refs.users_default.getDOMNode().value),
ban: parseInt(this.refs.ban.value),
kick: parseInt(this.refs.kick.value),
redact: parseInt(this.refs.redact.value),
invite: parseInt(this.refs.invite.value),
events_default: parseInt(this.refs.events_default.value),
state_default: parseInt(this.refs.state_default.value),
users_default: parseInt(this.refs.users_default.value),
users: power_levels.users,
events: power_levels.events,
};

View File

@ -17,6 +17,8 @@ limitations under the License.
'use strict';
var React = require('react');
var DragSource = require('react-dnd').DragSource;
var DropTarget = require('react-dnd').DropTarget;
var classNames = require('classnames');
var RoomTileController = require('matrix-react-sdk/lib/controllers/molecules/RoomTile')
@ -25,10 +27,178 @@ var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var sdk = require('matrix-react-sdk')
module.exports = React.createClass({
/**
* Specifies the drag source contract.
* Only `beginDrag` function is required.
*/
var roomTileSource = {
canDrag: function(props, monitor) {
return props.roomSubList.props.editable;
},
beginDrag: function (props) {
// Return the data describing the dragged item
var item = {
room: props.room,
originalList: props.roomSubList,
originalIndex: props.roomSubList.findRoomTile(props.room).index,
targetList: props.roomSubList, // at first target is same as original
lastTargetRoom: null,
lastYOffset: null,
lastYDelta: null,
};
if (props.roomSubList.debug) console.log("roomTile beginDrag for " + item.room.roomId);
// doing this 'correctly' with state causes react-dnd to break seemingly due to the state transitions
props.room._dragging = true;
return item;
},
endDrag: function (props, monitor, component) {
var item = monitor.getItem();
if (props.roomSubList.debug) console.log("roomTile endDrag for " + item.room.roomId + " with didDrop=" + monitor.didDrop());
props.room._dragging = false;
if (monitor.didDrop()) {
if (props.roomSubList.debug) console.log("force updating component " + item.targetList.props.label);
item.targetList.forceUpdate(); // as we're not using state
}
if (monitor.didDrop() && item.targetList.props.editable) {
// if we moved lists, remove the old tag
if (item.targetList !== item.originalList) {
// commented out attempts to set a spinner on our target component as component is actually
// the original source component being dragged, not our target. To fix we just need to
// move all of this to endDrop in the target instead. FIXME later.
//component.state.set({ spinner: component.state.spinner ? component.state.spinner++ : 1 });
MatrixClientPeg.get().deleteRoomTag(item.room.roomId, item.originalList.props.tagName).finally(function() {
//component.state.set({ spinner: component.state.spinner-- });
}).fail(function(err) {
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to remove tag " + item.originalList.props.tagName + " from room",
description: err.toString()
});
});
}
var newOrder= {};
if (item.targetList.props.order === 'manual') {
newOrder['order'] = item.targetList.calcManualOrderTagData(item.room);
}
// if we moved lists or the ordering changed, add the new tag
if (item.targetList.props.tagName && (item.targetList !== item.originalList || newOrder)) {
//component.state.set({ spinner: component.state.spinner ? component.state.spinner++ : 1 });
MatrixClientPeg.get().setRoomTag(item.room.roomId, item.targetList.props.tagName, newOrder).finally(function() {
//component.state.set({ spinner: component.state.spinner-- });
}).fail(function(err) {
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to add tag " + item.targetList.props.tagName + " to room",
description: err.toString()
});
});
}
}
else {
// cancel the drop and reset our original position
if (props.roomSubList.debug) console.log("cancelling drop & drag");
props.roomSubList.moveRoomTile(item.room, item.originalIndex);
if (item.targetList && item.targetList !== item.originalList) {
item.targetList.removeRoomTile(item.room);
}
}
}
};
var roomTileTarget = {
canDrop: function() {
return false;
},
hover: function(props, monitor) {
var item = monitor.getItem();
var off = monitor.getClientOffset();
// console.log("hovering on room " + props.room.roomId + ", isOver=" + monitor.isOver());
//console.log("item.targetList=" + item.targetList + ", roomSubList=" + props.roomSubList);
var switchedTarget = false;
if (item.targetList !== props.roomSubList) {
// we've switched target, so remove the tile from the previous target.
// n.b. the previous target might actually be the source list.
if (props.roomSubList.debug) console.log("switched target sublist");
switchedTarget = true;
item.targetList.removeRoomTile(item.room);
item.targetList = props.roomSubList;
}
if (!item.targetList.props.editable) return;
if (item.targetList.props.order === 'manual') {
if (item.room.roomId !== props.room.roomId && props.room !== item.lastTargetRoom) {
// find the offset of the target tile in the list.
var roomTile = props.roomSubList.findRoomTile(props.room);
// shuffle the list to add our tile to that position.
props.roomSubList.moveRoomTile(item.room, roomTile.index);
}
// stop us from flickering between our droptarget and the previous room.
// whenever the cursor changes direction we have to reset the flicker-damping.
var yDelta = off.y - item.lastYOffset;
if ((yDelta > 0 && item.lastYDelta < 0) ||
(yDelta < 0 && item.lastYDelta > 0))
{
// the cursor changed direction - forget our previous room
item.lastTargetRoom = null;
}
else {
// track the last room we were hovering over so we can stop
// bouncing back and forth if the droptarget is narrower than
// the other list items. The other way to do this would be
// to reduce the size of the hittarget on the list items, but
// can't see an easy way to do that.
item.lastTargetRoom = props.room;
}
if (yDelta) item.lastYDelta = yDelta;
item.lastYOffset = off.y;
}
else if (switchedTarget) {
if (!props.roomSubList.findRoomTile(item.room).room) {
// add to the list in the right place
props.roomSubList.moveRoomTile(item.room, 0);
}
// we have to sort the list whatever to recalculate it
props.roomSubList.sortList();
}
},
};
var RoomTile = React.createClass({
displayName: 'RoomTile',
mixins: [RoomTileController],
propTypes: {
connectDragSource: React.PropTypes.func.isRequired,
connectDropTarget: React.PropTypes.func.isRequired,
isDragging: React.PropTypes.bool.isRequired,
room: React.PropTypes.object.isRequired,
collapsed: React.PropTypes.bool.isRequired,
selected: React.PropTypes.bool.isRequired,
unread: React.PropTypes.bool.isRequired,
highlight: React.PropTypes.bool.isRequired,
isInvite: React.PropTypes.bool.isRequired,
roomSubList: React.PropTypes.object.isRequired,
},
getInitialState: function() {
return( { hover : false });
},
@ -42,21 +212,32 @@ module.exports = React.createClass({
},
render: function() {
// if (this.props.clientOffset) {
// //console.log("room " + this.props.room.roomId + " has dropTarget clientOffset " + this.props.clientOffset.x + "," + this.props.clientOffset.y);
// }
if (this.props.room._dragging) {
var RoomDropTarget = sdk.getComponent("molecules.RoomDropTarget");
return <RoomDropTarget placeholder={true}/>;
}
var myUserId = MatrixClientPeg.get().credentials.userId;
var me = this.props.room.currentState.members[myUserId];
var classes = classNames({
'mx_RoomTile': true,
'mx_RoomTile_selected': this.props.selected,
'mx_RoomTile_unread': this.props.unread,
'mx_RoomTile_highlight': this.props.highlight,
'mx_RoomTile_invited': this.props.room.currentState.members[myUserId].membership == 'invite'
'mx_RoomTile_invited': (me && me.membership == 'invite'),
});
var name;
if (this.props.isInvite) {
name = this.props.room.getMember(MatrixClientPeg.get().credentials.userId).events.member.getSender();
name = this.props.room.getMember(myUserId).events.member.getSender();
}
else {
name = this.props.room.name;
// XXX: We should never display raw room IDs, but sometimes the room name js sdk gives is undefined
name = this.props.room.name || this.props.room.roomId;
}
name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon
@ -91,7 +272,14 @@ module.exports = React.createClass({
}
var RoomAvatar = sdk.getComponent('atoms.RoomAvatar');
return (
// These props are injected by React DnD,
// as defined by your `collect` function above:
var isDragging = this.props.isDragging;
var connectDragSource = this.props.connectDragSource;
var connectDropTarget = this.props.connectDropTarget;
return connectDragSource(connectDropTarget(
<div className={classes} onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<div className="mx_RoomTile_avatar">
<RoomAvatar room={this.props.room} width="24" height="24" />
@ -99,6 +287,27 @@ module.exports = React.createClass({
</div>
{ label }
</div>
);
));
}
});
// Export the wrapped version, inlining the 'collect' functions
// to more closely resemble the ES7
module.exports =
DropTarget('RoomTile', roomTileTarget, function(connect, monitor) {
return {
// Call this function inside render()
// to let React DnD handle the drag events:
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
}
})(
DragSource('RoomTile', roomTileSource, function(connect, monitor) {
return {
// Call this function inside render()
// to let React DnD handle the drag events:
connectDragSource: connect.dragSource(),
// You can ask the monitor about the current drag state:
isDragging: monitor.isDragging()
};
})(RoomTile));

View File

@ -17,6 +17,7 @@ limitations under the License.
'use strict';
var React = require('react');
var ReactDOM = require('react-dom');
var dis = require('matrix-react-sdk/lib/dispatcher');
@ -24,21 +25,21 @@ module.exports = React.createClass({
displayName: 'RoomTooltip',
componentDidMount: function() {
var tooltip = ReactDOM.findDOMNode(this);
if (!this.props.bottom) {
// tell the roomlist about us so it can position us
dis.dispatch({
action: 'view_tooltip',
tooltip: this.getDOMNode(),
tooltip: tooltip,
});
}
else {
var tooltip = this.getDOMNode();
tooltip.style.top = tooltip.parentElement.getBoundingClientRect().top + "px";
tooltip.style.display = "block";
}
},
componentDidUnmount: function() {
componentWillUnmount: function() {
if (!this.props.bottom) {
dis.dispatch({
action: 'view_tooltip',

View File

@ -39,7 +39,7 @@ module.exports = React.createClass({
onSearchChange: function(e) {
if (e.keyCode === 13) { // on enter...
this.props.onSearch(this.refs.search_term.getDOMNode().value, this.state.scope);
this.props.onSearch(this.refs.search_term.value, this.state.scope);
}
},

View File

@ -25,8 +25,8 @@ module.exports = React.createClass({
mixins: [UserSelectorController],
onAddUserId: function() {
this.addUser(this.refs.user_id_input.getDOMNode().value);
this.refs.user_id_input.getDOMNode().value = "";
this.addUser(this.refs.user_id_input.value);
this.refs.user_id_input.value = "";
},
render: function() {

View File

@ -34,7 +34,7 @@ module.exports = React.createClass({
render: function(){
var VideoView = sdk.getComponent('molecules.voip.VideoView');
return (
<VideoView ref="video"/>
<VideoView ref="video" onClick={ this.props.onClick }/>
);
}
});

View File

@ -27,7 +27,7 @@ module.exports = React.createClass({
mixins: [IncomingCallBoxController],
getRingAudio: function() {
return this.refs.ringAudio.getDOMNode();
return this.refs.ringAudio;
},
render: function() {

View File

@ -17,6 +17,7 @@ limitations under the License.
'use strict';
var React = require('react');
var ReactDOM = require('react-dom');
var sdk = require('matrix-react-sdk')
var dis = require('matrix-react-sdk/lib/dispatcher')
@ -29,15 +30,15 @@ module.exports = React.createClass({
},
getRemoteVideoElement: function() {
return this.refs.remote.getDOMNode();
return ReactDOM.findDOMNode(this.refs.remote);
},
getRemoteAudioElement: function() {
return this.refs.remoteAudio.getDOMNode();
return this.refs.remoteAudio;
},
getLocalVideoElement: function() {
return this.refs.local.getDOMNode();
return ReactDOM.findDOMNode(this.refs.local);
},
setContainer: function(c) {
@ -50,7 +51,7 @@ module.exports = React.createClass({
if (!this.container) {
return;
}
var element = this.container.getDOMNode();
var element = this.container;
if (payload.fullscreen) {
var requestMethod = (
element.requestFullScreen ||
@ -78,7 +79,7 @@ module.exports = React.createClass({
render: function() {
var VideoFeed = sdk.getComponent('atoms.voip.VideoFeed');
return (
<div className="mx_VideoView" ref={this.setContainer}>
<div className="mx_VideoView" ref={this.setContainer} onClick={ this.props.onClick }>
<div className="mx_VideoView_remoteVideoFeed">
<VideoFeed ref="remote"/>
<audio ref="remoteAudio"/>

View File

@ -17,18 +17,72 @@ limitations under the License.
'use strict';
var React = require('react');
var DragDropContext = require('react-dnd').DragDropContext;
var HTML5Backend = require('react-dnd-html5-backend');
var sdk = require('matrix-react-sdk')
var dis = require('matrix-react-sdk/lib/dispatcher');
module.exports = React.createClass({
var CallHandler = require("matrix-react-sdk/lib/CallHandler");
var LeftPanel = React.createClass({
displayName: 'LeftPanel',
getInitialState: function() {
return {
showCallElement: null,
};
},
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
},
componentWillReceiveProps: function(newProps) {
this._recheckCallElement(newProps.selectedRoom);
},
componentWillUnmount: function() {
dis.unregister(this.dispatcherRef);
},
onAction: function(payload) {
switch (payload.action) {
// listen for call state changes to prod the render method, which
// may hide the global CallView if the call it is tracking is dead
case 'call_state':
this._recheckCallElement(this.props.selectedRoom);
break;
}
},
_recheckCallElement: function(selectedRoomId) {
// if we aren't viewing a room with an ongoing call, but there is an
// active call, show the call element - we need to do this to make
// audio/video not crap out
var activeCall = CallHandler.getAnyActiveCall();
var callForRoom = CallHandler.getCallForRoom(selectedRoomId);
var showCall = (activeCall && !callForRoom);
this.setState({
showCallElement: showCall
});
},
onHideClick: function() {
dis.dispatch({
action: 'hide_left_panel',
});
},
onCallViewClick: function() {
var call = CallHandler.getAnyActiveCall();
if (call) {
dis.dispatch({
action: 'view_room',
room_id: call.roomId,
});
}
},
render: function() {
var RoomList = sdk.getComponent('organisms.RoomList');
var BottomLeftMenu = sdk.getComponent('molecules.BottomLeftMenu');
@ -44,10 +98,17 @@ module.exports = React.createClass({
// collapseButton = <img className="mx_LeftPanel_hideButton" onClick={ this.onHideClick } src="img/hide.png" width="12" height="20" alt="<"/>
}
var callPreview;
if (this.state.showCallElement) {
var CallView = sdk.getComponent('molecules.voip.CallView');
callPreview = <CallView className="mx_LeftPanel_callView" onClick={this.onCallViewClick} />
}
return (
<aside className={classes}>
{ collapseButton }
<IncomingCallBox />
{ callPreview }
<RoomList selectedRoom={this.props.selectedRoom} collapsed={this.props.collapsed}/>
<BottomLeftMenu collapsed={this.props.collapsed}/>
</aside>
@ -55,3 +116,4 @@ module.exports = React.createClass({
}
});
module.exports = DragDropContext(HTML5Backend)(LeftPanel);

View File

@ -21,6 +21,7 @@ var classNames = require('classnames');
var Loader = require('react-loader');
var MemberListController = require('matrix-react-sdk/lib/controllers/organisms/MemberList')
var GeminiScrollbar = require('react-gemini-scrollbar');
var sdk = require('matrix-react-sdk')
@ -71,7 +72,7 @@ module.exports = React.createClass({
},
onPopulateInvite: function(e) {
this.onInvite(this.refs.invite.getDOMNode().value);
this.onInvite(this.refs.invite.value);
e.preventDefault();
},
@ -104,7 +105,7 @@ module.exports = React.createClass({
}
return (
<div className="mx_MemberList">
<div className="mx_MemberList_border">
<GeminiScrollbar autoshow={true} className="mx_MemberList_border">
{this.inviteTile()}
<div>
<div className="mx_MemberList_wrapper">
@ -112,7 +113,7 @@ module.exports = React.createClass({
</div>
</div>
{invitedSection}
</div>
</GeminiScrollbar>
</div>
);
}

View File

@ -110,9 +110,9 @@ module.exports = React.createClass({
onKeyUp: function(ev) {
this.forceUpdate();
this.setState({ roomAlias : this.refs.roomAlias.getDOMNode().value })
this.setState({ roomAlias : this.refs.roomAlias.value })
if (ev.key == "Enter") {
this.joinRoom(this.refs.roomAlias.getDOMNode().value);
this.joinRoom(this.refs.roomAlias.value);
}
if (ev.key == "Down") {

View File

@ -20,6 +20,7 @@ var React = require('react');
var sdk = require('matrix-react-sdk')
var dis = require('matrix-react-sdk/lib/dispatcher');
var GeminiScrollbar = require('react-gemini-scrollbar');
var RoomListController = require('../../../../controllers/organisms/RoomList')
module.exports = React.createClass({
@ -33,48 +34,82 @@ module.exports = React.createClass({
},
render: function() {
var CallView = sdk.getComponent('molecules.voip.CallView');
var RoomDropTarget = sdk.getComponent('molecules.RoomDropTarget');
var callElement;
if (this.state.show_call_element) {
callElement = <CallView className="mx_MatrixChat_callView"/>
}
var expandButton = this.props.collapsed ?
<img className="mx_RoomList_expandButton" onClick={ this.onShowClick } src="img/menu.png" width="20" alt=">"/> :
null;
var invitesLabel = this.props.collapsed ? null : "Invites";
var recentsLabel = this.props.collapsed ? null : "Recent";
var invites;
if (this.state.inviteList.length) {
invites = <div>
<h2 className="mx_RoomList_invitesLabel">{ invitesLabel }</h2>
<div className="mx_RoomList_invites">
{this.makeRoomTiles(this.state.inviteList, true)}
</div>
</div>
}
var RoomSubList = sdk.getComponent('organisms.RoomSubList');
var self = this;
return (
<div className="mx_RoomList" onScroll={this._repositionTooltip}>
<GeminiScrollbar className="mx_RoomList_scrollbar" autoshow={true} onScroll={self._repositionTooltip}>
<div className="mx_RoomList">
{ expandButton }
{ callElement }
<h2 className="mx_RoomList_favouritesLabel">Favourites</h2>
<RoomDropTarget text="Drop here to favourite"/>
{ invites }
<RoomSubList list={ self.state.lists['m.invite'] }
label="Invites"
editable={ false }
order="recent"
activityMap={ self.state.activityMap }
selectedRoom={ self.props.selectedRoom }
collapsed={ self.props.collapsed } />
<h2 className="mx_RoomList_recentsLabel">{ recentsLabel }</h2>
<div className="mx_RoomList_recents">
{this.makeRoomTiles(this.state.roomList, false)}
</div>
<h2 className="mx_RoomList_archiveLabel">Archive</h2>
<RoomDropTarget text="Drop here to archive"/>
<RoomSubList list={ self.state.lists['m.favourite'] }
label="Favourites"
tagName="m.favourite"
verb="favourite"
editable={ true }
order="manual"
activityMap={ self.state.activityMap }
selectedRoom={ self.props.selectedRoom }
collapsed={ self.props.collapsed } />
<RoomSubList list={ self.state.lists['m.recent'] }
label="Conversations"
editable={ true }
verb="restore"
order="recent"
activityMap={ self.state.activityMap }
selectedRoom={ self.props.selectedRoom }
collapsed={ self.props.collapsed } />
{ Object.keys(self.state.lists).map(function(tagName) {
if (!tagName.match(/^m\.(invite|favourite|recent|lowpriority|archived)$/)) {
return <RoomSubList list={ self.state.lists[tagName] }
key={ tagName }
label={ tagName }
tagName={ tagName }
verb={ "tag as " + tagName }
editable={ true }
order="manual"
activityMap={ self.state.activityMap }
selectedRoom={ self.props.selectedRoom }
collapsed={ self.props.collapsed } />
}
}) }
<RoomSubList list={ self.state.lists['m.lowpriority'] }
label="Low priority"
tagName="m.lowpriority"
verb="demote"
editable={ true }
order="recent"
bottommost={ self.state.lists['m.archived'].length === 0 }
activityMap={ self.state.activityMap }
selectedRoom={ self.props.selectedRoom }
collapsed={ self.props.collapsed } />
<RoomSubList list={ self.state.lists['m.archived'] }
label="Historical"
editable={ false }
order="recent"
bottommost={ true }
activityMap={ self.state.activityMap }
selectedRoom={ self.props.selectedRoom }
collapsed={ self.props.collapsed } />
</div>
</GeminiScrollbar>
);
}
});

View File

@ -0,0 +1,290 @@
/*
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 DropTarget = require('react-dnd').DropTarget;
var sdk = require('matrix-react-sdk')
var dis = require('matrix-react-sdk/lib/dispatcher');
// turn this on for drop & drag console debugging galore
var debug = false;
var roomListTarget = {
canDrop: function() {
return true;
},
drop: function(props, monitor, component) {
if (debug) console.log("dropped on sublist")
},
hover: function(props, monitor, component) {
var item = monitor.getItem();
if (component.state.sortedList.length == 0 && props.editable) {
if (debug) console.log("hovering on sublist " + props.label + ", isOver=" + monitor.isOver());
if (item.targetList !== component) {
item.targetList.removeRoomTile(item.room);
item.targetList = component;
}
component.moveRoomTile(item.room, 0);
}
},
};
var RoomSubList = React.createClass({
displayName: 'RoomSubList',
debug: debug,
propTypes: {
list: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
label: React.PropTypes.string.isRequired,
tagName: React.PropTypes.string,
editable: React.PropTypes.bool,
order: React.PropTypes.string.isRequired,
bottommost: React.PropTypes.bool,
selectedRoom: React.PropTypes.string.isRequired,
activityMap: React.PropTypes.object.isRequired,
collapsed: React.PropTypes.bool.isRequired
},
getInitialState: function() {
return {
hidden: false,
sortedList: [],
};
},
componentWillMount: function() {
this.sortList(this.props.list, this.props.order);
},
componentWillReceiveProps: function(newProps) {
// order the room list appropriately before we re-render
//if (debug) console.log("received new props, list = " + newProps.list);
this.sortList(newProps.list, newProps.order);
},
onClick: function(ev) {
this.setState({ hidden : !this.state.hidden });
},
tsOfNewestEvent: function(room) {
if (room.timeline.length) {
return room.timeline[room.timeline.length - 1].getTs();
}
else {
return Number.MAX_SAFE_INTEGER;
}
},
// TODO: factor the comparators back out into a generic comparator
// so that view_prev_room and view_next_room can do the right thing
recentsComparator: function(roomA, roomB) {
return this.tsOfNewestEvent(roomB) - this.tsOfNewestEvent(roomA);
},
manualComparator: function(roomA, roomB) {
if (!roomA.tags[this.props.tagName] || !roomB.tags[this.props.tagName]) return 0;
var a = roomA.tags[this.props.tagName].order;
var b = roomB.tags[this.props.tagName].order;
return a == b ? this.recentsComparator(roomA, roomB) : ( a > b ? 1 : -1);
},
sortList: function(list, order) {
if (list === undefined) list = this.state.sortedList;
if (order === undefined) order = this.props.order;
var comparator;
list = list || [];
if (order === "manual") comparator = this.manualComparator;
if (order === "recent") comparator = this.recentsComparator;
//if (debug) console.log("sorting list for sublist " + this.props.label + " with length " + list.length + ", this.props.list = " + this.props.list);
this.setState({ sortedList: list.sort(comparator) });
},
moveRoomTile: function(room, atIndex) {
if (debug) console.log("moveRoomTile: id " + room.roomId + ", atIndex " + atIndex);
//console.log("moveRoomTile before: " + JSON.stringify(this.state.rooms));
var found = this.findRoomTile(room);
var rooms = this.state.sortedList;
if (found.room) {
if (debug) console.log("removing at index " + found.index + " and adding at index " + atIndex);
rooms.splice(found.index, 1);
rooms.splice(atIndex, 0, found.room);
}
else {
if (debug) console.log("Adding at index " + atIndex);
rooms.splice(atIndex, 0, room);
}
this.setState({ sortedList: rooms });
// console.log("moveRoomTile after: " + JSON.stringify(this.state.rooms));
},
// XXX: this isn't invoked via a property method but indirectly via
// the roomList property method. Unsure how evil this is.
removeRoomTile: function(room) {
if (debug) console.log("remove room " + room.roomId);
var found = this.findRoomTile(room);
var rooms = this.state.sortedList;
if (found.room) {
rooms.splice(found.index, 1);
}
else {
console.warn("Can't remove room " + room.roomId + " - can't find it");
}
this.setState({ sortedList: rooms });
},
findRoomTile: function(room) {
var index = this.state.sortedList.indexOf(room);
if (index >= 0) {
// console.log("found: room: " + room.roomId + " with index " + index);
}
else {
if (debug) console.log("didn't find room");
room = null;
}
return ({
room: room,
index: index,
});
},
calcManualOrderTagData: function(room) {
var index = this.state.sortedList.indexOf(room);
// we sort rooms by the lexicographic ordering of the 'order' metadata on their tags.
// for convenience, we calculate this for now a floating point number between 0.0 and 1.0.
var orderA = 0.0; // by default we're next to the beginning of the list
if (index > 0) {
var prevTag = this.state.sortedList[index - 1].tags[this.props.tagName];
if (!prevTag) {
console.error("Previous room in sublist is not tagged to be in this list. This should never happen.")
}
else if (prevTag.order === undefined) {
console.error("Previous room in sublist has no ordering metadata. This should never happen.");
}
else {
orderA = prevTag.order;
}
}
var orderB = 1.0; // by default we're next to the end of the list too
if (index < this.state.sortedList.length - 1) {
var nextTag = this.state.sortedList[index + 1].tags[this.props.tagName];
if (!nextTag) {
console.error("Next room in sublist is not tagged to be in this list. This should never happen.")
}
else if (nextTag.order === undefined) {
console.error("Next room in sublist has no ordering metadata. This should never happen.");
}
else {
orderB = nextTag.order;
}
}
var order = (orderA + orderB) / 2.0;
if (order === orderA || order === orderB) {
console.error("Cannot describe new list position. This should be incredibly unlikely.");
// TODO: renumber the list
}
return order;
},
makeRoomTiles: function() {
var self = this;
var RoomTile = sdk.getComponent("molecules.RoomTile");
return this.state.sortedList.map(function(room) {
var selected = room.roomId == self.props.selectedRoom;
// XXX: is it evil to pass in self as a prop to RoomTile?
return (
<RoomTile
room={ room }
roomSubList={ self }
key={ room.roomId }
collapsed={ self.props.collapsed }
selected={ selected }
unread={ self.props.activityMap[room.roomId] === 1 }
highlight={ self.props.activityMap[room.roomId] === 2 }
isInvite={ self.props.label === 'Invites' } />
);
});
},
render: function() {
var connectDropTarget = this.props.connectDropTarget;
var RoomDropTarget = sdk.getComponent('molecules.RoomDropTarget');
var label = this.props.collapsed ? null : this.props.label;
//console.log("render: " + JSON.stringify(this.state.sortedList));
var target;
if (this.state.sortedList.length == 0 && this.props.editable) {
target = <RoomDropTarget label={ 'Drop here to ' + this.props.verb }/>;
}
if (this.state.sortedList.length > 0 || this.props.editable) {
var subList;
var classes = "mx_RoomSubList" +
(this.props.bottommost ? " mx_RoomSubList_bottommost" : "");
if (!this.state.hidden) {
subList = <div className={ classes }>
{ target }
{ this.makeRoomTiles() }
</div>;
}
else {
subList = <div className={ classes }>
</div>;
}
return connectDropTarget(
<div>
<h2 onClick={ this.onClick } className="mx_RoomSubList_label">{ this.props.collapsed ? '' : this.props.label }
<img className="mx_RoomSubList_chevron" src={ this.state.hidden ? "/img/list-open.png" : "/img/list-close.png" } width="10" height="10"/>
</h2>
{ subList }
</div>
);
}
else {
return (
<div className="mx_RoomSubList">
</div>
);
}
}
});
// Export the wrapped version, inlining the 'collect' functions
// to more closely resemble the ES7
module.exports =
DropTarget('RoomTile', roomListTarget, function(connect) {
return {
connectDropTarget: connect.dropTarget(),
}
})(RoomSubList);

View File

@ -17,6 +17,7 @@ limitations under the License.
'use strict';
var React = require('react');
var ReactDOM = require('react-dom');
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var dis = require('matrix-react-sdk/lib/dispatcher');
@ -25,6 +26,7 @@ var sdk = require('matrix-react-sdk')
var classNames = require("classnames");
var filesize = require('filesize');
var GeminiScrollbar = require('react-gemini-scrollbar');
var RoomViewController = require('../../../../controllers/organisms/RoomView')
var Loader = require("react-loader");
@ -103,7 +105,7 @@ module.exports = React.createClass({
scrollToBottom: function() {
if (!this.refs.messageWrapper) return;
var messageWrapper = this.refs.messageWrapper.getDOMNode();
var messageWrapper = ReactDOM.findDOMNode(this.refs.messageWrapper).children[2];
messageWrapper.scrollTop = messageWrapper.scrollHeight;
},
@ -196,10 +198,48 @@ module.exports = React.createClass({
);
} else {
var typingString = this.getWhoIsTypingString();
//typingString = "Testing typing...";
var unreadMsgs = this.getUnreadMessagesString();
// no conn bar trumps unread count since you can't get unread messages
// without a connection! (technically may already have some but meh)
// It also trumps the "some not sent" msg since you can't resend without
// a connection!
if (this.state.syncState === "ERROR") {
statusBar = (
<div className="mx_RoomView_connectionLostBar">
<img src="img/warning2.png" width="30" height="30" alt="/!\"/>
<div className="mx_RoomView_connectionLostBar_textArea">
<div className="mx_RoomView_connectionLostBar_title">
Connectivity to the server has been lost.
</div>
<div className="mx_RoomView_connectionLostBar_desc">
Sent messages will be stored until your connection has returned.
</div>
</div>
</div>
);
}
else if (this.state.hasUnsentMessages) {
statusBar = (
<div className="mx_RoomView_connectionLostBar">
<img src="img/warning2.png" width="30" height="30" alt="/!\"/>
<div className="mx_RoomView_connectionLostBar_textArea">
<div className="mx_RoomView_connectionLostBar_title">
Some of your messages have not been sent.
</div>
<div className="mx_RoomView_connectionLostBar_desc">
<a className="mx_RoomView_resend_link"
onClick={ this.onResendAllClick }>
Resend all now
</a> or select individual messages to re-send.
</div>
</div>
</div>
);
}
// unread count trumps who is typing since the unread count is only
// set when you've scrolled up
if (unreadMsgs) {
else if (unreadMsgs) {
statusBar = (
<div className="mx_RoomView_unreadMessagesBar" onClick={ this.scrollToBottom }>
<img src="img/newmessages.png" width="24" height="24" alt=""/>
@ -230,9 +270,13 @@ module.exports = React.createClass({
var conferenceCallNotification = null;
if (this.state.displayConfCallNotification) {
var supportedText;
if (!MatrixClientPeg.get().supportsVoip()) {
supportedText = " (unsupported)";
}
conferenceCallNotification = (
<div className="mx_RoomView_ongoingConfCallNotification" onClick={this.onConferenceNotificationClick}>
Ongoing conference call
Ongoing conference call {supportedText}
</div>
);
}
@ -256,7 +300,7 @@ module.exports = React.createClass({
{ conferenceCallNotification }
{ aux }
</div>
<div ref="messageWrapper" className="mx_RoomView_messagePanel" onScroll={ this.onMessageListScroll }>
<GeminiScrollbar autoshow={true} ref="messageWrapper" className="mx_RoomView_messagePanel" onScroll={ this.onMessageListScroll }>
<div className="mx_RoomView_messageListWrapper">
{ fileDropTarget }
<ol className="mx_RoomView_MessageList" aria-live="polite">
@ -265,7 +309,7 @@ module.exports = React.createClass({
{this.getEventTiles()}
</ol>
</div>
</div>
</GeminiScrollbar>
<div className="mx_RoomView_statusArea">
<div className="mx_RoomView_statusAreaBox">
<div className="mx_RoomView_statusAreaBox_line"></div>

View File

@ -21,9 +21,6 @@ var sdk = require('matrix-react-sdk')
var MatrixChatController = require('matrix-react-sdk/lib/controllers/pages/MatrixChat')
// should be atomised
var Loader = require("react-loader");
var dis = require('matrix-react-sdk/lib/dispatcher');
var Matrix = require("matrix-js-sdk");
var ContextualMenu = require("../../../../ContextualMenu");
@ -154,8 +151,9 @@ module.exports = React.createClass({
);
}
} else if (this.state.logged_in) {
var Spinner = sdk.getComponent('atoms.Spinner');
return (
<Loader />
<Spinner />
);
} else if (this.state.screen == 'register') {
return (

View File

@ -70,8 +70,8 @@ module.exports = React.createClass({
*/
getFormVals: function() {
return {
'username': this.refs.user.getDOMNode().value.trim(),
'password': this.refs.pass.getDOMNode().value.trim()
'username': this.refs.user.value.trim(),
'password': this.refs.pass.value.trim()
};
},

View File

@ -44,10 +44,10 @@ module.exports = React.createClass({
getRegFormVals: function() {
return {
email: this.refs.email.getDOMNode().value.trim(),
username: this.refs.username.getDOMNode().value.trim(),
password: this.refs.password.getDOMNode().value.trim(),
confirmPassword: this.refs.confirmPassword.getDOMNode().value.trim()
email: this.refs.email.value.trim(),
username: this.refs.username.value.trim(),
password: this.refs.password.value.trim(),
confirmPassword: this.refs.confirmPassword.value.trim()
};
},

View File

@ -18,6 +18,7 @@ limitations under the License.
var RunModernizrTests = require("./modernizr"); // this side-effects a global
var React = require("react");
var ReactDOM = require("react-dom");
var sdk = require("matrix-react-sdk");
sdk.loadSkin(require('../skins/vector/skindex'));
sdk.loadModule(require('../modules/VectorConferenceHandler'));
@ -65,14 +66,21 @@ function parseQsFromFragment(location) {
return {};
}
function parseQs(location) {
return qs.parse(location.search.substring(1));
}
// Here, we do some crude URL analysis to allow
// deep-linking. We only support registration
// deep-links in this example.
function routeUrl(location) {
if (location.hash.indexOf('#/register') == 0) {
var params = parseQs(location);
var loginToken = params.loginToken;
if (loginToken) {
window.matrixChat.showScreen('token_login', parseQs(location));
}
else if (location.hash.indexOf('#/register') == 0) {
window.matrixChat.showScreen('register', parseQsFromFragment(location));
} else if (location.hash.indexOf('#/login/cas') == 0) {
window.matrixChat.showScreen('cas_login', parseQsFromFragment(location));
} else {
window.matrixChat.showScreen(location.hash.substring(2));
}
@ -129,7 +137,7 @@ window.onload = function() {
function loadApp() {
if (validBrowser) {
var MatrixChat = sdk.getComponent('pages.MatrixChat');
window.matrixChat = React.render(
window.matrixChat = ReactDOM.render(
<MatrixChat onNewScreen={onNewScreen} registrationUrl={makeRegistrationUrl()} />,
document.getElementById('matrixchat')
);
@ -138,7 +146,7 @@ function loadApp() {
console.error("Browser is missing required features.");
// take to a different landing page to AWOOOOOGA at the user
var CompatibilityPage = require("../skins/vector/views/pages/CompatibilityPage");
window.matrixChat = React.render(
window.matrixChat = ReactDOM.render(
<CompatibilityPage onAccept={function() {
validBrowser = true;
console.log("User accepts the compatibility risks.");

View File

@ -4,26 +4,26 @@
<meta charset="utf-8">
<title>Vector</title>
<link href='fonts/MyriadPro.css' rel='stylesheet' type='text/css'>
<link rel="apple-touch-icon" sizes="57x57" href="/icons/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/icons/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/icons/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/icons/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/icons/apple-touch-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/icons/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/icons/apple-touch-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/icons/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/icons/apple-touch-icon-180x180.png">
<link rel="icon" type="image/png" href="/icons/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="/icons/android-chrome-192x192.png" sizes="192x192">
<link rel="icon" type="image/png" href="/icons/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="/icons/favicon-16x16.png" sizes="16x16">
<link rel="manifest" href="/icons/manifest.json">
<link rel="shortcut icon" href="/icons/favicon.ico">
<link rel="apple-touch-icon" sizes="57x57" href="icons/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="icons/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="icons/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="icons/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="icons/apple-touch-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="icons/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="icons/apple-touch-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="icons/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="icons/apple-touch-icon-180x180.png">
<link rel="icon" type="image/png" href="icons/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="icons/android-chrome-192x192.png" sizes="192x192">
<link rel="icon" type="image/png" href="icons/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="icons/favicon-16x16.png" sizes="16x16">
<link rel="manifest" href="icons/manifest.json">
<link rel="shortcut icon" href="icons/favicon.ico">
<meta name="apple-mobile-web-app-title" content="Vector">
<meta name="application-name" content="Vector">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="msapplication-TileImage" content="/icons/mstile-144x144.png">
<meta name="msapplication-config" content="/icons/browserconfig.xml">
<meta name="msapplication-TileImage" content="icons/mstile-144x144.png">
<meta name="msapplication-config" content="icons/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
</head>
<body style="height: 100%;">