fix conflicts

This commit is contained in:
Matthew Hodgson 2015-11-24 16:05:58 +00:00
commit f9040e08ce
82 changed files with 2870 additions and 1037 deletions

View file

@ -15,7 +15,18 @@ limitations under the License.
*/
.mx_MemberAvatar {
z-index: 20;
border-radius: 20px;
/* commenting this out as it breaks on FF seemingly */
/* position: relative; */
}
.mx_MemberAvatar_initial {
position: absolute;
color: #fff;
text-align: center;
speak: none;
pointer-events: none;
}
.mx_MemberAvatar_image {
border-radius: 20px;
}

View file

@ -14,22 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
.mx_RoomAvatar {
}
var React = require('react');
var CasLoginController = require('matrix-react-sdk/lib/controllers/organisms/CasLogin');
module.exports = React.createClass({
displayName: 'CasLogin',
mixins: [CasLoginController],
render: function() {
return (
<div>
<button onClick={this.onCasClicked}>Sign in with CAS</button>
</div>
);
},
});
.mx_RoomAvatar_initial {
position: absolute;
color: #fff;
text-align: center;
font-weight: normal ! important;
speak: none;
pointer-events: none;
}

View file

@ -0,0 +1,32 @@
/*
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;
width: 100%;
height: 100%;
flex: 1;
-webkit-flex: 1;
}
.mx_MatrixChat_middlePanel .mx_Spinner {
height: auto;
}

View file

@ -47,6 +47,14 @@ a:visited {
color: #76cfa6;
}
/* XXX: critical hack to GeminiScrollbar to allow them to work in FF 42 and Chrome 48.
Stop the scrollbar view from pushing out the container's overall sizing, which causes
flexbox to adapt to the new size and cause the view to keep growing.
*/
.gm-scrollbar-container .gm-scroll-view {
position: absolute;
}
.mx_ContextualMenu_background {
position: fixed;
top: 0;
@ -91,19 +99,9 @@ a:visited {
margin: 0 auto;
}
.mx_Dialog_background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #000;
opacity: 0.2;
z-index: 2000;
}
.mx_Dialog_wrapper {
position: fixed;
z-index: 4000;
top: 0;
left: 0;
width: 100%;
@ -124,7 +122,7 @@ a:visited {
background-color: #fff;
color: #747474;
text-align: center;
z-index: 2010;
z-index: 4010;
font-weight: 300;
font-size: 16px;
position: relative;
@ -132,6 +130,16 @@ a:visited {
max-width: 80%;
}
.mx_Dialog_background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #000;
opacity: 0.2;
}
.mx_Dialog_lightbox .mx_Dialog_background {
opacity: 0.85;
}

View file

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

View file

@ -0,0 +1 @@
../../../../node_modules/gfm.css/gfm.css

View file

@ -0,0 +1 @@
../../../../node_modules/highlight.js/styles/github.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;
}
@ -49,7 +49,6 @@ limitations under the License.
.mx_EventTile .mx_MessageTimestamp {
color: #acacac;
font-size: 12px;
float: right;
}
.mx_EventTile_line {
@ -66,6 +65,28 @@ limitations under the License.
margin-right: 100px;
}
/* Various markdown overrides */
.mx_MessageTile_content .markdown-body {
font-family: inherit ! important;
white-space: normal ! important;
line-height: inherit ! important;
}
.mx_MessageTile_content .markdown-body h1,
.mx_MessageTile_content .markdown-body h2,
.mx_MessageTile_content .markdown-body h3,
.mx_MessageTile_content .markdown-body h4,
.mx_MessageTile_content .markdown-body h5,
.mx_MessageTile_content .markdown-body h6
{
font-family: inherit ! important;
}
.mx_MessageTile_content .markdown-body a {
color: #76cfa6;
}
.mx_MessageTile_searchHighlight {
background-color: #76cfa6;
color: #fff;
@ -78,7 +99,7 @@ limitations under the License.
}
.mx_EventTile_notSent {
color: #f11;
color: #ddd;
}
.mx_EventTile_highlight {
@ -91,10 +112,18 @@ limitations under the License.
.mx_EventTile_msgOption {
float: right;
text-align: right;
z-index: 1;
position: relative;
width: 90px;
margin-right: 10px;
margin-top: -6px;
}
.mx_MessageTimestamp {
display: block;
visibility: hidden;
text-align: right;
}
.mx_EventTile_last .mx_MessageTimestamp {
@ -107,9 +136,8 @@ limitations under the License.
.mx_EventTile_editButton {
position: absolute;
right: 1px;
top: 15px;
visibility: hidden;
display: inline-block;
visibility: hidden;
}
.mx_EventTile:hover .mx_EventTile_editButton {
@ -123,3 +151,21 @@ limitations under the License.
.mx_EventTile.menu .mx_MessageTimestamp {
visibility: visible;
}
.mx_EventTile_readAvatars {
position: relative;
display: inline-block;
width: 14px;
height: 14px;
}
.mx_EventTile_readAvatars .mx_MemberAvatar {
position: absolute;
display: inline-block;
}
.mx_EventTile_readAvatarRemainder {
color: #acacac;
font-size: 12px;
position: absolute;
}

View file

@ -15,5 +15,6 @@ limitations under the License.
*/
.mx_MNoticeTile {
white-space: pre-wrap;
opacity: 0.6;
}

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

@ -16,29 +16,25 @@ limitations under the License.
.mx_MessageComposer_wrapper {
max-width: 960px;
height: 70px;
vertical-align: middle;
margin: auto;
background-color: #fff;
border-top: 2px solid #e1dddd;
}
.mx_MessageComposer_row {
display: table-row;
width: 100%;
height: 70px;
}
.mx_MessageComposer .mx_MessageComposer_avatar {
display: table-cell;
padding-left: 10px;
padding-right: 20px;
height: 70px;
padding-right: 28px;
vertical-align: middle;
}
.mx_MessageComposer .mx_MessageComposer_avatar img {
margin-top: 18px;
border-radius: 20px;
.mx_MessageComposer .mx_MessageComposer_avatar .mx_MemberAvatar {
display: block;
}
.mx_MessageComposer_input {
@ -49,17 +45,18 @@ limitations under the License.
}
.mx_MessageComposer_input textarea {
display: block;
font-size: 15px;
width: 100%;
height: 1.2em;
padding-top: 0.7em;
padding-bottom: 0.7em;
padding: 0px;
margin-top: 6px;
margin-bottom: 6px;
border: 0px;
resize: none;
outline: none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
box-shadow: none;
/* needed for FF */
font-family: 'Myriad Pro', Helvetica, Arial, Sans-Serif;
@ -75,7 +72,8 @@ limitations under the License.
}
.mx_MessageComposer_upload,
.mx_MessageComposer_call {
.mx_MessageComposer_voicecall,
.mx_MessageComposer_videocall {
display: table-cell;
vertical-align: middle;
padding-left: 10px;
@ -83,7 +81,12 @@ limitations under the License.
cursor: pointer;
}
.mx_MessageComposer_call {
.mx_MessageComposer_videocall {
padding-right: 10px;
padding-top: 4px;
}
.mx_MessageComposer_voicecall {
padding-right: 10px;
padding-top: 4px;
}

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;
@ -89,9 +90,9 @@ limitations under the License.
.mx_RoomHeader_simpleHeader {
line-height: 83px;
color: #76cfa6;
font-weight: 400;
font-size: 20px;
color: #454545;
font-size: 24px;
font-weight: bold;
overflow: hidden;
text-overflow: ellipsis;
}
@ -101,9 +102,9 @@ limitations under the License.
vertical-align: middle;
height: 28px;
color: #454545;
font-weight: 800;
font-weight: bold;
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 {
@ -105,16 +104,15 @@ limitations under the License.
}
.mx_RoomTile_unread,
.mx_RoomTile_highlight,
.mx_RoomTile_invited
{
.mx_RoomTile_highlight {
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 {
@ -32,13 +26,9 @@ limitations under the License.
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;
/* Evil hacky override until Chrome fixes drop and drag table cells
and we can correctly fix horizontal wrapping in the sidebar again */
.mx_RoomList_scrollbar .gm-scroll-view {
overflow-x: hidden ! important;
overflow-y: scroll ! important;
}

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

@ -125,11 +125,11 @@ limitations under the License.
clear: both;
}
.mx_RoomView_MessageList h2 {
.mx_RoomView_MessageList > h2 {
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;
}
@ -207,14 +236,14 @@ limitations under the License.
order: 5;
width: 100%;
-webkit-flex: 0 0 70px;
flex: 0 0 70px;
-webkit-flex: 0;
flex: 0;
margin-right: 2px;
}
.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

@ -14,6 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_MatrixChat_splash {
position: relative;
height: 100%;
}
.mx_MatrixChat_splashButtons {
text-align: center;
width: 100%;
position: absolute;
bottom: 30px;
}
.mx_MatrixChat_wrapper {
display: -webkit-box;
display: -moz-box;
@ -35,7 +47,7 @@ limitations under the License.
-webkit-order: 1;
order: 1;
height: 21px;
height: 40px;
}
.mx_MatrixChat_toolbarShowing {
@ -71,8 +83,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 +99,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;
@ -97,7 +109,8 @@ limitations under the License.
/* XXX: Hack: apparently if you try to nest a flex-box
* within a non-flex-box within a flex-box, the height
* of the innermost element gets miscalculated if the
* parents are both auto.
* parents are both auto. Height has to be auto here
* for RoomView to correctly fit when the Toolbar is shown.
* Ideally we'd launch straight into the RoomView at this
* point, but instead we fudge it and make the middlePanel
* flex itself.
@ -116,8 +129,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 {

View file

@ -17,6 +17,18 @@ limitations under the License.
.mx_Login {
width: 100%;
height: 100%;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-webkit-align-items: center;
align-items: center;
-webkit-justify-content: center;
justify-content: center;
overflow: auto;
}
.mx_Login h2 {
@ -28,8 +40,10 @@ limitations under the License.
.mx_Login_box {
width: 300px;
min-height: 450px;
padding-top: 50px;
padding-bottom: 50px;
margin: auto;
padding-top: 100px;
}
.mx_Login_logo {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -65,13 +65,11 @@ skin['molecules.RoomTile'] = require('./views/molecules/RoomTile');
skin['molecules.RoomTooltip'] = require('./views/molecules/RoomTooltip');
skin['molecules.SearchBar'] = require('./views/molecules/SearchBar');
skin['molecules.SenderProfile'] = require('./views/molecules/SenderProfile');
skin['molecules.ServerConfig'] = require('./views/molecules/ServerConfig');
skin['molecules.UnknownMessageTile'] = require('./views/molecules/UnknownMessageTile');
skin['molecules.UserSelector'] = require('./views/molecules/UserSelector');
skin['molecules.voip.CallView'] = require('./views/molecules/voip/CallView');
skin['molecules.voip.IncomingCallBox'] = require('./views/molecules/voip/IncomingCallBox');
skin['molecules.voip.VideoView'] = require('./views/molecules/voip/VideoView');
skin['organisms.CasLogin'] = require('./views/organisms/CasLogin');
skin['organisms.CreateRoom'] = require('./views/organisms/CreateRoom');
skin['organisms.ErrorDialog'] = require('./views/organisms/ErrorDialog');
skin['organisms.LeftPanel'] = require('./views/organisms/LeftPanel');
@ -82,12 +80,11 @@ 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');
skin['pages.CompatibilityPage'] = require('./views/pages/CompatibilityPage');
skin['pages.MatrixChat'] = require('./views/pages/MatrixChat');
skin['templates.Login'] = require('./views/templates/Login');
skin['templates.Register'] = require('./views/templates/Register');
module.exports = skin;

View file

@ -40,10 +40,32 @@ 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" {...this.props}>
<span className="mx_MemberAvatar_initial" aria-hidden="true"
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_image" src={this.state.imageUrl} title={this.props.member.name}
onError={this.onError} width={this.props.width} height={this.props.height} />
</span>
);
}
return (
<img className="mx_MemberAvatar" src={this.state.imageUrl}
<img className="mx_MemberAvatar mx_MemberAvatar_image" src={this.state.imageUrl}
onError={this.onError}
width={this.props.width} height={this.props.height} />
width={this.props.width} height={this.props.height}
title={this.props.member.name}
{...this.props}
/>
);
}
});

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,
};
return (
<img className="mx_RoomAvatar" src={this.state.imageUrl} onError={this.onError}
style={style}
/>
);
// 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 (
<span>
<span className="mx_RoomAvatar_initial" aria-hidden="true"
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

@ -21,9 +21,6 @@ var React = require('react');
var sdk = require('matrix-react-sdk')
var ChangeAvatarController = require('matrix-react-sdk/lib/controllers/molecules/ChangeAvatar')
var Loader = require("react-loader");
module.exports = React.createClass({
displayName: 'ChangeAvatar',
mixins: [ChangeAvatarController],
@ -70,6 +67,7 @@ module.exports = React.createClass({
</div>
);
case this.Phases.Uploading:
var Loader = sdk.getComponent("atoms.Spinner");
return (
<Loader />
);

View file

@ -20,8 +20,6 @@ var React = require('react');
var sdk = require('matrix-react-sdk');
var ChangeDisplayNameController = require("matrix-react-sdk/lib/controllers/molecules/ChangeDisplayName");
var Loader = require("react-loader");
module.exports = React.createClass({
displayName: 'ChangeDisplayName',
@ -39,6 +37,7 @@ module.exports = React.createClass({
render: function() {
if (this.state.busy) {
var Loader = sdk.getComponent("atoms.Spinner");
return (
<Loader />
);

View file

@ -19,17 +19,15 @@ limitations under the License.
var React = require('react');
var ChangePasswordController = require('matrix-react-sdk/lib/controllers/molecules/ChangePassword')
var Loader = require("react-loader");
module.exports = React.createClass({
displayName: 'ChangePassword',
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,
@ -64,6 +62,7 @@ module.exports = React.createClass({
</div>
);
case this.Phases.Uploading:
var Loader = sdk.getComponent("atoms.Spinner");
return (
<div className="mx_Dialog_content">
<Loader />

View file

@ -17,15 +17,28 @@ limitations under the License.
'use strict';
var React = require('react');
var ReactDom = require('react-dom');
var classNames = require("classnames");
var sdk = require('matrix-react-sdk')
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg')
var EventTileController = require('matrix-react-sdk/lib/controllers/molecules/EventTile')
var ContextualMenu = require('../../../../ContextualMenu');
var TextForEvent = require('matrix-react-sdk/lib/TextForEvent');
var Velociraptor = require('../../../../Velociraptor');
require('../../../../VelocityBounce');
var bounce = false;
try {
if (global.localStorage) {
bounce = global.localStorage.getItem('avatar_bounce') == 'true';
}
} catch (e) {
}
var eventTileTypes = {
'm.room.message': 'molecules.MessageTile',
'm.room.member' : 'molecules.EventAsTextTile',
@ -36,6 +49,8 @@ var eventTileTypes = {
'm.room.topic' : 'molecules.EventAsTextTile',
};
var MAX_READ_AVATARS = 5;
module.exports = React.createClass({
displayName: 'EventTile',
mixins: [EventTileController],
@ -52,7 +67,7 @@ module.exports = React.createClass({
},
getInitialState: function() {
return {menu: false};
return {menu: false, allReadAvatars: false};
},
onEditClicked: function(e) {
@ -72,6 +87,127 @@ module.exports = React.createClass({
this.setState({menu: true});
},
toggleAllReadAvatars: function() {
this.setState({
allReadAvatars: !this.state.allReadAvatars
});
},
getReadAvatars: function() {
var avatars = [];
var room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
if (!room) return [];
var myUserId = MatrixClientPeg.get().credentials.userId;
// get list of read receipts, sorted most recent first
var receipts = room.getReceiptsForEvent(this.props.mxEvent).filter(function(r) {
return r.type === "m.read" && r.userId != myUserId;
}).sort(function(r1, r2) {
return r2.data.ts - r1.data.ts;
});
var MemberAvatar = sdk.getComponent('atoms.MemberAvatar');
var left = 0;
var reorderTransitionOpts = {
duration: 100,
easing: 'easeOut'
};
for (var i = 0; i < receipts.length; ++i) {
var member = room.getMember(receipts[i].userId);
// Using react refs here would mean both getting Velociraptor to expose
// them and making them scoped to the whole RoomView. Not impossible, but
// getElementById seems simpler at least for a first cut.
var oldAvatarDomNode = document.getElementById('mx_readAvatar'+member.userId);
var startStyles = [];
var enterTransitionOpts = [];
var oldNodeTop = -15; // For avatars that weren't on screen, act as if they were just off the top
if (oldAvatarDomNode) {
oldNodeTop = oldAvatarDomNode.getBoundingClientRect().top;
}
if (this.readAvatarNode) {
var topOffset = oldNodeTop - this.readAvatarNode.getBoundingClientRect().top;
if (oldAvatarDomNode && oldAvatarDomNode.style.left !== '0px') {
var leftOffset = oldAvatarDomNode.style.left;
// start at the old height and in the old h pos
startStyles.push({ top: topOffset, left: leftOffset });
enterTransitionOpts.push(reorderTransitionOpts);
}
// then shift to the rightmost column,
// and then it will drop down to its resting position
startStyles.push({ top: topOffset, left: '0px' });
enterTransitionOpts.push({
duration: bounce ? Math.min(Math.log(Math.abs(topOffset)) * 200, 3000) : 300,
easing: bounce ? 'easeOutBounce' : 'easeOutCubic',
});
}
var style = {
left: left+'px',
top: '0px',
visibility: ((i < MAX_READ_AVATARS) || this.state.allReadAvatars) ? 'visible' : 'hidden'
};
//console.log("i = " + i + ", MAX_READ_AVATARS = " + MAX_READ_AVATARS + ", allReadAvatars = " + this.state.allReadAvatars + " visibility = " + style.visibility);
// add to the start so the most recent is on the end (ie. ends up rightmost)
avatars.unshift(
<MemberAvatar key={member.userId} member={member}
width={14} height={14} resizeMethod="crop"
style={style}
startStyle={startStyles}
enterTransitionOpts={enterTransitionOpts}
id={'mx_readAvatar'+member.userId}
onClick={this.toggleAllReadAvatars}
/>
);
// TODO: we keep the extra read avatars in the dom to make animation simpler
// we could optimise this to reduce the dom size.
if (i < MAX_READ_AVATARS - 1 || this.state.allReadAvatars) { // XXX: where does this -1 come from? is it to make the max'th avatar animate properly?
left -= 15;
}
}
var editButton;
if (!this.state.allReadAvatars) {
var remainder = receipts.length - MAX_READ_AVATARS;
var remText;
if (i >= MAX_READ_AVATARS - 1) left -= 15;
if (remainder > 0) {
remText = <span className="mx_EventTile_readAvatarRemainder"
onClick={this.toggleAllReadAvatars}
style={{ left: left }}>{ remainder }+
</span>;
left -= 15;
}
editButton = (
<input style={{ left: left }}
type="image" src="img/edit.png" alt="Options" title="Options" width="14" height="14"
className="mx_EventTile_editButton" onClick={this.onEditClicked} />
);
}
return <span className="mx_EventTile_readAvatars" ref={this.collectReadAvatarNode}>
{ editButton }
{ remText }
<Velociraptor transition={ reorderTransitionOpts }>
{ avatars }
</Velociraptor>
</span>;
},
collectReadAvatarNode: function(node) {
this.readAvatarNode = ReactDom.findDOMNode(node);
},
render: function() {
var MessageTimestamp = sdk.getComponent('atoms.MessageTimestamp');
var SenderProfile = sdk.getComponent('molecules.SenderProfile');
@ -100,18 +236,14 @@ module.exports = React.createClass({
menu: this.state.menu,
});
var timestamp = <MessageTimestamp ts={this.props.mxEvent.getTs()} />
var editButton = (
<input
type="image" src="img/edit.png" alt="Edit" width="14" height="14"
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 readAvatars = this.getReadAvatars();
var avatar, sender;
if (!this.props.continuation) {
if (this.props.mxEvent.sender) {
@ -127,11 +259,13 @@ module.exports = React.createClass({
}
return (
<div className={classes}>
<div className="mx_EventTile_msgOption">
{ timestamp }
{ readAvatars }
</div>
{ avatar }
{ sender }
<div className="mx_EventTile_line">
{ timestamp }
{ editButton }
<EventTileType mxEvent={this.props.mxEvent} searchTerm={this.props.searchTerm} />
</div>
</div>

View file

@ -30,15 +30,25 @@ module.exports = React.createClass({
var content = this.props.mxEvent.getContent();
var cli = MatrixClientPeg.get();
return (
<span className="mx_MFileTile">
<div className="mx_MImageTile_download">
<a href={cli.mxcUrlToHttp(content.url)} target="_blank">
<img src="img/download.png" width="10" height="12"/>
Download {this.presentableTextForFile(content)}
</a>
</div>
var httpUrl = cli.mxcUrlToHttp(content.url);
var text = this.presentableTextForFile(content);
if (httpUrl) {
return (
<span className="mx_MFileTile">
<div className="mx_MImageTile_download">
<a href={cli.mxcUrlToHttp(content.url)} target="_blank">
<img src="img/download.png" width="10" height="12"/>
Download {text}
</a>
</div>
</span>
);
} else {
var extra = text ? ': '+text : '';
return <span className="mx_MFileTile">
Invalid file{extra}
</span>
);
}
},
});

View file

@ -63,6 +63,34 @@ module.exports = React.createClass({
}
},
_isGif: function() {
var content = this.props.mxEvent.getContent();
return (content && content.info && content.info.mimetype === "image/gif");
},
onImageEnter: function(e) {
if (!this._isGif()) {
return;
}
var imgElement = e.target;
imgElement.src = MatrixClientPeg.get().mxcUrlToHttp(
this.props.mxEvent.getContent().url
);
},
onImageLeave: function(e) {
if (!this._isGif()) {
return;
}
var imgElement = e.target;
imgElement.src = this._getThumbUrl();
},
_getThumbUrl: function() {
var content = this.props.mxEvent.getContent();
return MatrixClientPeg.get().mxcUrlToHttp(content.url, 480, 360);
},
render: function() {
var content = this.props.mxEvent.getContent();
var cli = MatrixClientPeg.get();
@ -73,18 +101,36 @@ module.exports = React.createClass({
var imgStyle = {};
if (thumbHeight) imgStyle['height'] = thumbHeight;
return (
<span className="mx_MImageTile">
<a href={cli.mxcUrlToHttp(content.url)} onClick={ this.onClick }>
<img className="mx_MImageTile_thumbnail" src={cli.mxcUrlToHttp(content.url, 480, 360)} alt={content.body} style={imgStyle} />
</a>
<div className="mx_MImageTile_download">
<a href={cli.mxcUrlToHttp(content.url)} target="_blank">
<img src="img/download.png" width="10" height="12"/>
Download {content.body} ({ content.info && content.info.size ? filesize(content.info.size) : "Unknown size" })
var thumbUrl = this._getThumbUrl();
if (thumbUrl) {
return (
<span className="mx_MImageTile">
<a href={cli.mxcUrlToHttp(content.url)} onClick={ this.onClick }>
<img className="mx_MImageTile_thumbnail" src={thumbUrl}
alt={content.body} style={imgStyle}
onMouseEnter={this.onImageEnter}
onMouseLeave={this.onImageLeave} />
</a>
</div>
</span>
);
<div className="mx_MImageTile_download">
<a href={cli.mxcUrlToHttp(content.url)} target="_blank">
<img src="img/download.png" width="10" height="12"/>
Download {content.body} ({ content.info && content.info.size ? filesize(content.info.size) : "Unknown size" })
</a>
</div>
</span>
);
} else if (content.body) {
return (
<span className="mx_MImageTile">
Image '{content.body}' cannot be displayed.
</span>
);
} else {
return (
<span className="mx_MImageTile">
This image cannot be displayed.
</span>
);
}
},
});

View file

@ -17,67 +17,34 @@ limitations under the License.
'use strict';
var React = require('react');
var sanitizeHtml = require('sanitize-html');
var HtmlUtils = require('../../../../HtmlUtils');
var MNoticeTileController = require('matrix-react-sdk/lib/controllers/molecules/MNoticeTile')
var allowedAttributes = sanitizeHtml.defaults.allowedAttributes;
allowedAttributes['font'] = ['color'];
var sanitizeHtmlParams = {
allowedTags: sanitizeHtml.defaults.allowedTags.concat([ 'font' ]),
allowedAttributes: allowedAttributes,
};
module.exports = React.createClass({
displayName: 'MNoticeTile',
mixins: [MNoticeTileController],
// FIXME: this entire class is copy-pasted from MTextTile :(
componentDidMount: function() {
if (this.props.mxEvent.getContent().format === "org.matrix.custom.html")
HtmlUtils.highlightDom(this.getDOMNode());
},
componentDidUpdate: function() {
if (this.props.mxEvent.getContent().format === "org.matrix.custom.html")
HtmlUtils.highlightDom(this.getDOMNode());
},
shouldComponentUpdate: function(nextProps) {
// exploit that events are immutable :)
return (nextProps.mxEvent.getId() !== this.props.mxEvent.getId() ||
nextProps.searchTerm !== this.props.searchTerm);
},
// XXX: fix horrible duplication with MTextTile
render: function() {
var content = this.props.mxEvent.getContent();
var originalBody = content.body;
var body;
if (this.props.searchTerm) {
var lastOffset = 0;
var bodyList = [];
var k = 0;
var offset;
// XXX: rather than searching for the search term in the body,
// we should be looking at the match delimiters returned by the FTS engine
if (content.format === "org.matrix.custom.html") {
var safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams);
var safeSearchTerm = sanitizeHtml(this.props.searchTerm, sanitizeHtmlParams);
while ((offset = safeBody.indexOf(safeSearchTerm, lastOffset)) >= 0) {
// FIXME: we need to apply the search highlighting to only the text elements of HTML, which means
// hooking into the sanitizer parser rather than treating it as a string. Otherwise
// the act of highlighting a <b/> or whatever will break the HTML badly.
bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: safeBody.substring(lastOffset, offset) }} />);
bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: safeSearchTerm }} className="mx_MessageTile_searchHighlight" />);
lastOffset = offset + safeSearchTerm.length;
}
bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: safeBody.substring(lastOffset) }} />);
}
else {
while ((offset = originalBody.indexOf(this.props.searchTerm, lastOffset)) >= 0) {
bodyList.push(<span key={ k++ } >{ originalBody.substring(lastOffset, offset) }</span>);
bodyList.push(<span key={ k++ } className="mx_MessageTile_searchHighlight">{ this.props.searchTerm }</span>);
lastOffset = offset + this.props.searchTerm.length;
}
bodyList.push(<span key={ k++ }>{ originalBody.substring(lastOffset) }</span>);
}
body = bodyList;
}
else {
if (content.format === "org.matrix.custom.html") {
var safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams);
body = <span dangerouslySetInnerHTML={{ __html: safeBody }} />;
}
else {
body = originalBody;
}
}
var body = HtmlUtils.bodyToHtml(content, this.props.searchTerm);
return (
<span ref="content" className="mx_MNoticeTile mx_MessageTile_content">

View file

@ -17,67 +17,33 @@ limitations under the License.
'use strict';
var React = require('react');
var sanitizeHtml = require('sanitize-html');
var HtmlUtils = require('../../../../HtmlUtils');
var MTextTileController = require('matrix-react-sdk/lib/controllers/molecules/MTextTile')
var allowedAttributes = sanitizeHtml.defaults.allowedAttributes;
allowedAttributes['font'] = ['color'];
var sanitizeHtmlParams = {
allowedTags: sanitizeHtml.defaults.allowedTags.concat([ 'font' ]),
allowedAttributes: allowedAttributes,
};
module.exports = React.createClass({
displayName: 'MTextTile',
mixins: [MTextTileController],
// FIXME: this entire class is copy-pasted from MTextTile :(
componentDidMount: function() {
if (this.props.mxEvent.getContent().format === "org.matrix.custom.html")
HtmlUtils.highlightDom(this.getDOMNode());
},
componentDidUpdate: function() {
if (this.props.mxEvent.getContent().format === "org.matrix.custom.html")
HtmlUtils.highlightDom(this.getDOMNode());
},
shouldComponentUpdate: function(nextProps) {
// exploit that events are immutable :)
return (nextProps.mxEvent.getId() !== this.props.mxEvent.getId() ||
nextProps.searchTerm !== this.props.searchTerm);
},
render: function() {
var content = this.props.mxEvent.getContent();
var originalBody = content.body;
var body;
if (this.props.searchTerm) {
var lastOffset = 0;
var bodyList = [];
var k = 0;
var offset;
// XXX: rather than searching for the search term in the body,
// we should be looking at the match delimiters returned by the FTS engine
if (content.format === "org.matrix.custom.html") {
var safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams);
var safeSearchTerm = sanitizeHtml(this.props.searchTerm, sanitizeHtmlParams);
while ((offset = safeBody.indexOf(safeSearchTerm, lastOffset)) >= 0) {
// FIXME: we need to apply the search highlighting to only the text elements of HTML, which means
// hooking into the sanitizer parser rather than treating it as a string. Otherwise
// the act of highlighting a <b/> or whatever will break the HTML badly.
bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: safeBody.substring(lastOffset, offset) }} />);
bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: safeSearchTerm }} className="mx_MessageTile_searchHighlight" />);
lastOffset = offset + safeSearchTerm.length;
}
bodyList.push(<span key={ k++ } dangerouslySetInnerHTML={{ __html: safeBody.substring(lastOffset) }} />);
}
else {
while ((offset = originalBody.indexOf(this.props.searchTerm, lastOffset)) >= 0) {
bodyList.push(<span key={ k++ } >{ originalBody.substring(lastOffset, offset) }</span>);
bodyList.push(<span key={ k++ } className="mx_MessageTile_searchHighlight">{ this.props.searchTerm }</span>);
lastOffset = offset + this.props.searchTerm.length;
}
bodyList.push(<span key={ k++ }>{ originalBody.substring(lastOffset) }</span>);
}
body = bodyList;
}
else {
if (content.format === "org.matrix.custom.html") {
var safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams);
body = <span dangerouslySetInnerHTML={{ __html: safeBody }} />;
}
else {
body = originalBody;
}
}
var body = HtmlUtils.bodyToHtml(content, this.props.searchTerm);
return (
<span ref="content" className="mx_MTextTile mx_MessageTile_content">

View file

@ -28,12 +28,19 @@ 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

@ -17,7 +17,6 @@ limitations under the License.
'use strict';
var React = require('react');
var Loader = require("../atoms/Spinner");
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var sdk = require('matrix-react-sdk')
@ -47,6 +46,7 @@ module.exports = React.createClass({
}
if (this.state.creatingRoom) {
var Loader = sdk.getComponent("atoms.Spinner");
spinner = <Loader imgClassName="mx_ContextualMenu_spinner"/>;
}

View file

@ -28,8 +28,12 @@ module.exports = React.createClass({
displayName: 'MessageComposer',
mixins: [MessageComposerController],
onInputClick: function(ev) {
this.refs.textarea.focus();
},
onUploadClick: function(ev) {
this.refs.uploadInput.getDOMNode().click();
this.refs.uploadInput.click();
},
onUploadFileSelected: function(ev) {
@ -38,7 +42,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) {
@ -49,6 +53,14 @@ module.exports = React.createClass({
});
},
onVoiceCallClick: function(ev) {
dis.dispatch({
action: 'place_call',
type: 'voice',
room_id: this.props.room.roomId
});
},
render: function() {
var me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId);
var uploadInputStyle = {display: 'none'};
@ -60,15 +72,18 @@ module.exports = React.createClass({
<div className="mx_MessageComposer_avatar">
<MemberAvatar member={me} width={24} height={24} />
</div>
<div className="mx_MessageComposer_input">
<textarea ref="textarea" onKeyDown={this.onKeyDown} placeholder="Type a message..." />
<div className="mx_MessageComposer_input" onClick={ this.onInputClick }>
<textarea ref="textarea" rows="1" onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} placeholder="Type a message..." />
</div>
<div className="mx_MessageComposer_upload" onClick={this.onUploadClick}>
<img src="img/upload.png" width="17" height="22"/>
<img src="img/upload.png" alt="Upload file" title="Upload file" width="17" height="22"/>
<input type="file" style={uploadInputStyle} ref="uploadInput" onChange={this.onUploadFileSelected} />
</div>
<div className="mx_MessageComposer_call" onClick={this.onCallClick}>
<img src="img/call.png" width="28" height="20"/>
<div className="mx_MessageComposer_voicecall" onClick={this.onVoiceCallClick}>
<img src="img/voice.png" alt="Voice call" title="Voice call" width="16" height="26"/>
</div>
<div className="mx_MessageComposer_videocall" onClick={this.onCallClick}>
<img src="img/call.png" alt="Video call" title="Video call" width="28" height="20"/>
</div>
</div>
</div>

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() {
return (
<div className="mx_RoomDropTarget">
{this.props.text}
</div>
);
if (this.props.placeholder) {
return (
<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,179 @@ 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 +213,34 @@ 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 +275,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 +290,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

@ -1,50 +0,0 @@
/*
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 Modal = require('matrix-react-sdk/lib/Modal');
var sdk = require('matrix-react-sdk')
var ServerConfigController = require('matrix-react-sdk/lib/controllers/molecules/ServerConfig')
module.exports = React.createClass({
displayName: 'ServerConfig',
mixins: [ServerConfigController],
showHelpPopup: function() {
var ErrorDialog = sdk.getComponent('organisms.ErrorDialog');
Modal.createDialog(ErrorDialog, {
title: 'Custom Server Options',
description: "You can use the custom server options to log into other Matrix servers by specifying a different Home server URL. This allows you to use Vector with an existing Matrix account on a different Home server. You can also set a cutom Identity server but this will affect people ability to find you if you use a server in a group other than tha main Matrix.org group.",
button: "Dismiss",
focus: true
});
},
render: function() {
return (
<div className="mx_ServerConfig">
<label className="mx_Login_label mx_ServerConfig_hslabel" htmlFor="hsurl">Home server URL</label>
<input className="mx_Login_field" id="hsurl" type="text" value={this.state.hs_url} onChange={this.hsChanged} />
<label className="mx_Login_label mx_ServerConfig_islabel" htmlFor="isurl">Identity server URL</label>
<input className="mx_Login_field" type="text" value={this.state.is_url} onChange={this.isChanged} />
<a className="mx_ServerConfig_help" href="#" onClick={this.showHelpPopup}>What does this mean?</a>
</div>
);
}
});

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

@ -24,9 +24,6 @@ var sdk = require('matrix-react-sdk')
var PresetValues = require('matrix-react-sdk/lib/controllers/atoms/create_room/Presets').Presets;
var Loader = require("react-loader");
module.exports = React.createClass({
displayName: 'CreateRoom',
mixins: [CreateRoomController],
@ -122,6 +119,7 @@ module.exports = React.createClass({
render: function() {
var curr_phase = this.state.phase;
if (curr_phase == this.phases.CREATING) {
var Loader = sdk.getComponent("atoms.Spinner");
return (
<Loader/>
);

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

@ -18,9 +18,9 @@ limitations under the License.
var React = require('react');
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,12 +71,13 @@ module.exports = React.createClass({
},
onPopulateInvite: function(e) {
this.onInvite(this.refs.invite.getDOMNode().value);
this.onInvite(this.refs.invite.value);
e.preventDefault();
},
inviteTile: function() {
if (this.state.inviting) {
var Loader = sdk.getComponent("atoms.Spinner");
return (
<Loader />
);
@ -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

@ -23,8 +23,6 @@ var Modal = require('matrix-react-sdk/lib/Modal');
var sdk = require('matrix-react-sdk')
var dis = require('matrix-react-sdk/lib/dispatcher');
var Loader = require("react-loader");
module.exports = React.createClass({
displayName: 'RoomDirectory',
@ -110,9 +108,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") {
@ -121,6 +119,7 @@ module.exports = React.createClass({
render: function() {
if (this.state.loading) {
var Loader = sdk.getComponent("atoms.Spinner");
return (
<div className="mx_RoomDirectory">
<Loader />
@ -136,7 +135,9 @@ module.exports = React.createClass({
<input ref="roomAlias" placeholder="Join a room (e.g. #foo:domain.com)" className="mx_RoomDirectory_input" size="64" onKeyUp={ this.onKeyUp }/>
<div className="mx_RoomDirectory_tableWrapper">
<table className="mx_RoomDirectory_table">
<tr><th width="45%">Room</th><th width="45%">Alias</th><th width="10%">Members</th></tr>
<thead>
<tr><th width="45%">Room</th><th width="45%">Alias</th><th width="10%">Members</th></tr>
</thead>
{ this.getRows(this.state.roomAlias) }
</table>
</div>

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>
<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 } />
<h2 className="mx_RoomList_archiveLabel">Archive</h2>
<RoomDropTarget text="Drop here to archive"/>
<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 || false}
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,11 +26,9 @@ 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");
module.exports = React.createClass({
displayName: 'RoomView',
mixins: [RoomViewController],
@ -102,9 +101,9 @@ module.exports = React.createClass({
},
scrollToBottom: function() {
if (!this.refs.messageWrapper) return;
var messageWrapper = this.refs.messageWrapper.getDOMNode();
messageWrapper.scrollTop = messageWrapper.scrollHeight;
var scrollNode = this._getScrollNode();
if (!scrollNode) return;
scrollNode.scrollTop = scrollNode.scrollHeight;
},
render: function() {
@ -131,6 +130,7 @@ module.exports = React.createClass({
var myUserId = MatrixClientPeg.get().credentials.userId;
if (this.state.room.currentState.members[myUserId].membership == 'invite') {
if (this.state.joining || this.state.rejecting) {
var Loader = sdk.getComponent("atoms.Spinner");
return (
<div className="mx_RoomView">
<Loader />
@ -196,10 +196,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=""/>
@ -222,6 +260,7 @@ module.exports = React.createClass({
aux = <RoomSettings ref="room_settings" onSaveClick={this.onSaveClick} room={this.state.room} />;
}
else if (this.state.uploadingRoomSettings) {
var Loader = sdk.getComponent("atoms.Spinner");
aux = <Loader/>;
}
else if (this.state.searching) {
@ -260,7 +299,7 @@ module.exports = React.createClass({
{ conferenceCallNotification }
{ aux }
</div>
<div ref="messageWrapper" className="mx_RoomView_messagePanel" onScroll={ this.onMessageListScroll }>
<GeminiScrollbar autoshow={true} ref="messagePanel" className="mx_RoomView_messagePanel" onScroll={ this.onMessageListScroll }>
<div className="mx_RoomView_messageListWrapper">
{ fileDropTarget }
<ol className="mx_RoomView_MessageList" aria-live="polite">
@ -269,14 +308,14 @@ 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>
{statusBar}
</div>
</div>
<MessageComposer room={this.state.room} uploadFile={this.uploadFile} />
<MessageComposer room={this.state.room} roomView={this} uploadFile={this.uploadFile} />
</div>
);
}

View file

@ -19,8 +19,6 @@ var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var UserSettingsController = require('matrix-react-sdk/lib/controllers/organisms/UserSettings')
var Loader = require("react-loader");
var Modal = require('matrix-react-sdk/lib/Modal');
module.exports = React.createClass({
@ -68,6 +66,7 @@ module.exports = React.createClass({
},
render: function() {
var Loader = sdk.getComponent("atoms.Spinner");
switch (this.state.phase) {
case this.Phases.Loading:
return <Loader />

View file

@ -21,12 +21,14 @@ 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");
var Login = require("../../../../components/login/Login");
var Registration = require("../../../../components/login/Registration");
var PostRegistration = require("../../../../components/login/PostRegistration");
var config = require("../../../../../config.json");
module.exports = React.createClass({
displayName: 'MatrixChat',
@ -63,6 +65,14 @@ module.exports = React.createClass({
});
},
onLogoutClick: function(event) {
dis.dispatch({
action: 'logout'
});
event.stopPropagation();
event.preventDefault();
},
handleResize: function(e) {
var hideLhsThreshold = 1000;
var showLhsThreshold = 1000;
@ -92,19 +102,46 @@ module.exports = React.createClass({
});
},
onRegisterClick: function() {
this.showScreen("register");
},
onLoginClick: function() {
this.showScreen("login");
},
onRegistered: function(credentials) {
this.onLoggedIn(credentials);
// do post-registration stuff
this.showScreen("post_registration");
},
onFinishPostRegistration: function() {
// Don't confuse this with "PageType" which is the middle window to show
this.setState({
screen: undefined
});
this.showScreen("settings");
},
render: function() {
var LeftPanel = sdk.getComponent('organisms.LeftPanel');
var RoomView = sdk.getComponent('organisms.RoomView');
var RightPanel = sdk.getComponent('organisms.RightPanel');
var Login = sdk.getComponent('templates.Login');
var UserSettings = sdk.getComponent('organisms.UserSettings');
var Register = sdk.getComponent('templates.Register');
var CreateRoom = sdk.getComponent('organisms.CreateRoom');
var RoomDirectory = sdk.getComponent('organisms.RoomDirectory');
var MatrixToolbar = sdk.getComponent('molecules.MatrixToolbar');
var Notifier = sdk.getComponent('organisms.Notifier');
if (this.state.logged_in && this.state.ready) {
// needs to be before normal PageTypes as you are logged in technically
if (this.state.screen == 'post_registration') {
return (
<PostRegistration
onComplete={this.onFinishPostRegistration} />
);
}
else if (this.state.logged_in && this.state.ready) {
var page_element;
var right_panel = "";
@ -154,21 +191,33 @@ module.exports = React.createClass({
);
}
} else if (this.state.logged_in) {
var Spinner = sdk.getComponent('atoms.Spinner');
return (
<Loader />
<div className="mx_MatrixChat_splash">
<Spinner />
<a href="#" className="mx_MatrixChat_splashButtons" onClick={ this.onLogoutClick }>Logout</a>
</div>
);
} else if (this.state.screen == 'register') {
return (
<Register onLoggedIn={this.onLoggedIn} clientSecret={this.state.register_client_secret}
sessionId={this.state.register_session_id} idSid={this.state.register_id_sid}
hsUrl={this.state.register_hs_url} isUrl={this.state.register_is_url}
<Registration
clientSecret={this.state.register_client_secret}
sessionId={this.state.register_session_id}
idSid={this.state.register_id_sid}
hsUrl={config.default_hs_url}
isUrl={config.default_is_url}
registrationUrl={this.props.registrationUrl}
/>
onLoggedIn={this.onRegistered}
onLoginClick={this.onLoginClick} />
);
} else {
return (
<Login onLoggedIn={this.onLoggedIn} />
<Login
onLoggedIn={this.onLoggedIn}
onRegisterClick={this.onRegisterClick}
homeserverUrl={config.default_hs_url}
identityServerUrl={config.default_is_url} />
);
}
}
});
});

View file

@ -1,194 +0,0 @@
/*
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 sdk = require('matrix-react-sdk')
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var Loader = require("react-loader");
var LoginController = require('matrix-react-sdk/lib/controllers/templates/Login')
var config = require('../../../../../config.json');
module.exports = React.createClass({
displayName: 'Login',
mixins: [LoginController],
getInitialState: function() {
return {
serverConfigVisible: false
};
},
componentWillMount: function() {
this.onHSChosen();
this.customHsUrl = config.default_hs_url;
this.customIsUrl = config.default_is_url;
},
getHsUrl: function() {
if (this.state.serverConfigVisible) {
return this.customHsUrl;
} else {
return config.default_hs_url;
}
},
getIsUrl: function() {
if (this.state.serverConfigVisible) {
return this.customIsUrl;
} else {
return config.default_is_url;
}
},
onServerConfigVisibleChange: function(ev) {
this.setState({
serverConfigVisible: ev.target.checked
}, this.onHsUrlChanged);
},
/**
* Gets the form field values for the current login stage
*/
getFormVals: function() {
return {
'username': this.refs.user.getDOMNode().value.trim(),
'password': this.refs.pass.getDOMNode().value.trim()
};
},
onHsUrlChanged: function() {
var newHsUrl = this.refs.serverConfig.getHsUrl().trim();
var newIsUrl = this.refs.serverConfig.getIsUrl().trim();
if (newHsUrl == this.customHsUrl &&
newIsUrl == this.customIsUrl)
{
return;
}
else {
this.customHsUrl = newHsUrl;
this.customIsUrl = newIsUrl;
}
MatrixClientPeg.replaceUsingUrls(
this.getHsUrl(),
this.getIsUrl()
);
this.setState({
hs_url: this.getHsUrl(),
is_url: this.getIsUrl()
});
// XXX: HSes do not have to offer password auth, so we
// need to update and maybe show a different component
// when a new HS is entered.
if (this.updateHsTimeout) {
clearTimeout(this.updateHsTimeout);
}
var self = this;
this.updateHsTimeout = setTimeout(function() {
self.onHSChosen();
}, 1000);
},
componentForStep: function(step) {
switch (step) {
case 'choose_hs':
case 'fetch_stages':
var serverConfigStyle = {};
serverConfigStyle.display = this.state.serverConfigVisible ? 'block' : 'none';
var ServerConfig = sdk.getComponent("molecules.ServerConfig");
return (
<div>
<input className="mx_Login_checkbox" id="advanced" type="checkbox" checked={this.state.serverConfigVisible} onChange={this.onServerConfigVisibleChange} />
<label className="mx_Login_label" htmlFor="advanced">Use custom server options (advanced)</label>
<div style={serverConfigStyle}>
<ServerConfig ref="serverConfig"
defaultHsUrl={this.customHsUrl} defaultIsUrl={this.customIsUrl}
onHsUrlChanged={this.onHsUrlChanged}
/>
</div>
</div>
);
// XXX: clearly these should be separate organisms
case 'stage_m.login.password':
return (
<div>
<form onSubmit={this.onUserPassEntered}>
<input className="mx_Login_field" ref="user" type="text" value={this.state.username} onChange={this.onUsernameChanged} placeholder="Email or user name" /><br />
<input className="mx_Login_field" ref="pass" type="password" value={this.state.password} onChange={this.onPasswordChanged} placeholder="Password" /><br />
{ this.componentForStep('choose_hs') }
<input className="mx_Login_submit" type="submit" value="Log in" />
</form>
</div>
);
case 'stage_m.login.cas':
var CasLogin = sdk.getComponent('organisms.CasLogin');
return (
<CasLogin />
);
}
},
onUsernameChanged: function(ev) {
this.setState({username: ev.target.value});
},
onPasswordChanged: function(ev) {
this.setState({password: ev.target.value});
},
loginContent: function() {
var loader = this.state.busy ? <div className="mx_Login_loader"><Loader /></div> : null;
return (
<div>
<h2>Sign in</h2>
{this.componentForStep(this.state.step)}
<div className="mx_Login_error">
{ loader }
{this.state.errorText}
</div>
<a className="mx_Login_create" onClick={this.showRegister} href="#">Create a new account</a>
<br/>
<div className="mx_Login_links">
<a href="https://medium.com/@Vector">blog</a>&nbsp;&nbsp;&middot;&nbsp;&nbsp;
<a href="https://twitter.com/@VectorCo">twitter</a>&nbsp;&nbsp;&middot;&nbsp;&nbsp;
<a href="https://github.com/vector-im/vector-web">github</a>&nbsp;&nbsp;&middot;&nbsp;&nbsp;
<a href="https://matrix.org">powered by Matrix</a>
</div>
</div>
);
},
render: function() {
return (
<div className="mx_Login">
<div className="mx_Login_box">
<div className="mx_Login_logo">
<img src="img/logo.png" width="249" height="78" alt="vector"/>
</div>
{this.loginContent()}
</div>
</div>
);
}
});

View file

@ -1,201 +0,0 @@
/*
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 sdk = require('matrix-react-sdk')
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg')
var Loader = require("react-loader");
var RegisterController = require('../../../../controllers/templates/Register')
var config = require('../../../../../config.json');
module.exports = React.createClass({
displayName: 'Register',
mixins: [RegisterController],
getInitialState: function() {
return {
serverConfigVisible: false
};
},
componentWillMount: function() {
this.customHsUrl = config.default_hs_url;
this.customIsUrl = config.default_is_url;
},
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()
};
},
getHsUrl: function() {
if (this.state.serverConfigVisible) {
return this.customHsUrl;
} else {
return config.default_hs_url;
}
},
getIsUrl: function() {
if (this.state.serverConfigVisible) {
return this.customIsUrl;
} else {
return config.default_is_url;
}
},
onServerConfigVisibleChange: function(ev) {
this.setState({
serverConfigVisible: ev.target.checked
});
},
onServerUrlChanged: function(newUrl) {
this.customHsUrl = this.refs.serverConfig.getHsUrl();
this.customIsUrl = this.refs.serverConfig.getIsUrl();
this.forceUpdate();
},
onProfileContinueClicked: function() {
this.onAccountReady();
},
componentForStep: function(step) {
switch (step) {
case 'initial':
var serverConfigStyle = {};
serverConfigStyle.display = this.state.serverConfigVisible ? 'block' : 'none';
var ServerConfig = sdk.getComponent("molecules.ServerConfig");
return (
<div>
<form onSubmit={this.onInitialStageSubmit}>
<input className="mx_Login_field" type="text" ref="email" placeholder="Email address" defaultValue={this.savedParams.email} /><br />
<input className="mx_Login_field" type="text" ref="username" placeholder="User name" defaultValue={this.savedParams.username} /><br />
<input className="mx_Login_field" type="password" ref="password" placeholder="Password" defaultValue={this.savedParams.password} /><br />
<input className="mx_Login_field" type="password" ref="confirmPassword" placeholder="Confirm password" defaultValue={this.savedParams.confirmPassword} /><br />
<input className="mx_Login_checkbox" id="advanced" type="checkbox" value={this.state.serverConfigVisible} onChange={this.onServerConfigVisibleChange} />
<label htmlFor="advanced">Use custom server options (advanced)</label>
<div style={serverConfigStyle}>
<ServerConfig ref="serverConfig"
defaultHsUrl={this.customHsUrl} defaultIsUrl={this.customIsUrl}
onHsUrlChanged={this.onServerUrlChanged} onIsUrlChanged={this.onServerUrlChanged} />
</div>
<br />
<input className="mx_Login_submit" type="submit" value="Register" />
</form>
</div>
);
// XXX: clearly these should be separate organisms
case 'stage_m.login.email.identity':
return (
<div>
Please check your email to continue registration.
</div>
);
case 'stage_m.login.recaptcha':
return (
<div ref="recaptchaContainer">
This Home Server would like to make sure you are not a robot
<div id="mx_recaptcha"></div>
</div>
);
}
},
registerContent: function() {
if (this.state.busy) {
return (
<Loader />
);
} else if (this.state.step == 'profile') {
var ChangeDisplayName = sdk.getComponent('molecules.ChangeDisplayName');
var ChangeAvatar = sdk.getComponent('molecules.ChangeAvatar');
return (
<div className="mx_Login_profile">
Set a display name:
<ChangeDisplayName />
Upload an avatar:
<ChangeAvatar initialAvatarUrl={MatrixClientPeg.get().mxcUrlToHttp(this.state.avatarUrl)} />
<button onClick={this.onProfileContinueClicked}>Continue</button>
</div>
);
} else {
return (
<div>
<h2>Create an account</h2>
{this.componentForStep(this.state.step)}
<div className="mx_Login_error">{this.state.errorText}</div>
<a className="mx_Login_create" onClick={this.showLogin} href="#">I already have an account</a>
</div>
);
}
},
onBadFields: function(bad) {
var keys = Object.keys(bad);
var strings = [];
for (var i = 0; i < keys.length; ++i) {
switch (bad[keys[i]]) {
case this.FieldErrors.PasswordMismatch:
strings.push("Passwords don't match");
break;
case this.FieldErrors.Missing:
strings.push("Missing "+keys[i]);
break;
case this.FieldErrors.TooShort:
strings.push(keys[i]+" is too short");
break;
case this.FieldErrors.InUse:
strings.push(keys[i]+" is already taken");
break;
case this.FieldErrors.Length:
strings.push(keys[i] + " is not long enough.");
break;
default:
console.error("Unhandled FieldError: %s", bad[keys[i]]);
break;
}
}
var errtxt = strings.join(', ');
this.setState({
errorText: errtxt
});
},
render: function() {
return (
<div className="mx_Login">
<div className="mx_Login_box">
<div className="mx_Login_logo">
<img src="img/logo.png" width="249" height="78" alt="vector"/>
</div>
{this.registerContent()}
</div>
</div>
);
}
});