diff --git a/.gitignore b/.gitignore index 13466ce8..7cbd6fc2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ node_modules vector/bundle.* lib +.DS_Store +key.pem +cert.pem +build diff --git a/README.md b/README.md index a15917a4..c834e013 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ setup above, and your changes will cause an instant rebuild. However, all serious development on Vector happens on the `develop` branch. This typically depends on the `develop` snapshot versions of `matrix-react-sdk` and `matrix-js-sdk` -too, which isn't expressed in Vector's `package.json`. To do this, check out +too, which isn't handled by Vector's `package.json`. To get the right dependencies, check out the `develop` branches of these libraries and then use `npm link` to tell Vector about them: diff --git a/package.json b/package.json index 27302a74..eb9c3aff 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "matrix-react-sdk": "^0.0.2", "q": "^1.4.1", "react": "^0.13.3", - "react-loader": "^1.4.0" + "react-loader": "^1.4.0", + "sanitize-html": "^1.11.1" }, "devDependencies": { "babel": "^5.8.23", diff --git a/src/DateUtils.js b/src/DateUtils.js new file mode 100644 index 00000000..fe363586 --- /dev/null +++ b/src/DateUtils.js @@ -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. +*/ + +'use strict'; + +var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; +var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + +module.exports = { + formatDate: function(date) { + // date.toLocaleTimeString is completely system dependent. + // just go 24h for now + function pad(n) { + return (n < 10 ? '0' : '') + n; + } + + var now = new Date(); + if (date.toDateString() === now.toDateString()) { + return pad(date.getHours()) + ':' + pad(date.getMinutes()); + } + else if (now.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) { + return days[date.getDay()] + " " + pad(date.getHours()) + ':' + pad(date.getMinutes()); + } + else if (now.getFullYear() === date.getFullYear()) { + return days[date.getDay()] + ", " + months[date.getMonth()] + " " + (date.getDay()+1) + " " + pad(date.getHours()) + ':' + pad(date.getMinutes()); + } + else { + return days[date.getDay()] + ", " + months[date.getMonth()] + " " + (date.getDay()+1) + " " + date.getFullYear() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes()); + } + } +} + diff --git a/src/controllers/organisms/RoomList.js b/src/controllers/organisms/RoomList.js index 151a6ca2..964a2648 100644 --- a/src/controllers/organisms/RoomList.js +++ b/src/controllers/organisms/RoomList.js @@ -36,11 +36,9 @@ module.exports = { cli.on("RoomState.events", this.onRoomStateEvents); cli.on("RoomMember.name", this.onRoomMemberName); - var rooms = this.getRoomList(); - this.setState({ - roomList: rooms, - activityMap: {} - }); + var s = this.getRoomLists(); + s.activityMap = {}; + this.setState(s); }, componentDidMount: function() { @@ -87,9 +85,7 @@ module.exports = { onRoomTimeline: function(ev, room, toStartOfTimeline) { if (toStartOfTimeline) return; - var newState = { - roomList: this.getRoomList() - }; + var newState = this.getRoomLists(); if ( room.roomId != this.props.selectedRoom && ev.getSender() != MatrixClientPeg.get().credentials.userId) @@ -123,18 +119,23 @@ module.exports = { refreshRoomList: function() { - var rooms = this.getRoomList(); - this.setState({ - roomList: rooms - }); + this.setState(this.getRoomLists()); }, - getRoomList: function() { - return RoomListSorter.mostRecentActivityFirst( + getRoomLists: function() { + var s = {}; + var inviteList = []; + s.roomList = RoomListSorter.mostRecentActivityFirst( MatrixClientPeg.get().getRooms().filter(function(room) { var me = room.getMember(MatrixClientPeg.get().credentials.userId); + + if (me && me.membership == "invite") { + inviteList.push(room); + return false; + } + var shouldShowRoom = ( - me && (me.membership == "join" || me.membership == "invite") + me && (me.membership == "join") ); // hiding conf rooms only ever toggles shouldShowRoom to false if (shouldShowRoom && HIDE_CONFERENCE_CHANS) { @@ -153,6 +154,8 @@ module.exports = { return shouldShowRoom; }) ); + s.inviteList = RoomListSorter.mostRecentActivityFirst(inviteList); + return s; }, _recheckCallElement: function(selectedRoomId) { @@ -174,10 +177,10 @@ module.exports = { } }, - makeRoomTiles: function() { + makeRoomTiles: function(list, isInvite) { var self = this; var RoomTile = sdk.getComponent("molecules.RoomTile"); - return this.state.roomList.map(function(room) { + return list.map(function(room) { var selected = room.roomId == self.props.selectedRoom; return ( <RoomTile @@ -187,6 +190,7 @@ module.exports = { selected={selected} unread={self.state.activityMap[room.roomId] === 1} highlight={self.state.activityMap[room.roomId] === 2} + isInvite={isInvite} /> ); }); diff --git a/src/controllers/organisms/RoomView.js b/src/controllers/organisms/RoomView.js index 21027cbf..c0077bc0 100644 --- a/src/controllers/organisms/RoomView.js +++ b/src/controllers/organisms/RoomView.js @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +var Matrix = require("matrix-js-sdk"); var MatrixClientPeg = require("matrix-react-sdk/lib/MatrixClientPeg"); var React = require("react"); var q = require("q"); @@ -38,6 +39,8 @@ module.exports = { uploadingRoomSettings: false, numUnreadMessages: 0, draggingFile: false, + searching: false, + searchResults: null, } }, @@ -356,6 +359,41 @@ module.exports = { return WhoIsTyping.whoIsTypingString(this.state.room); }, + onSearch: function(term, scope) { + var filter; + if (scope === "Room") { // FIXME: should be enum + filter = { + // XXX: it's unintuitive that the filter for searching doesn't have the same shape as the v2 filter API :( + rooms: [ + this.props.roomId + ] + }; + } + + var self = this; + MatrixClientPeg.get().search({ + body: { + search_categories: { + room_events: { + search_term: term, + filter: filter, + event_context: { + before_limit: 1, + after_limit: 1, + } + } + } + } + }).then(function(data) { + self.setState({ + searchTerm: term, + searchResults: data, + }); + }, function(error) { + // TODO: show dialog or something + }); + }, + getEventTiles: function() { var DateSeparator = sdk.getComponent('molecules.DateSeparator'); @@ -364,6 +402,36 @@ module.exports = { var EventTile = sdk.getComponent('molecules.EventTile'); + if (this.state.searchResults) { + // XXX: this dance is foul, due to the results API not returning sorted results + var results = this.state.searchResults.search_categories.room_events.results; + var eventIds = Object.keys(results); + // XXX: todo: merge overlapping results somehow? + // XXX: why doesn't searching on name work? + var resultList = eventIds.map(function(key) { return results[key]; }).sort(function(a, b) { b.rank - a.rank }); + for (var i = 0; i < resultList.length; i++) { + var ts1 = resultList[i].result.origin_server_ts; + ret.push(<li key={ts1 + "-search"}><DateSeparator ts={ts1}/></li>); // Rank: {resultList[i].rank} + var mxEv = new Matrix.MatrixEvent(resultList[i].result); + if (resultList[i].context.events_before[0]) { + var mxEv2 = new Matrix.MatrixEvent(resultList[i].context.events_before[0]); + if (EventTile.supportsEventType(mxEv2.getType())) { + ret.push(<li key={mxEv.getId() + "-1"}><EventTile mxEvent={mxEv2} contextual={true} /></li>); + } + } + if (EventTile.supportsEventType(mxEv.getType())) { + ret.push(<li key={mxEv.getId() + "+0"}><EventTile mxEvent={mxEv} searchTerm={this.state.searchTerm}/></li>); + } + if (resultList[i].context.events_after[0]) { + var mxEv2 = new Matrix.MatrixEvent(resultList[i].context.events_after[0]); + if (EventTile.supportsEventType(mxEv2.getType())) { + ret.push(<li key={mxEv.getId() + "+1"}><EventTile mxEvent={mxEv2} contextual={true} /></li>); + } + } + } + return ret; + } + for (var i = this.state.room.timeline.length-1; i >= 0 && count < this.state.messageCap; --i) { var mxEv = this.state.room.timeline[i]; diff --git a/src/skins/vector/css/atoms/ImageView.css b/src/skins/vector/css/atoms/ImageView.css new file mode 100644 index 00000000..22ef343b --- /dev/null +++ b/src/skins/vector/css/atoms/ImageView.css @@ -0,0 +1,144 @@ +/* +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. +*/ + +/* This has got to be the most fragile piece of CSS ever written. + But empirically it works on Chrome/FF/Safari + */ + +.mx_ImageView { + display: -webkit-flex; + display: flex; + width: 100%; + height: 100%; + -webkit-align-items: center; + align-items: center; +} + +.mx_ImageView_lhs { + -webkit-box-ordinal-group: 1; + order: 1; + -webkit-flex: 1; + flex: 1 1 10%; + min-width: 60px; + /* + background-color: #080; + height: 20px; + */ +} + +.mx_ImageView_content { + -webkit-box-ordinal-group: 2; + order: 2; + /* min-width hack needed for FF */ + min-width: 0px; + height: 90%; + -webkit-flex: 15; + flex: 15 15 0; + + display: -webkit-flex; + display: flex; + -webkit-align-items: center; + -webkit-justify-content: center; + align-items: center; + justify-content: center; +} + +.mx_ImageView_content img { + max-width: 100%; + /* XXX: max-height interacts badly with flex on Chrome and doesn't relayout properly until you refresh */ + max-height: 100%; + /* object-fit hack needed for Chrome due to Chrome not relaying out until you refresh */ + object-fit: contain; + /* background-image: url('img/trans.png'); */ +} + +.mx_ImageView_labelWrapper { + position: absolute; + top: 0px; + height: 100%; + overflow: auto; +} + +.mx_ImageView_label { + text-align: left; + display: flex; + display: -webkit-flex; + justify-content: center; + -webkit-justify-content: center; + flex-direction: column; + -webkit-flex-direction: column; + padding-left: 60px; + padding-right: 60px; + min-height: 100%; + color: #fff; +} + +.mx_ImageView_name { + font-size: 20px; + margin-bottom: 6px; + pointer-events: all; +} + +.mx_ImageView_metadata { + font-size: 16px; + opacity: 0.5; + pointer-events: all; +} + +.mx_ImageView_download { + pointer-events: all; + display: table; + margin-top: 24px; + margin-bottom: 6px; + border-radius: 5px; + background-color: #454545; + font-size: 16px; + padding: 9px; + border: 1px solid #fff; +} + +.mx_ImageView_size { + font-size: 12px; +} + +.mx_ImageView_link { + color: #fff ! important; + text-decoration: none ! important; +} + +.mx_ImageView_button { + pointer-events: all; + font-size: 16px; + opacity: 0.5; + margin-top: 18px; + cursor: pointer; +} + +.mx_ImageView_shim { + height: 30px; +} + +.mx_ImageView_rhs { + -webkit-box-ordinal-group: 3; + order: 3; + -webkit-flex: 1; + flex: 1 1 10%; + min-width: 300px; + /* + background-color: #800; + height: 20px; + */ +} diff --git a/src/skins/vector/css/atoms/MemberAvatar.css b/src/skins/vector/css/atoms/MemberAvatar.css index 6422df79..fc5fd60d 100644 --- a/src/skins/vector/css/atoms/MemberAvatar.css +++ b/src/skins/vector/css/atoms/MemberAvatar.css @@ -17,6 +17,5 @@ limitations under the License. .mx_MemberAvatar { z-index: 20; border-radius: 20px; - background-color: #dbdbdb; } diff --git a/src/skins/vector/css/common.css b/src/skins/vector/css/common.css index 93012c0f..a68d190d 100644 --- a/src/skins/vector/css/common.css +++ b/src/skins/vector/css/common.css @@ -22,7 +22,7 @@ html { } body { - font-family: 'Lato', Helvetica, Arial, Sans-Serif; + font-family: 'Myriad Pro', Helvetica, Arial, Sans-Serif; font-size: 16px; color: #454545; border: 0px; @@ -34,7 +34,7 @@ div.error { } h2 { - color: #80cef4; + color: #454545; font-weight: 400; font-size: 20px; margin-top: 16px; @@ -44,7 +44,7 @@ h2 { a:hover, a:link, a:visited { - color: #80CEF4; + color: #76cfa6; } .mx_ContextualMenu_background { @@ -58,7 +58,7 @@ a:visited { } .mx_ContextualMenu { - border: 1px solid #a9dbf4; + border: 1px solid #a4a4a4; border-radius: 8px; background-color: #fff; color: #747474; @@ -129,13 +129,21 @@ a:visited { font-size: 16px; position: relative; border-radius: 8px; - max-width: 75%; + max-width: 80%; } -.mx_ImageView { - margin: 6px; - /* hack: flexbox bug? */ - margin-bottom: 4px; +.mx_Dialog_lightbox .mx_Dialog_background { + opacity: 0.85; +} + +.mx_Dialog_lightbox .mx_Dialog { + border-radius: 0px; + background-color: transparent; + width: 100%; + height: 100%; + max-width: 100%; + max-height: 100%; + pointer-events: none; } .mx_Dialog_content { @@ -153,7 +161,7 @@ a:visited { font-weight: 400; font-size: 16px; color: #fff; - background-color: #80cef4; + background-color: #76cfa6; margin-left: 8px; margin-right: 8px; padding-left: 1em; @@ -164,7 +172,7 @@ a:visited { .mx_QuestionDialogTitle { min-height: 16px; padding: 12px; - border-bottom: 1px solid #a9dbf4; + border-bottom: 1px solid #a4a4a4; font-weight: bold; font-size: 20px; line-height: 1.4; diff --git a/src/skins/vector/css/hide.css b/src/skins/vector/css/hide.css index fbc2db20..7d8ee302 100644 --- a/src/skins/vector/css/hide.css +++ b/src/skins/vector/css/hide.css @@ -1,7 +1,4 @@ .mx_RoomDropTarget, -.mx_RoomList_favourites_label, -.mx_RoomList_archive_label, -.mx_RoomHeader_search, .mx_RoomSettings_encrypt, .mx_CreateRoom_encrypt, .mx_RightPanel_filebutton diff --git a/src/skins/vector/css/molecules/EventAsTextTile.css b/src/skins/vector/css/molecules/EventAsTextTile.css new file mode 100644 index 00000000..d18fdc80 --- /dev/null +++ b/src/skins/vector/css/molecules/EventAsTextTile.css @@ -0,0 +1,19 @@ +/* +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_EventAsTextTile { + opacity: 0.5; +} diff --git a/src/skins/vector/css/molecules/EventTile.css b/src/skins/vector/css/molecules/EventTile.css index 1cd2fa46..eb59711e 100644 --- a/src/skins/vector/css/molecules/EventTile.css +++ b/src/skins/vector/css/molecules/EventTile.css @@ -17,20 +17,19 @@ limitations under the License. .mx_EventTile { max-width: 100%; clear: both; - margin-top: 32px; + margin-top: 24px; margin-left: 56px; } .mx_EventTile_avatar { - padding-left: 12px; + padding-left: 18px; padding-right: 12px; margin-left: -64px; - margin-top: -7px; + margin-top: -4px; float: left; } .mx_EventTile_avatar img { - background-color: #dbdbdb; border-radius: 20px; border: 0px; } @@ -48,19 +47,30 @@ limitations under the License. } .mx_EventTile .mx_MessageTimestamp { - color: #454545; - opacity: 0.5; - font-size: 14px; + color: #acacac; + font-size: 12px; float: right; } +.mx_EventTile_line { + position: relative; +} + .mx_EventTile_content { padding-right: 100px; display: block; } -.mx_EventTile_notice .mx_MessageTile_content { - opacity: 0.5; +.mx_MessageTile_content { + display: block; + margin-right: 100px; +} + +.mx_MessageTile_searchHighlight { + background-color: #76cfa6; + color: #fff; + border-radius: 5px; + padding: 4px; } .mx_EventTile_sending { @@ -75,38 +85,41 @@ limitations under the License. color: #FF0064; } +.mx_EventTile_contextual { + opacity: 0.4; +} + .mx_EventTile_msgOption { float: right; } .mx_MessageTimestamp { - display: none; + visibility: hidden; } .mx_EventTile_last .mx_MessageTimestamp { - display: block; + visibility: visible; } .mx_EventTile:hover .mx_MessageTimestamp { - display: block; + visibility: visible; } .mx_EventTile_editButton { - float: right; - display: none; - border: 0px; - outline: none; - margin-right: 3px; + position: absolute; + right: 1px; + top: 15px; + visibility: hidden; } .mx_EventTile:hover .mx_EventTile_editButton { - display: inline-block; + visibility: visible; } .mx_EventTile.menu .mx_EventTile_editButton { - display: inline-block; + visibility: visible; } .mx_EventTile.menu .mx_MessageTimestamp { - display: inline-block; + visibility: visible; } diff --git a/src/skins/vector/css/molecules/MImageTile.css b/src/skins/vector/css/molecules/MImageTile.css index 10e5b50b..94f1c919 100644 --- a/src/skins/vector/css/molecules/MImageTile.css +++ b/src/skins/vector/css/molecules/MImageTile.css @@ -23,12 +23,12 @@ limitations under the License. } .mx_MImageTile_download { - color: #80cef4; + color: #76cfa6; cursor: pointer; } .mx_MImageTile_download a { - color: #80cef4; + color: #76cfa6; text-decoration: none; } diff --git a/src/skins/vector/css/molecules/MNoticeTile.css b/src/skins/vector/css/molecules/MNoticeTile.css index 0e9c3ca1..0a0db62e 100644 --- a/src/skins/vector/css/molecules/MNoticeTile.css +++ b/src/skins/vector/css/molecules/MNoticeTile.css @@ -15,5 +15,5 @@ limitations under the License. */ .mx_MNoticeTile { - opacity: 0.5; + opacity: 0.6; } diff --git a/src/skins/vector/css/molecules/MTextTile.css b/src/skins/vector/css/molecules/MTextTile.css index 5b117e41..96a9d1db 100644 --- a/src/skins/vector/css/molecules/MTextTile.css +++ b/src/skins/vector/css/molecules/MTextTile.css @@ -17,4 +17,3 @@ limitations under the License. .mx_MTextTile { white-space: pre-wrap; } - diff --git a/src/skins/vector/css/molecules/MemberInfo.css b/src/skins/vector/css/molecules/MemberInfo.css index 52c48a79..6471a86c 100644 --- a/src/skins/vector/css/molecules/MemberInfo.css +++ b/src/skins/vector/css/molecules/MemberInfo.css @@ -14,3 +14,49 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_MemberInfo { + height: 100%; +} + +.mx_MemberInfo h2 { + margin-top: 6px; +} + +.mx_MemberInfo_cancel { + float: right; + margin-right: 18px; + cursor: pointer; +} + +.mx_MemberInfo_avatar { + clear: both; +} + +.mx_MemberInfo_avatar img { + border-radius: 48px; +} + +.mx_MemberInfo_profileField { + opacity: 0.6; + font-size: 14px; +} + +.mx_MemberInfo_buttons { + margin-top: 18px; +} + +.mx_MemberInfo_field { + cursor: pointer; + width: 100px; + text-align: center; + font-size: 12px; + background-color: #888; + color: #fff; + font-weight: bold; + border-radius: 20px; + padding-left: 6px; + padding-right: 6px; + padding-top: 4px; + padding-bottom: 2px; + margin-bottom: 4px; +} diff --git a/src/skins/vector/css/molecules/MemberTile.css b/src/skins/vector/css/molecules/MemberTile.css index faae142a..cfeaeaee 100644 --- a/src/skins/vector/css/molecules/MemberTile.css +++ b/src/skins/vector/css/molecules/MemberTile.css @@ -16,52 +16,27 @@ limitations under the License. .mx_MemberTile { display: table-row; - height: 49px; position: relative; + color: #454545; + cursor: pointer; } .mx_MemberTile_avatar { display: table-cell; - padding-left: 14px; + padding-left: 3px; padding-right: 12px; - padding-top: 3px; - padding-bottom: 3px; + padding-top: 2px; + padding-bottom: 0px; vertical-align: middle; - width: 40px; - height: 40px; + width: 36px; + height: 36px; position: relative; } -.mx_MemberTile_inviteTile { - cursor: pointer; -} - -.mx_MemberTile_inviteEditing { - display: initial ! important; -} - -.mx_MemberTile_inviteEditing .mx_MemberTile_avatar { - display: none; -} - -.mx_MemberTile_inviteEditing .mx_MemberTile_name { - width: 200px; -} - -.mx_MemberTile_inviteEditing .mx_MemberTile_name input { - border-radius: 3px; - border: 1px solid #c7c7c7; - font-weight: 300; - font-size: 14px; - padding: 9px; - margin-top: 6px; - margin-left: 14px; -} - .mx_MemberTile_power { position: absolute; - width: 48px; - height: 48px; + width: 44px; + height: 44px; left: 10px; top: -1px; } @@ -79,20 +54,18 @@ limitations under the License. vertical-align: middle; } -.mx_MemberTile_hover { - background-color: #f0f0f0; - font-size: 12px; - color: #747474; -} - .mx_MemberTile_userId { - font-weight: bold; + font-size: 14px; overflow: hidden; text-overflow: ellipsis; } -.mx_MemberTile_leave { - cursor: pointer; +.mx_MemberTile_presence { + font-size: 12px; + opacity: 0.5; +} + +.mx_MemberTile_chevron { margin-top: 8px; margin-right: -4px; margin-left: 6px; @@ -113,14 +86,14 @@ limitations under the License. .mx_MemberTile_unavailable .mx_MemberTile_avatar, .mx_MemberTile_unavailable .mx_MemberTile_name, -.mx_MemberTile_unavailable .mx_MemberTile_nameSpan +.mx_MemberTile_unavailable .mx_MemberTile_userId { opacity: 0.66; } .mx_MemberTile_offline .mx_MemberTile_avatar, .mx_MemberTile_offline .mx_MemberTile_name, -.mx_MemberTile_offline .mx_MemberTile_nameSpan +.mx_MemberTile_offline .mx_MemberTile_userId { opacity: 0.25; } diff --git a/src/skins/vector/css/molecules/MessageComposer.css b/src/skins/vector/css/molecules/MessageComposer.css index af4934ee..44e12276 100644 --- a/src/skins/vector/css/molecules/MessageComposer.css +++ b/src/skins/vector/css/molecules/MessageComposer.css @@ -15,39 +15,37 @@ limitations under the License. */ .mx_MessageComposer_wrapper { - max-width: 720px; - height: 50px; + max-width: 960px; + height: 70px; vertical-align: middle; margin: auto; background-color: #fff; - border-radius: 25px; - border: 1px solid #a9dbf4; + border-top: 2px solid #e1dddd; } .mx_MessageComposer_row { display: table-row; width: 100%; - height: 50px; + height: 70px; } .mx_MessageComposer .mx_MessageComposer_avatar { display: table-cell; - padding-left: 5px; - padding-right: 10px; - height: 50px; + padding-left: 10px; + padding-right: 20px; + height: 70px; } .mx_MessageComposer .mx_MessageComposer_avatar img { - margin-top: 5px; + margin-top: 18px; border-radius: 20px; - background-color: #dbdbdb; } .mx_MessageComposer_input { display: table-cell; width: 100%; vertical-align: middle; - height: 50px; + height: 70px; } .mx_MessageComposer_input textarea { @@ -64,21 +62,32 @@ limitations under the License. box-shadow: none; /* needed for FF */ - font-family: 'Lato', Helvetica, Arial, Sans-Serif; + font-family: 'Myriad Pro', Helvetica, Arial, Sans-Serif; } /* hack for FF as vertical alignment of custom placeholder text is broken */ .mx_MessageComposer_input textarea::-moz-placeholder { line-height: 100%; + color: #76cfa6; +} +.mx_MessageComposer_input textarea::-webkit-input-placeholder { + color: #76cfa6; } -.mx_MessageComposer_upload { +.mx_MessageComposer_upload, +.mx_MessageComposer_call { display: table-cell; vertical-align: middle; - padding-right: 15px; + padding-left: 10px; + padding-right: 10px; cursor: pointer; } +.mx_MessageComposer_call { + padding-right: 10px; + padding-top: 4px; +} + .mx_MessageComposer_upload img { margin-top: 5px; } diff --git a/src/skins/vector/css/molecules/RoomHeader.css b/src/skins/vector/css/molecules/RoomHeader.css index f7adb618..2eeda241 100644 --- a/src/skins/vector/css/molecules/RoomHeader.css +++ b/src/skins/vector/css/molecules/RoomHeader.css @@ -18,10 +18,10 @@ limitations under the License. } .mx_RoomHeader_wrapper { - max-width: 720px; + max-width: 960px; margin: auto; - height: 88px; - border-bottom: 1px solid #a8dbf3; + height: 83px; + border-bottom: 1px solid #eeeeee; display: -webkit-box; display: -moz-box; @@ -47,7 +47,7 @@ limitations under the License. .mx_RoomHeader_textButton { height: 48px; margin-top: 18px; - background-color: #80cef4; + background-color: #76cfa6; border-radius: 48px; margin-right: 8px; color: #fff; @@ -71,11 +71,8 @@ limitations under the License. } .mx_RoomHeader_rightRow { - height: 48px; - margin-top: 18px; + margin-top: 32px; background-color: #fff; - border-radius: 48px; - border: 1px solid #a9dbf4; -webkit-box-ordinal-group: 3; -moz-box-ordinal-group: 3; @@ -91,8 +88,8 @@ limitations under the License. } .mx_RoomHeader_simpleHeader { - line-height: 88px; - color: #80cef4; + line-height: 83px; + color: #76cfa6; font-weight: 400; font-size: 20px; overflow: hidden; @@ -100,18 +97,39 @@ limitations under the License. } .mx_RoomHeader_name { + cursor: pointer; vertical-align: middle; height: 28px; - color: #80cef4; - font-weight: 400; - font-size: 20px; - padding-left: 16px; + color: #454545; + font-weight: 800; + font-size: 24px; + padding-left: 8px; padding-right: 16px; text-overflow: ellipsis; } +.mx_RoomHeader_nametext { + display: inline-block; +} + +.mx_RoomHeader_settingsButton { + display: inline-block; + visibility: hidden; + position: relative; + bottom: 10px; + left: 4px; +} + +.mx_RoomHeader_name:hover { + color: #76cfa6; +} + +.mx_RoomHeader_name:hover .mx_RoomHeader_settingsButton { + visibility: visible; +} + .mx_RoomHeader_nameEditing { - padding-left: 16px; + padding-left: 8px; padding-right: 16px; margin-top: -5px; } @@ -133,9 +151,9 @@ limitations under the License. vertical-align: bottom; float: left; max-height: 38px; - color: #70b5d7; + color: #454545; font-weight: 300; - padding-left: 16px; + padding-left: 8px; padding-right: 16px; overflow: hidden; text-overflow: ellipsis; @@ -153,9 +171,8 @@ limitations under the License. } .mx_RoomHeader_button { - height: 48px; display: table-cell; - vertical-align: middle; + vertical-align: top; padding-left: 8px; padding-right: 8px; } @@ -170,4 +187,4 @@ limitations under the License. .mx_RoomHeader_voipButtons { margin-top: 18px; -} \ No newline at end of file +} diff --git a/src/skins/vector/css/molecules/RoomSettings.css b/src/skins/vector/css/molecules/RoomSettings.css index 3a66f0e9..a669c5b2 100644 --- a/src/skins/vector/css/molecules/RoomSettings.css +++ b/src/skins/vector/css/molecules/RoomSettings.css @@ -61,7 +61,7 @@ limitations under the License. font-weight: 400; font-size: 16px; color: #fff; - background-color: #80cef4; + background-color: #76cfa6; width: auto; margin: auto; padding: 6px; diff --git a/src/skins/vector/css/molecules/RoomTile.css b/src/skins/vector/css/molecules/RoomTile.css index 511fc94f..f2c1daad 100644 --- a/src/skins/vector/css/molecules/RoomTile.css +++ b/src/skins/vector/css/molecules/RoomTile.css @@ -17,24 +17,24 @@ limitations under the License. .mx_RoomTile { cursor: pointer; display: table-row; - color: #818794; + font-size: 14px; } .mx_RoomTile_avatar { display: table-cell; - padding-right: 10px; - padding-top: 3px; - padding-bottom: 3px; - padding-left: 10px; + background: #eaf5f0; + padding-right: 8px; + padding-top: 4px; + padding-bottom: 2px; + padding-left: 18px; vertical-align: middle; - width: 36px; - height: 36px; + width: 24px; + height: 24px; position: relative; } .mx_RoomTile_avatar img { border-radius: 20px; - background-color: #dbdbdb; } .mx_RoomTile_name { @@ -43,6 +43,13 @@ limitations under the License. overflow: hidden; text-overflow: ellipsis; padding-right: 16px; + color: #454545; + opacity: 0.8; +} + +.mx_RoomTile_invite { + opacity: 0.5; + font-weight: normal; } .collapsed .mx_RoomTile_name { @@ -63,7 +70,7 @@ limitations under the License. } .mx_RoomTile_badge { - background-color: #80cef4; + background-color: #76cfa6; color: #fff; border-radius: 26px; font-weight: 400; @@ -75,6 +82,7 @@ limitations under the License. } */ +/* .mx_RoomTile_badge { background-color: #ff0064; border: 3px solid #fff; @@ -85,19 +93,36 @@ limitations under the License. right: 9px; bottom: 3px; } +*/ + +.mx_RoomTile_badge { + background-color: #76cfa6; + width: 4px; + position: absolute; + left: 0px; + top: 5px; + height: 24px; +} .mx_RoomTile_unread, .mx_RoomTile_highlight, .mx_RoomTile_invited { font-weight: bold; - color: #000; } .mx_RoomTile_selected { - background-color: #f3f8fa; - color: #80cef4; - font-weight: bold; +} + +.mx_RoomTile.mx_RoomTile_selected { + background: url('img/selected.png'); + background-repeat: no-repeat; + background-position: right center; +} + +.mx_RoomTile_arrow { + position: absolute; + right: 0px; } .mx_RoomTile:hover { diff --git a/src/skins/vector/css/molecules/RoomTooltip.css b/src/skins/vector/css/molecules/RoomTooltip.css index f45970fe..604c6a56 100644 --- a/src/skins/vector/css/molecules/RoomTooltip.css +++ b/src/skins/vector/css/molecules/RoomTooltip.css @@ -17,7 +17,7 @@ limitations under the License. .mx_RoomTooltip { display: none; position: fixed; - border: 1px solid #a9dbf4; + border: 1px solid #a4a4a4; border-radius: 8px; background-color: #fff; z-index: 1000; diff --git a/src/skins/vector/css/molecules/SearchBar.css b/src/skins/vector/css/molecules/SearchBar.css new file mode 100644 index 00000000..b116674b --- /dev/null +++ b/src/skins/vector/css/molecules/SearchBar.css @@ -0,0 +1,64 @@ +/* +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_SearchBar { + padding-top: 5px; + padding-bottom: 5px; + display: flex; + align-items: center; +} + +.mx_SearchBar input { + display: inline block; + border-radius: 3px; + border: 1px solid #f0f0f0; + font-size: 16px; + padding: 9px; + padding-left: 11px; + margin-right: 17px; + width: auto; + flex: 1 1 0; +} + +.mx_SearchBar_button { + display: inline; + border: 0px; + border-radius: 36px; + font-weight: 400; + font-size: 16px; + color: #fff; + background-color: #76cfa6; + width: auto; + margin: auto; + margin-left: 7px; + padding-top: 6px; + padding-bottom: 4px; + padding-left: 24px; + padding-right: 24px; + cursor: pointer; +} + +.mx_SearchBar_unselected { + background-color: #fff; + color: #9fddc1; + border: #9fddc1 1px solid; +} + +.mx_SearchBar_cancel { + padding-left: 14px; + padding-right: 14px; + cursor: pointer; +} diff --git a/src/skins/vector/css/molecules/voip/IncomingCallbox.css b/src/skins/vector/css/molecules/voip/IncomingCallbox.css index 2c57a327..24b24cc2 100644 --- a/src/skins/vector/css/molecules/voip/IncomingCallbox.css +++ b/src/skins/vector/css/molecules/voip/IncomingCallbox.css @@ -16,7 +16,7 @@ limitations under the License. .mx_IncomingCallBox { text-align: center; - border: 1px solid #a9dbf4; + border: 1px solid #a4a4a4; border-radius: 8px; background-color: #fff; position: absolute; diff --git a/src/skins/vector/css/organisms/CreateRoom.css b/src/skins/vector/css/organisms/CreateRoom.css index d6b1765c..578c79e6 100644 --- a/src/skins/vector/css/organisms/CreateRoom.css +++ b/src/skins/vector/css/organisms/CreateRoom.css @@ -15,7 +15,7 @@ limitations under the License. */ .mx_CreateRoom { - width: 720px; + width: 960px; margin-left: auto; margin-right: auto; color: #4a4a4a; diff --git a/src/skins/vector/css/organisms/LeftPanel.css b/src/skins/vector/css/organisms/LeftPanel.css index 0e7e077e..67f00c35 100644 --- a/src/skins/vector/css/organisms/LeftPanel.css +++ b/src/skins/vector/css/organisms/LeftPanel.css @@ -53,17 +53,20 @@ limitations under the License. -webkit-order: 3; order: 3; - -webkit-flex: 0 0 170px; - flex: 0 0 170px; - - border-top: 1px solid #f3f8fa; + -webkit-flex: 0 0 126px; + flex: 0 0 126px; } .mx_LeftPanel .mx_BottomLeftMenu .mx_RoomTile { - color: #378bb4; + color: #454545; } .mx_LeftPanel .mx_BottomLeftMenu .mx_BottomLeftMenu_options { margin-top: 12px; width: 100%; +} + +.mx_LeftPanel .mx_BottomLeftMenu img { + border-radius: 0px; + background-color: transparent; } \ No newline at end of file diff --git a/src/skins/vector/css/organisms/MemberList.css b/src/skins/vector/css/organisms/MemberList.css index aab0def4..df699e64 100644 --- a/src/skins/vector/css/organisms/MemberList.css +++ b/src/skins/vector/css/organisms/MemberList.css @@ -16,8 +16,6 @@ limitations under the License. .mx_MemberList { height: 100%; - margin-bottom: 100px; - padding: 8px; -webkit-flex: 1; flex: 1; @@ -39,22 +37,47 @@ limitations under the License. } .mx_MemberList_border { - border: 1px solid #a9dbf4; overflow-y: auto; - border-radius: 8px; - background-color: #fff; order: 1; -webkit-flex: 1 1 0; flex: 1 1 0px; } +.mx_MemberList_invite { + font-family: 'Myriad Pro', Helvetica, Arial, Sans-Serif; + border-radius: 3px; + border: 1px solid #f0f0f0; + padding: 9px; + color: #454545; + margin-left: 3px; + font-size: 16px; + margin-bottom: 8px; + width: 180px; +} + +.mx_MemberList_invite::-moz-placeholder { + color: #454545; + opacity: 0.5; +} +.mx_MessageList_invite::-webkit-input-placeholder { + color: #454545; + opacity: 0.5; +} + +.mx_MemberList_invited h2 { + text-transform: uppercase; + color: #3d3b39; + font-weight: 600; + font-size: 14px; + padding-left: 3px; + padding-right: 12px; + margin-top: 8px; + margin-bottom: 4px; +} + .mx_MemberList_wrapper { display: table; table-layout: fixed; width: 100%; } - -.mx_MemberList h2 { - margin: 14px; -} diff --git a/src/skins/vector/css/organisms/RightPanel.css b/src/skins/vector/css/organisms/RightPanel.css index 30ef4726..bf473a44 100644 --- a/src/skins/vector/css/organisms/RightPanel.css +++ b/src/skins/vector/css/organisms/RightPanel.css @@ -33,32 +33,53 @@ limitations under the License. -webkit-order: 1; order: 1; - -webkit-flex: 0 0 66px; - flex: 0 0 66px; + -webkit-flex: 0 0 83px; + flex: 0 0 83px; } /** Fixme - factor this out with the main header **/ .mx_RightPanel_headerButtonGroup { - margin-top: 18px; - height: 48px; - float: right; + margin-top: 32px; + float: left; background-color: #fff; - border-radius: 48px; - border: 1px solid #a9dbf4; - margin-right: 22px; + margin-left: -4px; } .mx_RightPanel_headerButton { cursor: pointer; - height: 48px; display: table-cell; vertical-align: middle; - padding-left: 8px; - padding-right: 8px; + padding-left: 15px; + padding-right: 15px; + position: relative; } -.mx_RightPanel .mx_MemberList { +.mx_RightPanel_headerButton_highlight { + position: absolute; + bottom: -2px; + left: 10px; + width: 25px; + height: 4px; + background-color: #76cfa6; +} + +.mx_RightPanel_headerButton_badge { + position: absolute; + top: 5px; + left: 28px; + font-size: 12px; + background-color: #76cfa6; + color: #fff; + font-weight: bold; + border-radius: 20px; + padding-left: 4px; + padding-right: 4px; + padding-top: 2px; +} + +.mx_RightPanel .mx_MemberList, +.mx_RightPanel .mx_MemberInfo { -webkit-box-ordinal-group: 2; -moz-box-ordinal-group: 2; -ms-flex-order: 2; diff --git a/src/skins/vector/css/organisms/RoomDirectory.css b/src/skins/vector/css/organisms/RoomDirectory.css index 21985a25..f53f0556 100644 --- a/src/skins/vector/css/organisms/RoomDirectory.css +++ b/src/skins/vector/css/organisms/RoomDirectory.css @@ -15,7 +15,7 @@ limitations under the License. */ .mx_RoomDirectory { - width: 720px; + width: 960px; margin-left: auto; margin-right: auto; margin-bottom: 12px; diff --git a/src/skins/vector/css/organisms/RoomList.css b/src/skins/vector/css/organisms/RoomList.css index 21cb7812..34ebd1db 100644 --- a/src/skins/vector/css/organisms/RoomList.css +++ b/src/skins/vector/css/organisms/RoomList.css @@ -15,17 +15,30 @@ limitations under the License. */ .mx_RoomList { + padding-top: 24px; } +.mx_RoomList_invites, .mx_RoomList_recents { - margin-top: -12px; display: table; table-layout: fixed; width: 100%; } +.mx_RoomList_expandButton { + margin-left: 8px; + cursor: pointer; + padding-left: 12px; + padding-right: 12px; +} + .mx_RoomList h2 { - padding-left: 16px; - padding-right: 16px; - padding-bottom: 10px; -} \ No newline at end of file + text-transform: uppercase; + color: #3d3b39; + font-weight: 600; + font-size: 14px; + padding-left: 12px; + padding-right: 12px; + margin-top: 8px; + margin-bottom: 4px; +} diff --git a/src/skins/vector/css/organisms/RoomView.css b/src/skins/vector/css/organisms/RoomView.css index 2aab203a..d564b086 100644 --- a/src/skins/vector/css/organisms/RoomView.css +++ b/src/skins/vector/css/organisms/RoomView.css @@ -36,13 +36,13 @@ limitations under the License. -webkit-order: 1; order: 1; - -webkit-flex: 0 0 88px; - flex: 0 0 88px; + -webkit-flex: 0 0 83px; + flex: 0 0 83px; } .mx_RoomView_fileDropTarget { min-width: 0px; - max-width: 720px; + max-width: 960px; width: 100%; font-size: 20px; text-align: center; @@ -61,10 +61,10 @@ limitations under the License. border-top-right-radius: 10px; background-color: rgba(255, 255, 255, 0.9); - border: 2px dashed #80cef4; + border: 2px #e1dddd solid; border-bottom: none; position: absolute; - top: 88px; + top: 83px; bottom: 0px; z-index: 3000; } @@ -84,12 +84,12 @@ limitations under the License. order: 2; min-width: 0px; - max-width: 720px; + max-width: 960px; width: 100%; margin: auto; overflow: auto; - border-bottom: 1px solid #a8dbf3; + border-bottom: 1px solid #eee; -webkit-flex: 0 0 auto; flex: 0 0 auto; @@ -111,7 +111,7 @@ limitations under the License. } .mx_RoomView_messageListWrapper { - max-width: 720px; + max-width: 960px; margin: auto; } @@ -129,8 +129,9 @@ limitations under the License. clear: both; margin-top: 32px; margin-bottom: 8px; + margin-left: 54px; padding-bottom: 6px; - border-bottom: 1px solid #a8dbf3; + border-bottom: 1px solid #eee; } .mx_RoomView_invitePrompt { @@ -141,7 +142,7 @@ limitations under the License. order: 2; min-width: 0px; - max-width: 720px; + max-width: 960px; width: 100%; margin: auto; @@ -157,44 +158,45 @@ limitations under the License. order: 4; width: 100%; - -webkit-flex: 0 0 58px; - flex: 0 0 58px; + -webkit-flex: 0 0 36px; + flex: 0 0 36px; } .mx_RoomView_statusAreaBox { - max-width: 720px; + max-width: 960px; margin: auto; - border-top: 1px solid #a8dbf3; +} + +.mx_RoomView_statusAreaBox_line { + border-top: 1px solid #eee; + margin-left: 54px; + height: 1px; } .mx_RoomView_unreadMessagesBar { - margin-top: 13px; - color: #fff; - font-weight: bold; - background-color: #ff0064; - border-radius: 30px; - height: 30px; - line-height: 30px; + color: #ff0064; cursor: pointer; + margin-top: 5px; } .mx_RoomView_unreadMessagesBar img { - padding-left: 22px; + padding-left: 10px; padding-right: 22px; + vertical-align: middle; } .mx_RoomView_typingBar { - margin-top: 17px; - margin-left: 56px; - color: #818794; + margin-top: 10px; + margin-left: 54px; + color: #4a4a4a; + opacity: 0.5; } -.mx_RoomView_typingBar img { - padding-left: 12px; - padding-right: 12px; - margin-left: -64px; - margin-top: -7px; - float: left; +.mx_RoomView_typingImage { + display: inline; + margin-left: -38px; + margin-top: -4px; + float: left; } .mx_RoomView .mx_MessageComposer { @@ -205,44 +207,46 @@ limitations under the License. order: 5; width: 100%; - -webkit-flex: 0 0 63px; - flex: 0 0 63px; + -webkit-flex: 0 0 70px; + flex: 0 0 70px; margin-right: 2px; } .mx_RoomView_uploadProgressOuter { - width: 100%; - background-color: rgba(169, 219, 244, 0.5); height: 4px; + margin-left: 54px; + margin-top: -1px; } .mx_RoomView_uploadProgressInner { - background-color: #80cef4; + background-color: #76cfa6; height: 4px; } .mx_RoomView_uploadFilename { - margin-top: 15px; + margin-top: 5px; margin-left: 56px; + opacity: 0.5; + color: #4a4a4a; } .mx_RoomView_uploadIcon { float: left; - margin-top: 6px; - margin-left: 5px; + margin-top: 1px; + margin-left: 14px; } .mx_RoomView_uploadCancel { float: right; - margin-top: 6px; + margin-top: 5px; margin-right: 10px; } .mx_RoomView_uploadBytes { float: right; - opacity: 0.5; - margin-top: 15px; - margin-right: 10px; + margin-top: 5px; + margin-right: 30px; + color: #76cfa6; } .mx_RoomView_ongoingConfCallNotification { @@ -251,5 +255,5 @@ limitations under the License. background-color: #ff0064; color: #fff; font-weight: bold; - padding: 6px; -} \ No newline at end of file + padding: 6px 0; +} diff --git a/src/skins/vector/css/organisms/UserSettings.css b/src/skins/vector/css/organisms/UserSettings.css index b69399b7..2b0aca3d 100644 --- a/src/skins/vector/css/organisms/UserSettings.css +++ b/src/skins/vector/css/organisms/UserSettings.css @@ -15,7 +15,7 @@ limitations under the License. */ .mx_UserSettings { - width: 720px; + width: 960px; margin-left: auto; margin-right: auto; } diff --git a/src/skins/vector/css/organisms/ViewSource.css b/src/skins/vector/css/organisms/ViewSource.css index ae61ae58..f932c9a4 100644 --- a/src/skins/vector/css/organisms/ViewSource.css +++ b/src/skins/vector/css/organisms/ViewSource.css @@ -1,3 +1,22 @@ +/* +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_ViewSource pre { text-align: left; + font-size: 12px; + padding: 0.5em 1em 0.5em 1em; + word-wrap: break-word; } diff --git a/src/skins/vector/css/pages/MatrixChat.css b/src/skins/vector/css/pages/MatrixChat.css index 6fa0415a..f649aa24 100644 --- a/src/skins/vector/css/pages/MatrixChat.css +++ b/src/skins/vector/css/pages/MatrixChat.css @@ -69,6 +69,8 @@ limitations under the License. -webkit-order: 1; order: 1; + background-color: #eaf5f0; + -webkit-flex: 0 0 230px; flex: 0 0 230px; } @@ -87,7 +89,7 @@ limitations under the License. padding-left: 12px; padding-right: 12px; - background-color: #f3f8fa; + background-color: #fff; -webkit-flex: 1; flex: 1; @@ -114,7 +116,6 @@ limitations under the License. -webkit-order: 3; order: 3; - background-color: #f3f8fa; -webkit-flex: 0 0 230px; flex: 0 0 230px; } diff --git a/src/skins/vector/fonts/MyriadPro-Bold.woff b/src/skins/vector/fonts/MyriadPro-Bold.woff new file mode 100644 index 00000000..f30c591f Binary files /dev/null and b/src/skins/vector/fonts/MyriadPro-Bold.woff differ diff --git a/src/skins/vector/fonts/MyriadPro-BoldIt.woff b/src/skins/vector/fonts/MyriadPro-BoldIt.woff new file mode 100644 index 00000000..db605a6c Binary files /dev/null and b/src/skins/vector/fonts/MyriadPro-BoldIt.woff differ diff --git a/src/skins/vector/fonts/MyriadPro-It.woff b/src/skins/vector/fonts/MyriadPro-It.woff new file mode 100644 index 00000000..9a133bcd Binary files /dev/null and b/src/skins/vector/fonts/MyriadPro-It.woff differ diff --git a/src/skins/vector/fonts/MyriadPro-Regular.woff b/src/skins/vector/fonts/MyriadPro-Regular.woff new file mode 100644 index 00000000..5a95583f Binary files /dev/null and b/src/skins/vector/fonts/MyriadPro-Regular.woff differ diff --git a/src/skins/vector/fonts/MyriadPro-SemiBold.woff b/src/skins/vector/fonts/MyriadPro-SemiBold.woff new file mode 100644 index 00000000..2a80f65d Binary files /dev/null and b/src/skins/vector/fonts/MyriadPro-SemiBold.woff differ diff --git a/src/skins/vector/fonts/MyriadPro.css b/src/skins/vector/fonts/MyriadPro.css new file mode 100644 index 00000000..833e6828 --- /dev/null +++ b/src/skins/vector/fonts/MyriadPro.css @@ -0,0 +1,20 @@ +@font-face { + font-family: 'Myriad Pro'; + font-style: normal; + font-weight: normal; + src: local('Myriad Pro'), local('MyriadPro'), url(MyriadPro-Regular.woff) format('woff'); +} + +@font-face { + font-family: 'Myriad Pro'; + font-style: normal; + font-weight: 600; + src: local('Myriad Pro SemiBold'), local('MyriadPro-SemiBold'), url(MyriadPro-SemiBold.woff) format('woff'); +} + +@font-face { + font-family: 'Myriad Pro'; + font-style: normal; + font-weight: bold; + src: local('Myriad Pro Bold'), local('MyriadPro-Bold'), url(MyriadPro-Bold.woff) format('woff'); +} diff --git a/src/skins/vector/img/76cfa6.png b/src/skins/vector/img/76cfa6.png new file mode 100644 index 00000000..de1ea60d Binary files /dev/null and b/src/skins/vector/img/76cfa6.png differ diff --git a/src/skins/vector/img/call.png b/src/skins/vector/img/call.png new file mode 100644 index 00000000..a7805e05 Binary files /dev/null and b/src/skins/vector/img/call.png differ diff --git a/src/skins/vector/img/cancel-black.png b/src/skins/vector/img/cancel-black.png new file mode 100644 index 00000000..87dcfd41 Binary files /dev/null and b/src/skins/vector/img/cancel-black.png differ diff --git a/src/skins/vector/img/cancel.png b/src/skins/vector/img/cancel.png index c963a4b7..2bda8ff5 100644 Binary files a/src/skins/vector/img/cancel.png and b/src/skins/vector/img/cancel.png differ diff --git a/src/skins/vector/img/chevron-left.png b/src/skins/vector/img/chevron-left.png index 12abcc26..efb0065d 100644 Binary files a/src/skins/vector/img/chevron-left.png and b/src/skins/vector/img/chevron-left.png differ diff --git a/src/skins/vector/img/chevron-right.png b/src/skins/vector/img/chevron-right.png index 1fe5d347..18a4684e 100644 Binary files a/src/skins/vector/img/chevron-right.png and b/src/skins/vector/img/chevron-right.png differ diff --git a/src/skins/vector/img/chevron.png b/src/skins/vector/img/chevron.png index 3df8655b..81236f91 100644 Binary files a/src/skins/vector/img/chevron.png and b/src/skins/vector/img/chevron.png differ diff --git a/src/skins/vector/img/create-big.png b/src/skins/vector/img/create-big.png index 247b0030..b7307a11 100644 Binary files a/src/skins/vector/img/create-big.png and b/src/skins/vector/img/create-big.png differ diff --git a/src/skins/vector/img/directory-big.png b/src/skins/vector/img/directory-big.png index bbcb16a4..03cab69c 100644 Binary files a/src/skins/vector/img/directory-big.png and b/src/skins/vector/img/directory-big.png differ diff --git a/src/skins/vector/img/edit.png b/src/skins/vector/img/edit.png index 2686885f..6f373d3f 100644 Binary files a/src/skins/vector/img/edit.png and b/src/skins/vector/img/edit.png differ diff --git a/src/skins/vector/img/fileicon.png b/src/skins/vector/img/fileicon.png index be277a1c..af018efa 100644 Binary files a/src/skins/vector/img/fileicon.png and b/src/skins/vector/img/fileicon.png differ diff --git a/src/skins/vector/img/files.png b/src/skins/vector/img/files.png new file mode 100644 index 00000000..83932267 Binary files /dev/null and b/src/skins/vector/img/files.png differ diff --git a/src/skins/vector/img/member_chevron.png b/src/skins/vector/img/member_chevron.png new file mode 100644 index 00000000..cbbd289d Binary files /dev/null and b/src/skins/vector/img/member_chevron.png differ diff --git a/src/skins/vector/img/members.png b/src/skins/vector/img/members.png index 6526f236..b5e58757 100644 Binary files a/src/skins/vector/img/members.png and b/src/skins/vector/img/members.png differ diff --git a/src/skins/vector/img/newmessages.png b/src/skins/vector/img/newmessages.png index e6dbeb17..a22156ab 100644 Binary files a/src/skins/vector/img/newmessages.png and b/src/skins/vector/img/newmessages.png differ diff --git a/src/skins/vector/img/search.png b/src/skins/vector/img/search.png index d2c99855..2f98d290 100644 Binary files a/src/skins/vector/img/search.png and b/src/skins/vector/img/search.png differ diff --git a/src/skins/vector/img/selected.png b/src/skins/vector/img/selected.png new file mode 100644 index 00000000..8931cba7 Binary files /dev/null and b/src/skins/vector/img/selected.png differ diff --git a/src/skins/vector/img/settings-big.png b/src/skins/vector/img/settings-big.png index 3be13bc7..cb2e0a62 100644 Binary files a/src/skins/vector/img/settings-big.png and b/src/skins/vector/img/settings-big.png differ diff --git a/src/skins/vector/img/settings.png b/src/skins/vector/img/settings.png index 445a3909..264b3c9b 100644 Binary files a/src/skins/vector/img/settings.png and b/src/skins/vector/img/settings.png differ diff --git a/src/skins/vector/img/trans.png b/src/skins/vector/img/trans.png new file mode 100644 index 00000000..8ba2310a Binary files /dev/null and b/src/skins/vector/img/trans.png differ diff --git a/src/skins/vector/img/upload-big.png b/src/skins/vector/img/upload-big.png index 7754f587..c11c0c45 100644 Binary files a/src/skins/vector/img/upload-big.png and b/src/skins/vector/img/upload-big.png differ diff --git a/src/skins/vector/img/upload.png b/src/skins/vector/img/upload.png index 428501f0..7457bcd0 100644 Binary files a/src/skins/vector/img/upload.png and b/src/skins/vector/img/upload.png differ diff --git a/src/skins/vector/skindex.js b/src/skins/vector/skindex.js index 8dba10cf..e715656c 100644 --- a/src/skins/vector/skindex.js +++ b/src/skins/vector/skindex.js @@ -23,9 +23,6 @@ limitations under the License. var skin = {}; -skin['atoms.create_room.CreateRoomButton'] = require('./views/atoms/create_room/CreateRoomButton'); -skin['atoms.create_room.Presets'] = require('./views/atoms/create_room/Presets'); -skin['atoms.create_room.RoomAlias'] = require('./views/atoms/create_room/RoomAlias'); skin['atoms.EditableText'] = require('./views/atoms/EditableText'); skin['atoms.EnableNotificationsButton'] = require('./views/atoms/EnableNotificationsButton'); skin['atoms.ImageView'] = require('./views/atoms/ImageView'); @@ -33,6 +30,9 @@ skin['atoms.LogoutButton'] = require('./views/atoms/LogoutButton'); skin['atoms.MemberAvatar'] = require('./views/atoms/MemberAvatar'); skin['atoms.MessageTimestamp'] = require('./views/atoms/MessageTimestamp'); skin['atoms.RoomAvatar'] = require('./views/atoms/RoomAvatar'); +skin['atoms.create_room.CreateRoomButton'] = require('./views/atoms/create_room/CreateRoomButton'); +skin['atoms.create_room.Presets'] = require('./views/atoms/create_room/Presets'); +skin['atoms.create_room.RoomAlias'] = require('./views/atoms/create_room/RoomAlias'); skin['atoms.voip.VideoFeed'] = require('./views/atoms/voip/VideoFeed'); skin['molecules.BottomLeftMenu'] = require('./views/molecules/BottomLeftMenu'); skin['molecules.BottomLeftMenuTile'] = require('./views/molecules/BottomLeftMenuTile'); @@ -42,18 +42,18 @@ skin['molecules.ChangePassword'] = require('./views/molecules/ChangePassword'); skin['molecules.DateSeparator'] = require('./views/molecules/DateSeparator'); skin['molecules.EventAsTextTile'] = require('./views/molecules/EventAsTextTile'); skin['molecules.EventTile'] = require('./views/molecules/EventTile'); -skin['molecules.MatrixToolbar'] = require('./views/molecules/MatrixToolbar'); -skin['molecules.MemberInfo'] = require('./views/molecules/MemberInfo'); -skin['molecules.MemberTile'] = require('./views/molecules/MemberTile'); skin['molecules.MEmoteTile'] = require('./views/molecules/MEmoteTile'); -skin['molecules.MessageComposer'] = require('./views/molecules/MessageComposer'); -skin['molecules.MessageContextMenu'] = require('./views/molecules/MessageContextMenu'); -skin['molecules.MessageTile'] = require('./views/molecules/MessageTile'); skin['molecules.MFileTile'] = require('./views/molecules/MFileTile'); skin['molecules.MImageTile'] = require('./views/molecules/MImageTile'); skin['molecules.MNoticeTile'] = require('./views/molecules/MNoticeTile'); skin['molecules.MRoomMemberTile'] = require('./views/molecules/MRoomMemberTile'); skin['molecules.MTextTile'] = require('./views/molecules/MTextTile'); +skin['molecules.MatrixToolbar'] = require('./views/molecules/MatrixToolbar'); +skin['molecules.MemberInfo'] = require('./views/molecules/MemberInfo'); +skin['molecules.MemberTile'] = require('./views/molecules/MemberTile'); +skin['molecules.MessageComposer'] = require('./views/molecules/MessageComposer'); +skin['molecules.MessageContextMenu'] = require('./views/molecules/MessageContextMenu'); +skin['molecules.MessageTile'] = require('./views/molecules/MessageTile'); skin['molecules.ProgressBar'] = require('./views/molecules/ProgressBar'); skin['molecules.RoomCreate'] = require('./views/molecules/RoomCreate'); skin['molecules.RoomDropTarget'] = require('./views/molecules/RoomDropTarget'); @@ -61,6 +61,7 @@ skin['molecules.RoomHeader'] = require('./views/molecules/RoomHeader'); skin['molecules.RoomSettings'] = require('./views/molecules/RoomSettings'); 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'); diff --git a/src/skins/vector/views/atoms/ImageView.js b/src/skins/vector/views/atoms/ImageView.js index 676348c0..a842f7c8 100644 --- a/src/skins/vector/views/atoms/ImageView.js +++ b/src/skins/vector/views/atoms/ImageView.js @@ -18,10 +18,19 @@ limitations under the License. var React = require('react'); +var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); + +var DateUtils = require('../../../../DateUtils'); +var filesize = require('filesize'); + module.exports = React.createClass({ displayName: 'ImageView', - // XXX: keyboard shortcuts for managing dialogs should be done by the modal dialog base class omehow, surely... + propTypes: { + onFinished: React.PropTypes.func.isRequired + }, + + // XXX: keyboard shortcuts for managing dialogs should be done by the modal dialog base class somehow, surely... componentDidMount: function() { document.addEventListener("keydown", this.onKeyDown); }, @@ -38,10 +47,29 @@ module.exports = React.createClass({ } }, + onRedactClick: function() { + var self = this; + MatrixClientPeg.get().redactEvent( + this.props.mxEvent.getRoomId(), this.props.mxEvent.getId() + ).done(function() { + if (self.props.onFinished) self.props.onFinished(); + }, function(e) { + var ErrorDialog = sdk.getComponent("organisms.ErrorDialog"); + // display error message stating you couldn't delete this. + var code = e.errcode || e.statusCode; + Modal.createDialog(ErrorDialog, { + title: "Error", + description: "You cannot delete this image. (" + code + ")" + }); + }); + }, + render: function() { - // XXX: can't we just do max-width: 80%, max-height: 80% on the CSS? - +/* + // In theory max-width: 80%, max-height: 80% on the CSS should work + // but in practice, it doesn't, so do it manually: + var width = this.props.width || 500; var height = this.props.height || 500; @@ -65,9 +93,55 @@ module.exports = React.createClass({ width: displayWidth, height: displayHeight }; +*/ + var style, res; + + if (this.props.width && this.props.height) { + style = { + width: this.props.width, + height: this.props.height, + }; + res = ", " + style.width + "x" + style.height + "px"; + } return ( - <img className="mx_ImageView" src={this.props.src} style={style} /> + <div className="mx_ImageView"> + <div className="mx_ImageView_lhs"> + </div> + <div className="mx_ImageView_content"> + <img src={this.props.src} style={style}/> + <div className="mx_ImageView_labelWrapper"> + <div className="mx_ImageView_label"> + <div className="mx_ImageView_shim"> + </div> + <div className="mx_ImageView_name"> + { this.props.mxEvent.getContent().body } + </div> + <div className="mx_ImageView_metadata"> + Uploaded on { DateUtils.formatDate(new Date(this.props.mxEvent.getTs())) } by { this.props.mxEvent.getSender() } + </div> + <a className="mx_ImageView_link" href={ this.props.src } target="_blank"> + <div className="mx_ImageView_download"> + Download this file<br/> + <span className="mx_ImageView_size">({ filesize(this.props.mxEvent.getContent().info.size) }{ res })</span> + </div> + </a> + <div className="mx_ImageView_button"> + <a className="mx_ImageView_link" href={ this.props.src } target="_blank"> + View full screen + </a> + </div> + <div className="mx_ImageView_button" onClick={this.onRedactClick}> + Redact + </div> + <div className="mx_ImageView_shim"> + </div> + </div> + </div> + </div> + <div className="mx_ImageView_rhs"> + </div> + </div> ); } }); diff --git a/src/skins/vector/views/atoms/MessageTimestamp.js b/src/skins/vector/views/atoms/MessageTimestamp.js index 98cfe4a1..5795e556 100644 --- a/src/skins/vector/views/atoms/MessageTimestamp.js +++ b/src/skins/vector/views/atoms/MessageTimestamp.js @@ -17,40 +17,16 @@ limitations under the License. 'use strict'; var React = require('react'); - -var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; -var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; +var DateUtils = require('../../../../DateUtils'); module.exports = React.createClass({ displayName: 'MessageTimestamp', - formatDate: function(date) { - // date.toLocaleTimeString is completely system dependent. - // just go 24h for now - function pad(n) { - return (n < 10 ? '0' : '') + n; - } - - var now = new Date(); - if (date.toDateString() === now.toDateString()) { - return pad(date.getHours()) + ':' + pad(date.getMinutes()); - } - else if (now.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) { - return days[date.getDay()] + " " + pad(date.getHours()) + ':' + pad(date.getMinutes()); - } - else if (now.getFullYear() === date.getFullYear()) { - return days[date.getDay()] + ", " + months[date.getMonth()] + " " + (date.getDay()+1) + " " + pad(date.getHours()) + ':' + pad(date.getMinutes()); - } - else { - return days[date.getDay()] + ", " + months[date.getMonth()] + " " + (date.getDay()+1) + " " + date.getFullYear() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes()); - } - }, - render: function() { var date = new Date(this.props.ts); return ( <span className="mx_MessageTimestamp"> - { this.formatDate(date) } + { DateUtils.formatDate(date) } </span> ); }, diff --git a/src/skins/vector/views/atoms/RoomAvatar.js b/src/skins/vector/views/atoms/RoomAvatar.js index 3b5d4634..a1d87f7f 100644 --- a/src/skins/vector/views/atoms/RoomAvatar.js +++ b/src/skins/vector/views/atoms/RoomAvatar.js @@ -33,7 +33,7 @@ module.exports = React.createClass({ }, getFallbackAvatar: function() { - var images = [ '80cef4', '50e2c2', 'f4c371' ]; + var images = [ '76cfa6', '50e2c2', 'f4c371' ]; var total = 0; for (var i = 0; i < this.props.room.roomId.length; ++i) { total += this.props.room.roomId.charCodeAt(i); diff --git a/src/skins/vector/views/molecules/BottomLeftMenuTile.js b/src/skins/vector/views/molecules/BottomLeftMenuTile.js index 2644769c..8c28058d 100644 --- a/src/skins/vector/views/molecules/BottomLeftMenuTile.js +++ b/src/skins/vector/views/molecules/BottomLeftMenuTile.js @@ -48,7 +48,7 @@ module.exports = React.createClass({ return ( <div className="mx_RoomTile" onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onClick={this.props.onClick}> <div className="mx_RoomTile_avatar"> - <img src={ this.props.img } width="36" height="36"/> + <img src={ this.props.img } width="24" height="24"/> </div> { label } </div> diff --git a/src/skins/vector/views/molecules/EventTile.js b/src/skins/vector/views/molecules/EventTile.js index b92ab18b..6c2f948c 100644 --- a/src/skins/vector/views/molecules/EventTile.js +++ b/src/skins/vector/views/molecules/EventTile.js @@ -30,6 +30,7 @@ var eventTileTypes = { 'm.call.invite' : 'molecules.EventAsTextTile', 'm.call.answer' : 'molecules.EventAsTextTile', 'm.call.hangup' : 'molecules.EventAsTextTile', + 'm.room.name' : 'molecules.EventAsTextTile', 'm.room.topic' : 'molecules.EventAsTextTile', }; @@ -88,12 +89,13 @@ module.exports = React.createClass({ mx_EventTile_highlight: this.shouldHighlight(), mx_EventTile_continuation: this.props.continuation, mx_EventTile_last: this.props.last, - menu: this.state.menu + mx_EventTile_contextual: this.props.contextual, + menu: this.state.menu, }); var timestamp = <MessageTimestamp ts={this.props.mxEvent.getTs()} /> var editButton = ( <input - type="image" src="img/edit.png" alt="Edit" + type="image" src="img/edit.png" alt="Edit" width="14" height="14" className="mx_EventTile_editButton" onClick={this.onEditClicked} /> ); @@ -108,7 +110,7 @@ module.exports = React.createClass({ if (this.props.mxEvent.sender) { avatar = ( <div className="mx_EventTile_avatar"> - <MemberAvatar member={this.props.mxEvent.sender} /> + <MemberAvatar member={this.props.mxEvent.sender} width={24} height={24} /> </div> ); } @@ -120,10 +122,10 @@ module.exports = React.createClass({ <div className={classes}> { avatar } { sender } - <div> + <div className="mx_EventTile_line"> { timestamp } { editButton } - <EventTileType mxEvent={this.props.mxEvent} /> + <EventTileType mxEvent={this.props.mxEvent} searchTerm={this.props.searchTerm} /> </div> </div> ); diff --git a/src/skins/vector/views/molecules/MImageTile.js b/src/skins/vector/views/molecules/MImageTile.js index ed61a390..0667dabd 100644 --- a/src/skins/vector/views/molecules/MImageTile.js +++ b/src/skins/vector/views/molecules/MImageTile.js @@ -57,8 +57,9 @@ module.exports = React.createClass({ Modal.createDialog(ImageView, { src: httpUrl, width: content.info.w, - height: content.info.h - }); + height: content.info.h, + mxEvent: this.props.mxEvent, + }, "mx_Dialog_lightbox"); } }, @@ -67,7 +68,7 @@ module.exports = React.createClass({ var cli = MatrixClientPeg.get(); var thumbHeight = null; - if (content.info) thumbHeight = this.thumbHeight(content.info.w, content.info.h, 320, 240); + if (content.info) thumbHeight = this.thumbHeight(content.info.w, content.info.h, 480, 360); var imgStyle = {}; if (thumbHeight) imgStyle['height'] = thumbHeight; @@ -75,7 +76,7 @@ module.exports = React.createClass({ return ( <span className="mx_MImageTile"> <a href={cli.mxcUrlToHttp(content.url)} onClick={ this.onClick }> - <img className="mx_MImageTile_thumbnail" src={cli.mxcUrlToHttp(content.url, 320, 240)} alt={content.body} style={imgStyle} /> + <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"> diff --git a/src/skins/vector/views/molecules/MNoticeTile.js b/src/skins/vector/views/molecules/MNoticeTile.js index aa886127..a0cedb1d 100644 --- a/src/skins/vector/views/molecules/MNoticeTile.js +++ b/src/skins/vector/views/molecules/MNoticeTile.js @@ -17,18 +17,71 @@ limitations under the License. 'use strict'; var React = require('react'); +var sanitizeHtml = require('sanitize-html'); 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 :( 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; + } + } + return ( <span ref="content" className="mx_MNoticeTile mx_MessageTile_content"> - {content.body} + { body } </span> ); }, diff --git a/src/skins/vector/views/molecules/MTextTile.js b/src/skins/vector/views/molecules/MTextTile.js index 50555f94..12bafa37 100644 --- a/src/skins/vector/views/molecules/MTextTile.js +++ b/src/skins/vector/views/molecules/MTextTile.js @@ -17,18 +17,71 @@ limitations under the License. 'use strict'; var React = require('react'); +var sanitizeHtml = require('sanitize-html'); 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 :( 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; + } + } + return ( <span ref="content" className="mx_MTextTile mx_MessageTile_content"> - {content.body} + { body } </span> ); }, diff --git a/src/skins/vector/views/molecules/MemberInfo.js b/src/skins/vector/views/molecules/MemberInfo.js index a2a3874a..5f8e806d 100644 --- a/src/skins/vector/views/molecules/MemberInfo.js +++ b/src/skins/vector/views/molecules/MemberInfo.js @@ -20,19 +20,30 @@ var React = require('react'); var Loader = require("../atoms/Spinner"); var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); +var sdk = require('matrix-react-sdk') +var dis = require('matrix-react-sdk/lib/dispatcher'); var MemberInfoController = require('matrix-react-sdk/lib/controllers/molecules/MemberInfo') +// FIXME: this should probably be an organism, to match with MemberList, not a molecule + module.exports = React.createClass({ displayName: 'MemberInfo', mixins: [MemberInfoController], + onCancel: function(e) { + dis.dispatch({ + action: "view_user", + member: null + }); + }, + render: function() { var interactButton, kickButton, banButton, muteButton, giveModButton, spinner; if (this.props.member.userId === MatrixClientPeg.get().credentials.userId) { - interactButton = <div className="mx_ContextualMenu_field" onClick={this.onLeaveClick}>Leave room</div>; + interactButton = <div className="mx_MemberInfo_field" onClick={this.onLeaveClick}>Leave room</div>; } else { - interactButton = <div className="mx_ContextualMenu_field" onClick={this.onChatClick}>Start chat</div>; + interactButton = <div className="mx_MemberInfo_field" onClick={this.onChatClick}>Start chat</div>; } if (this.state.creatingRoom) { @@ -40,36 +51,50 @@ module.exports = React.createClass({ } if (this.state.can.kick) { - kickButton = <div className="mx_ContextualMenu_field" onClick={this.onKick}> + kickButton = <div className="mx_MemberInfo_field" onClick={this.onKick}> Kick </div>; } if (this.state.can.ban) { - banButton = <div className="mx_ContextualMenu_field" onClick={this.onBan}> + banButton = <div className="mx_MemberInfo_field" onClick={this.onBan}> Ban </div>; } if (this.state.can.mute) { var muteLabel = this.state.muted ? "Unmute" : "Mute"; - muteButton = <div className="mx_ContextualMenu_field" onClick={this.onMuteToggle}> + muteButton = <div className="mx_MemberInfo_field" onClick={this.onMuteToggle}> {muteLabel} </div>; } if (this.state.can.modifyLevel) { var giveOpLabel = this.state.isTargetMod ? "Revoke Mod" : "Make Mod"; - giveModButton = <div className="mx_ContextualMenu_field" onClick={this.onModToggle}> + giveModButton = <div className="mx_MemberInfo_field" onClick={this.onModToggle}> {giveOpLabel} </div> } + var MemberAvatar = sdk.getComponent('atoms.MemberAvatar'); return ( - <div> - {interactButton} - {muteButton} - {kickButton} - {banButton} - {giveModButton} - {spinner} + <div className="mx_MemberInfo"> + <img className="mx_MemberInfo_cancel" src="img/cancel-black.png" width="18" height="18" onClick={this.onCancel}/> + <div className="mx_MemberInfo_avatar"> + <MemberAvatar member={this.props.member} width={48} height={48} /> + </div> + <h2>{ this.props.member.name }</h2> + <div className="mx_MemberInfo_profileField"> + { this.props.member.userId } + </div> + <div className="mx_MemberInfo_profileField"> + power: { this.props.member.powerLevelNorm }% + </div> + <div className="mx_MemberInfo_buttons"> + {interactButton} + {muteButton} + {kickButton} + {banButton} + {giveModButton} + {spinner} + </div> </div> ); } diff --git a/src/skins/vector/views/molecules/MemberTile.js b/src/skins/vector/views/molecules/MemberTile.js index 4c34fcb7..25ba3db9 100644 --- a/src/skins/vector/views/molecules/MemberTile.js +++ b/src/skins/vector/views/molecules/MemberTile.js @@ -20,7 +20,7 @@ var React = require('react'); var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); var sdk = require('matrix-react-sdk') -var ContextualMenu = require('../../../../ContextualMenu'); +var dis = require('matrix-react-sdk/lib/dispatcher'); var MemberTileController = require('matrix-react-sdk/lib/controllers/molecules/MemberTile') // The Lato WOFF doesn't include sensible combining diacritics, so Chrome chokes on rendering them. @@ -58,16 +58,9 @@ module.exports = React.createClass({ }, onClick: function(e) { - var self = this; - self.setState({ 'menu': true }); - var MemberInfo = sdk.getComponent('molecules.MemberInfo'); - ContextualMenu.createMenu(MemberInfo, { - member: self.props.member, - right: window.innerWidth - e.pageX, - top: e.pageY, - onFinished: function() { - self.setState({ 'menu': false }); - } + dis.dispatch({ + action: 'view_user', + member: this.props.member, }); }, @@ -119,10 +112,10 @@ module.exports = React.createClass({ var isMyUser = MatrixClientPeg.get().credentials.userId == this.props.member.userId; var power; - if (this.props.member && this.props.member.powerLevelNorm > 0) { - var img = "img/p/p" + Math.floor(20 * this.props.member.powerLevelNorm / 100) + ".png"; - power = <img src={ img } className="mx_MemberTile_power" width="48" height="48" alt=""/>; - } + // if (this.props.member && this.props.member.powerLevelNorm > 0) { + // var img = "img/p/p" + Math.floor(20 * this.props.member.powerLevelNorm / 100) + ".png"; + // power = <img src={ img } className="mx_MemberTile_power" width="44" height="44" alt=""/>; + // } var presenceClass = "mx_MemberTile_offline"; var mainClassName = "mx_MemberTile "; if (this.props.member.user) { @@ -134,13 +127,13 @@ module.exports = React.createClass({ } } mainClassName += presenceClass; - if (this.state.hover || this.state.menu) { + if (this.state.hover) { mainClassName += " mx_MemberTile_hover"; } var name = this.props.member.name; // if (isMyUser) name += " (me)"; // this does nothing other than introduce line wrapping and pain - var leave = isMyUser ? <img className="mx_MemberTile_leave" src="img/delete.png" width="10" height="10" onClick={this.onLeaveClick}/> : null; + //var leave = isMyUser ? <img className="mx_MemberTile_leave" src="img/delete.png" width="10" height="10" onClick={this.onLeaveClick}/> : null; var nameClass = "mx_MemberTile_name"; if (zalgo.test(name)) { @@ -148,7 +141,7 @@ module.exports = React.createClass({ } var nameEl; - if (this.state.hover || this.state.menu) { + if (this.state.hover) { var presence; // FIXME: make presence data update whenever User.presence changes... var active = this.props.member.user ? ((Date.now() - (this.props.member.user.lastPresenceTs - this.props.member.user.lastActiveAgo)) || -1) : -1; @@ -161,8 +154,8 @@ module.exports = React.createClass({ nameEl = <div className="mx_MemberTile_details"> - { leave } - <div className="mx_MemberTile_userId">{ this.props.member.userId }</div> + <img className="mx_MemberTile_chevron" src="img/member_chevron.png" width="8" height="12"/> + <div className="mx_MemberTile_userId">{ name }</div> { presence } </div> } @@ -177,7 +170,7 @@ module.exports = React.createClass({ return ( <div className={mainClassName} title={ this.getPowerLabel() } onClick={ this.onClick } onMouseEnter={ this.mouseEnter } onMouseLeave={ this.mouseLeave }> <div className="mx_MemberTile_avatar"> - <MemberAvatar member={this.props.member} /> + <MemberAvatar member={this.props.member} width={36} height={36} /> { power } </div> { nameEl } diff --git a/src/skins/vector/views/molecules/MessageComposer.js b/src/skins/vector/views/molecules/MessageComposer.js index c94cade5..25f69bda 100644 --- a/src/skins/vector/views/molecules/MessageComposer.js +++ b/src/skins/vector/views/molecules/MessageComposer.js @@ -22,6 +22,7 @@ var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); var MessageComposerController = require('matrix-react-sdk/lib/controllers/molecules/MessageComposer') var sdk = require('matrix-react-sdk') +var dis = require('matrix-react-sdk/lib/dispatcher') module.exports = React.createClass({ displayName: 'MessageComposer', @@ -40,6 +41,14 @@ module.exports = React.createClass({ this.refs.uploadInput.getDOMNode().value = null; }, + onCallClick: function(ev) { + dis.dispatch({ + action: 'place_call', + type: ev.shiftKey ? "screensharing" : "video", + room_id: this.props.room.roomId + }); + }, + render: function() { var me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId); var uploadInputStyle = {display: 'none'}; @@ -49,15 +58,18 @@ module.exports = React.createClass({ <div className="mx_MessageComposer_wrapper"> <div className="mx_MessageComposer_row"> <div className="mx_MessageComposer_avatar"> - <MemberAvatar member={me} /> + <MemberAvatar member={me} width={24} height={24} /> </div> <div className="mx_MessageComposer_input"> - <textarea ref="textarea" onKeyDown={this.onKeyDown} placeholder="Type a message" /> + <textarea ref="textarea" onKeyDown={this.onKeyDown} placeholder="Type a message..." /> </div> <div className="mx_MessageComposer_upload" onClick={this.onUploadClick}> - <img src="img/upload.png" width="32" height="32"/> + <img src="img/upload.png" 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> </div> </div> </div> diff --git a/src/skins/vector/views/molecules/MessageContextMenu.js b/src/skins/vector/views/molecules/MessageContextMenu.js index 249d9a34..995c2c4b 100644 --- a/src/skins/vector/views/molecules/MessageContextMenu.js +++ b/src/skins/vector/views/molecules/MessageContextMenu.js @@ -84,7 +84,7 @@ module.exports = React.createClass({ else { redactButton = ( <div className="mx_ContextualMenu_field" onClick={this.onRedactClick}> - Delete + Redact </div> ); } diff --git a/src/skins/vector/views/molecules/MessageTile.js b/src/skins/vector/views/molecules/MessageTile.js index f30fee92..6ea44413 100644 --- a/src/skins/vector/views/molecules/MessageTile.js +++ b/src/skins/vector/views/molecules/MessageTile.js @@ -50,6 +50,6 @@ module.exports = React.createClass({ TileType = tileTypes[msgtype]; } - return <TileType mxEvent={this.props.mxEvent} />; + return <TileType mxEvent={this.props.mxEvent} searchTerm={this.props.searchTerm} />; }, }); diff --git a/src/skins/vector/views/molecules/RoomHeader.js b/src/skins/vector/views/molecules/RoomHeader.js index d3f9119a..7f45fd42 100644 --- a/src/skins/vector/views/molecules/RoomHeader.js +++ b/src/skins/vector/views/molecules/RoomHeader.js @@ -41,7 +41,7 @@ module.exports = React.createClass({ onFullscreenClick: function() { dis.dispatch({action: 'video_fullscreen', fullscreen: true}, true); }, - + render: function() { var EditableText = sdk.getComponent("atoms.EditableText"); var RoomAvatar = sdk.getComponent('atoms.RoomAvatar'); @@ -59,7 +59,6 @@ module.exports = React.createClass({ var topic = this.props.room.currentState.getStateEvents('m.room.topic', ''); var call_buttons; - var zoom_button; if (this.state && this.state.call_state != 'ended') { //var muteVideoButton; var activeCall = ( @@ -111,16 +110,15 @@ module.exports = React.createClass({ cancel_button = <div className="mx_RoomHeader_textButton" onClick={this.props.onCancelClick}>Cancel</div> save_button = <div className="mx_RoomHeader_textButton" onClick={this.props.onSaveClick}>Save Changes</div> } else { + // <EditableText label={this.props.room.name} initialValue={actual_name} placeHolder="Name" onValueChanged={this.onNameChange} /> name = - <div className="mx_RoomHeader_name"> - <EditableText label={this.props.room.name} initialValue={actual_name} placeHolder="Name" onValueChanged={this.onNameChange} /> + <div className="mx_RoomHeader_name" onClick={this.props.onSettingsClick}> + <div className="mx_RoomHeader_nametext">{ this.props.room.name }</div> + <div className="mx_RoomHeader_settingsButton"> + <img src="img/settings.png" width="12" height="12"/> + </div> </div> if (topic) topic_el = <div className="mx_RoomHeader_topic" title={topic.getContent().topic}>{ topic.getContent().topic }</div>; - settings_button = ( - <div className="mx_RoomHeader_button" onClick={this.props.onSettingsClick}> - <img src="img/settings.png" width="32" height="32"/> - </div> - ); } var roomAvatar = null; @@ -130,12 +128,23 @@ module.exports = React.createClass({ ); } - if (activeCall && activeCall.type == "video") { - zoom_button = ( - <div className="mx_RoomHeader_button" onClick={this.onFullscreenClick}> - <img src="img/zoom.png" title="Fullscreen" alt="Fullscreen" width="32" height="32" style={{ 'marginTop': '3px' }}/> - </div> - ); + var zoom_button, video_button, voice_button; + if (activeCall) { + if (activeCall.type == "video") { + zoom_button = ( + <div className="mx_RoomHeader_button" onClick={this.onFullscreenClick}> + <img src="img/zoom.png" title="Fullscreen" alt="Fullscreen" width="32" height="32" style={{ 'marginTop': '-5px' }}/> + </div> + ); + } + video_button = + <div className="mx_RoomHeader_button mx_RoomHeader_video" onClick={activeCall && activeCall.type === "video" ? this.onMuteVideoClick : this.onVideoClick}> + <img src="img/video.png" title="Video call" alt="Video call" width="32" height="32" style={{ 'marginTop': '-8px' }}/> + </div>; + voice_button = + <div className="mx_RoomHeader_button mx_RoomHeader_voice" onClick={activeCall ? this.onMuteAudioClick : this.onVoiceClick}> + <img src="img/voip.png" title="VoIP call" alt="VoIP call" width="32" height="32" style={{ 'marginTop': '-8px' }}/> + </div>; } header = @@ -153,16 +162,11 @@ module.exports = React.createClass({ {cancel_button} {save_button} <div className="mx_RoomHeader_rightRow"> - { settings_button } + { video_button } + { voice_button } { zoom_button } - <div className="mx_RoomHeader_button mx_RoomHeader_search"> - <img src="img/search.png" title="Search" alt="Search" width="32" height="32"/> - </div> - <div className="mx_RoomHeader_button mx_RoomHeader_video" onClick={activeCall && activeCall.type === "video" ? this.onMuteVideoClick : this.onVideoClick}> - <img src="img/video.png" title="Video call" alt="Video call" width="32" height="32"/> - </div> - <div className="mx_RoomHeader_button mx_RoomHeader_voice" onClick={activeCall ? this.onMuteAudioClick : this.onVoiceClick}> - <img src="img/voip.png" title="VoIP call" alt="VoIP call" width="32" height="32"/> + <div className="mx_RoomHeader_button"> + <img src="img/search.png" title="Search" alt="Search" width="21" height="19" onClick={this.props.onSearchClick}/> </div> </div> </div> diff --git a/src/skins/vector/views/molecules/RoomTile.js b/src/skins/vector/views/molecules/RoomTile.js index 39e7b43c..82616b5a 100644 --- a/src/skins/vector/views/molecules/RoomTile.js +++ b/src/skins/vector/views/molecules/RoomTile.js @@ -50,7 +50,16 @@ module.exports = React.createClass({ 'mx_RoomTile_highlight': this.props.highlight, 'mx_RoomTile_invited': this.props.room.currentState.members[myUserId].membership == 'invite' }); - var name = this.props.room.name.replace(":", ":\u200b"); + + var name; + if (this.props.isInvite) { + name = this.props.room.getMember(MatrixClientPeg.get().credentials.userId).events.member.getSender(); + } + else { + name = this.props.room.name; + } + + name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon var badge; if (this.props.highlight) { badge = <div className="mx_RoomTile_badge"/>; @@ -73,7 +82,8 @@ module.exports = React.createClass({ var label; if (!this.props.collapsed) { - label = <div className="mx_RoomTile_name">{name}</div>; + var className = 'mx_RoomTile_name' + (this.props.isInvite ? ' mx_RoomTile_invite' : ''); + label = <div className={ className }>{name}</div>; } else if (this.state.hover) { var RoomTooltip = sdk.getComponent("molecules.RoomTooltip"); @@ -84,7 +94,7 @@ module.exports = React.createClass({ return ( <div className={classes} onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}> <div className="mx_RoomTile_avatar"> - <RoomAvatar room={this.props.room} /> + <RoomAvatar room={this.props.room} width="24" height="24" /> { badge } </div> { label } diff --git a/src/skins/vector/views/molecules/SearchBar.js b/src/skins/vector/views/molecules/SearchBar.js new file mode 100644 index 00000000..d31e24b4 --- /dev/null +++ b/src/skins/vector/views/molecules/SearchBar.js @@ -0,0 +1,56 @@ +/* +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 MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); +var sdk = require('matrix-react-sdk'); + +module.exports = React.createClass({ + displayName: 'SearchBar', + + getInitialState: function() { + return ({ + scope: 'Room' + }); + }, + + onThisRoomClick: function() { + this.setState({ scope: 'Room' }); + }, + + onAllRoomsClick: function() { + this.setState({ scope: 'All' }); + }, + + onSearchChange: function(e) { + if (e.keyCode === 13) { // on enter... + this.props.onSearch(this.refs.search_term.getDOMNode().value, this.state.scope); + } + }, + + render: function() { + return ( + <div className="mx_SearchBar"> + <input ref="search_term" className="mx_SearchBar_input" type="text" autoFocus={true} placeholder="Search..." onKeyDown={this.onSearchChange}/> + <div className={"mx_SearchBar_button" + (this.state.scope !== 'Room' ? " mx_SearchBar_unselected" : "")} onClick={this.onThisRoomClick}>This Room</div> + <div className={"mx_SearchBar_button" + (this.state.scope !== 'All' ? " mx_SearchBar_unselected" : "")} onClick={this.onAllRoomsClick}>All Rooms</div> + <img className="mx_SearchBar_cancel" src="img/cancel-black.png" width="18" height="18" onClick={this.props.onCancelClick} /> + </div> + ); + } +}); diff --git a/src/skins/vector/views/organisms/LeftPanel.js b/src/skins/vector/views/organisms/LeftPanel.js index a57f7a0a..ec25f934 100644 --- a/src/skins/vector/views/organisms/LeftPanel.js +++ b/src/skins/vector/views/organisms/LeftPanel.js @@ -40,7 +40,8 @@ module.exports = React.createClass({ classes += " collapsed"; } else { - collapseButton = <img className="mx_LeftPanel_hideButton" onClick={ this.onHideClick } src="img/hide.png" width="12" height="20" alt="<"/> + // Hide the collapse button until we work out how to display it in the new skin + // collapseButton = <img className="mx_LeftPanel_hideButton" onClick={ this.onHideClick } src="img/hide.png" width="12" height="20" alt="<"/> } return ( diff --git a/src/skins/vector/views/organisms/MemberList.js b/src/skins/vector/views/organisms/MemberList.js index 08f4e45c..ba7bc010 100644 --- a/src/skins/vector/views/organisms/MemberList.js +++ b/src/skins/vector/views/organisms/MemberList.js @@ -30,7 +30,6 @@ module.exports = React.createClass({ mixins: [MemberListController], getInitialState: function() { - return { editing: false }; }, memberSort: function(userIdA, userIdB) { @@ -71,43 +70,21 @@ module.exports = React.createClass({ }); }, - onPopulateInvite: function(inputText, shouldSubmit) { - // reset back to placeholder - this.refs.invite.setValue("Invite", false, true); - this.setState({ editing: false }); - if (!shouldSubmit) { - return; // enter key wasn't pressed - } - this.onInvite(inputText); - }, - - onClickInvite: function(ev) { - this.setState({ editing: true }); - this.refs.invite.onClickDiv(); - ev.stopPropagation(); - ev.preventDefault(); + onPopulateInvite: function(e) { + this.onInvite(this.refs.invite.getDOMNode().value); + e.preventDefault(); }, inviteTile: function() { - var classes = classNames({ - mx_MemberTile: true, - mx_MemberTile_inviteTile: true, - mx_MemberTile_inviteEditing: this.state.editing, - }); - - var EditableText = sdk.getComponent("atoms.EditableText"); if (this.state.inviting) { return ( <Loader /> ); } else { return ( - <div className={ classes } onClick={ this.onClickInvite } > - <div className="mx_MemberTile_avatar"><img src="img/create-big.png" width="40" height="40" alt=""/></div> - <div className="mx_MemberTile_name"> - <EditableText ref="invite" label="Invite" placeHolder="@user:domain.com" initialValue="" onValueChanged={this.onPopulateInvite}/> - </div> - </div> + <form onSubmit={this.onPopulateInvite}> + <input className="mx_MemberList_invite" ref="invite" placeholder="Invite another user"/> + </form> ); } }, @@ -117,7 +94,7 @@ module.exports = React.createClass({ var invitedMemberTiles = this.makeMemberTiles('invite'); if (invitedMemberTiles.length > 0) { invitedSection = ( - <div> + <div className="mx_MemberList_invited"> <h2>Invited</h2> <div className="mx_MemberList_wrapper"> {invitedMemberTiles} @@ -127,18 +104,14 @@ module.exports = React.createClass({ } return ( <div className="mx_MemberList"> - <div className="mx_MemberList_chevron"> - <img src="img/chevron.png" width="24" height="13"/> - </div> <div className="mx_MemberList_border"> + {this.inviteTile()} <div> - <h2>Members</h2> <div className="mx_MemberList_wrapper"> {this.makeMemberTiles('join')} </div> </div> {invitedSection} - {this.inviteTile()} </div> </div> ); diff --git a/src/skins/vector/views/organisms/RightPanel.js b/src/skins/vector/views/organisms/RightPanel.js index 6d9dc743..feebcfeb 100644 --- a/src/skins/vector/views/organisms/RightPanel.js +++ b/src/skins/vector/views/organisms/RightPanel.js @@ -19,6 +19,7 @@ limitations under the License. var React = require('react'); var sdk = require('matrix-react-sdk') var dis = require('matrix-react-sdk/lib/dispatcher'); +var MatrixClientPeg = require("matrix-react-sdk/lib/MatrixClientPeg"); module.exports = React.createClass({ displayName: 'RightPanel', @@ -26,6 +27,20 @@ module.exports = React.createClass({ Phase : { MemberList: 'MemberList', FileList: 'FileList', + MemberInfo: 'MemberInfo', + }, + + componentWillMount: function() { + this.dispatcherRef = dis.register(this.onAction); + var cli = MatrixClientPeg.get(); + cli.on("RoomState.members", this.onRoomStateMember); + }, + + componentWillUnmount: function() { + dis.unregister(this.dispatcherRef); + if (MatrixClientPeg.get()) { + MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember); + } }, getInitialState: function() { @@ -48,25 +63,85 @@ module.exports = React.createClass({ } }, + onRoomStateMember: function(ev, state, member) { + // redraw the badge on the membership list + if (this.state.phase == this.Phase.MemberList && member.roomId === this.props.roomId) { + this.forceUpdate(); + } + }, + + onAction: function(payload) { + if (payload.action === "view_user") { + if (payload.member) { + this.setState({ + phase: this.Phase.MemberInfo, + member: payload.member, + }); + } + else { + this.setState({ + phase: this.Phase.MemberList + }); + } + } + if (payload.action === "view_room") { + if (this.state.phase === this.Phase.MemberInfo) { + this.setState({ + phase: this.Phase.MemberList + }); + } + } + }, + render: function() { var MemberList = sdk.getComponent('organisms.MemberList'); var buttonGroup; var panel; + var filesHighlight; + var membersHighlight; + if (!this.props.collapsed) { + if (this.state.phase == this.Phase.MemberList || this.state.phase === this.Phase.MemberInfo) { + membersHighlight = <div className="mx_RightPanel_headerButton_highlight"></div>; + } + else if (this.state.phase == this.Phase.FileList) { + filesHighlight = <div className="mx_RightPanel_headerButton_highlight"></div>; + } + } + + var membersBadge; + if ((this.state.phase == this.Phase.MemberList || this.state.phase === this.Phase.MemberInfo) && this.props.roomId) { + var cli = MatrixClientPeg.get(); + var room = cli.getRoom(this.props.roomId); + if (room) { + membersBadge = <div className="mx_RightPanel_headerButton_badge">{ room.getJoinedMembers().length }</div>; + } + } + if (this.props.roomId) { buttonGroup = <div className="mx_RightPanel_headerButtonGroup"> - <div className="mx_RightPanel_headerButton mx_RightPanel_filebutton"> - <img src="img/file.png" width="32" height="32" title="Files" alt="Files"/> - </div> <div className="mx_RightPanel_headerButton" onClick={ this.onMemberListButtonClick }> - <img src="img/members.png" width="32" height="32" title="Members" alt="Members"/> + <img src="img/members.png" width="17" height="22" title="Members" alt="Members"/> + { membersBadge } + { membersHighlight } + </div> + <div className="mx_RightPanel_headerButton mx_RightPanel_filebutton"> + <img src="img/files.png" width="17" height="22" title="Files" alt="Files"/> + { filesHighlight } </div> </div>; - if (!this.props.collapsed && this.state.phase == this.Phase.MemberList) { - panel = <MemberList roomId={this.props.roomId} key={this.props.roomId} /> + if (!this.props.collapsed) { + if(this.state.phase == this.Phase.MemberList) { + panel = <MemberList roomId={this.props.roomId} key={this.props.roomId} /> + } + else if(this.state.phase == this.Phase.MemberInfo) { + var MemberInfo = sdk.getComponent('molecules.MemberInfo'); + panel = <MemberInfo roomId={this.props.roomId} member={this.state.member} key={this.props.roomId} /> + } } + } var classes = "mx_RightPanel"; diff --git a/src/skins/vector/views/organisms/RoomDirectory.js b/src/skins/vector/views/organisms/RoomDirectory.js index 5f5717c8..90c5dd57 100644 --- a/src/skins/vector/views/organisms/RoomDirectory.js +++ b/src/skins/vector/views/organisms/RoomDirectory.js @@ -69,6 +69,7 @@ module.exports = React.createClass({ }); }, function(err) { console.error("Failed to join room: %s", JSON.stringify(err)); + var ErrorDialog = sdk.getComponent("organisms.ErrorDialog"); Modal.createDialog(ErrorDialog, { title: "Failed to join room", description: err.message diff --git a/src/skins/vector/views/organisms/RoomList.js b/src/skins/vector/views/organisms/RoomList.js index dd8840e5..dc958a4e 100644 --- a/src/skins/vector/views/organisms/RoomList.js +++ b/src/skins/vector/views/organisms/RoomList.js @@ -41,22 +41,38 @@ module.exports = React.createClass({ callElement = <CallView className="mx_MatrixChat_callView"/> } - var recentsLabel = this.props.collapsed ? - <img style={{cursor: 'pointer'}} onClick={ this.onShowClick } src="img/menu.png" width="27" height="20" alt=">"/> : - "Recents"; + 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> + } return ( <div className="mx_RoomList" onScroll={this._repositionTooltip}> - {callElement} - <h2 className="mx_RoomList_favourites_label">Favourites</h2> + { expandButton } + { callElement } + <h2 className="mx_RoomList_favouritesLabel">Favourites</h2> <RoomDropTarget text="Drop here to favourite"/> - <h2 className="mx_RoomList_recents_label">{ recentsLabel }</h2> + { invites } + + <h2 className="mx_RoomList_recentsLabel">{ recentsLabel }</h2> <div className="mx_RoomList_recents"> - {this.makeRoomTiles()} + {this.makeRoomTiles(this.state.roomList, false)} </div> - <h2 className="mx_RoomList_archive_label">Archive</h2> + <h2 className="mx_RoomList_archiveLabel">Archive</h2> <RoomDropTarget text="Drop here to archive"/> </div> ); diff --git a/src/skins/vector/views/organisms/RoomView.js b/src/skins/vector/views/organisms/RoomView.js index f62eb3c3..0a2d2411 100644 --- a/src/skins/vector/views/organisms/RoomView.js +++ b/src/skins/vector/views/organisms/RoomView.js @@ -63,6 +63,10 @@ module.exports = React.createClass({ this.setState(this.getInitialState()); }, + onSearchClick: function() { + this.setState({ searching: true }); + }, + onConferenceNotificationClick: function() { dis.dispatch({ action: 'place_call', @@ -89,6 +93,7 @@ module.exports = React.createClass({ var MessageComposer = sdk.getComponent('molecules.MessageComposer'); var CallView = sdk.getComponent("molecules.voip.CallView"); var RoomSettings = sdk.getComponent("molecules.RoomSettings"); + var SearchBar = sdk.getComponent("molecules.SearchBar"); if (!this.state.room) { if (this.props.roomId) { @@ -159,8 +164,8 @@ module.exports = React.createClass({ <div className="mx_RoomView_uploadProgressOuter"> <div className="mx_RoomView_uploadProgressInner" style={innerProgressStyle}></div> </div> - <img className="mx_RoomView_uploadIcon" src="img/fileicon.png" width="40" height="40"/> - <img className="mx_RoomView_uploadCancel" src="img/cancel.png" width="40" height="40"/> + <img className="mx_RoomView_uploadIcon" src="img/fileicon.png" width="17" height="22"/> + <img className="mx_RoomView_uploadCancel" src="img/cancel.png" width="18" height="18"/> <div className="mx_RoomView_uploadBytes"> { uploadedSize } / { totalSize } </div> @@ -175,7 +180,7 @@ module.exports = React.createClass({ if (unreadMsgs) { statusBar = ( <div className="mx_RoomView_unreadMessagesBar" onClick={ this.scrollToBottom }> - <img src="img/newmessages.png" width="10" height="12" alt=""/> + <img src="img/newmessages.png" width="24" height="24" alt=""/> {unreadMsgs} </div> ); @@ -183,19 +188,22 @@ module.exports = React.createClass({ else if (typingString) { statusBar = ( <div className="mx_RoomView_typingBar"> - <img src="img/typing.png" width="40" height="40" alt=""/> + <div className="mx_RoomView_typingImage">...</div> {typingString} </div> ); } } - var roomEdit = null; + var aux = null; if (this.state.editingRoomSettings) { - roomEdit = <RoomSettings ref="room_settings" onSaveClick={this.onSaveClick} room={this.state.room} />; + aux = <RoomSettings ref="room_settings" onSaveClick={this.onSaveClick} room={this.state.room} />; } - if (this.state.uploadingRoomSettings) { - roomEdit = <Loader/>; + else if (this.state.uploadingRoomSettings) { + aux = <Loader/>; + } + else if (this.state.searching) { + aux = <SearchBar ref="search_bar" onCancelClick={this.onCancelClick} onSearch={this.onSearch}/>; } var conferenceCallNotification = null; @@ -211,7 +219,7 @@ module.exports = React.createClass({ if (this.state.draggingFile) { fileDropTarget = <div className="mx_RoomView_fileDropTarget"> <div className="mx_RoomView_fileDropTargetLabel"> - <img src="img/upload-big.png" width="46" height="61" alt="Drop File Here"/><br/> + <img src="img/upload-big.png" width="43" height="57" alt="Drop File Here"/><br/> Drop File Here </div> </div>; @@ -219,12 +227,12 @@ module.exports = React.createClass({ return ( <div className="mx_RoomView"> - <RoomHeader ref="header" room={this.state.room} editing={this.state.editingRoomSettings} + <RoomHeader ref="header" room={this.state.room} editing={this.state.editingRoomSettings} onSearchClick={this.onSearchClick} onSettingsClick={this.onSettingsClick} onSaveClick={this.onSaveClick} onCancelClick={this.onCancelClick} /> <div className="mx_RoomView_auxPanel"> <CallView room={this.state.room}/> { conferenceCallNotification } - { roomEdit } + { aux } </div> <div ref="messageWrapper" className="mx_RoomView_messagePanel" onScroll={ this.onMessageListScroll }> <div className="mx_RoomView_messageListWrapper"> @@ -238,6 +246,7 @@ module.exports = React.createClass({ </div> <div className="mx_RoomView_statusArea"> <div className="mx_RoomView_statusAreaBox"> + <div className="mx_RoomView_statusAreaBox_line"></div> {statusBar} </div> </div> diff --git a/src/skins/vector/views/organisms/ViewSource.js b/src/skins/vector/views/organisms/ViewSource.js index a00cfc85..371223d4 100644 --- a/src/skins/vector/views/organisms/ViewSource.js +++ b/src/skins/vector/views/organisms/ViewSource.js @@ -21,6 +21,26 @@ var React = require('react'); module.exports = React.createClass({ displayName: 'ViewSource', + propTypes: { + onFinished: React.PropTypes.func.isRequired + }, + + componentDidMount: function() { + document.addEventListener("keydown", this.onKeyDown); + }, + + componentWillUnmount: function() { + document.removeEventListener("keydown", this.onKeyDown); + }, + + onKeyDown: function(ev) { + if (ev.keyCode == 27) { // escape + ev.stopPropagation(); + ev.preventDefault(); + this.props.onFinished(); + } + }, + render: function() { return ( <div className="mx_ViewSource"> diff --git a/vector/index.html b/vector/index.html index 2985994b..d893523e 100644 --- a/vector/index.html +++ b/vector/index.html @@ -3,7 +3,7 @@ <head> <meta charset="utf-8"> <title>Vector</title> - <link href='fonts/Lato.css' rel='stylesheet' type='text/css'> + <link href='fonts/MyriadPro.css' rel='stylesheet' type='text/css'> <link rel="apple-touch-icon" sizes="57x57" href="/icons/apple-touch-icon-57x57.png"> <link rel="apple-touch-icon" sizes="60x60" href="/icons/apple-touch-icon-60x60.png"> <link rel="apple-touch-icon" sizes="72x72" href="/icons/apple-touch-icon-72x72.png">