diff --git a/.gitignore b/.gitignore index 13466ce843..7cbd6fc236 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ node_modules vector/bundle.* lib +.DS_Store +key.pem +cert.pem +build diff --git a/.modernizr.json b/.modernizr.json new file mode 100644 index 0000000000..29e620a5ba --- /dev/null +++ b/.modernizr.json @@ -0,0 +1,14 @@ +{ + "minify": true, + "classPrefix": "modernizr_", + "options": [ + "setClasses" + ], + "feature-detects": [ + "test/css/displaytable", + "test/css/flexbox", + "test/es5/specification", + "test/css/objectfit", + "test/storage/localstorage" + ] +} \ No newline at end of file diff --git a/README.md b/README.md index a15917a475..c834e01379 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 cab45e836d..eb9c3aff95 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "style": "bundle.css", "scripts": { "reskindex": "reskindex vector -h src/skins/vector/header", + "build:modernizr": "modernizr -c .modernizr.json -d src/vector/modernizr.js", "build:css": "catw \"src/skins/vector/css/**/*.css\" -o vector/bundle.css -c uglifycss --no-watch", "build:compile": "babel --source-maps -d lib src", "build:bundle": "NODE_ENV=production webpack -p lib/vector/index.js vector/bundle.js", @@ -27,11 +28,13 @@ "filesize": "^3.1.2", "flux": "~2.0.3", "linkifyjs": "^2.0.0-beta.4", + "modernizr": "^3.1.0", "matrix-js-sdk": "^0.3.0", "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 0000000000..fe363586ab --- /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 151a6ca278..964a26481a 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 ( ); }); diff --git a/src/controllers/organisms/RoomView.js b/src/controllers/organisms/RoomView.js index ff36d4a13e..746dd98ac3 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, } }, @@ -361,6 +364,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'); @@ -369,10 +407,40 @@ 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(
  • ); // 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.haveTileForEvent(mxEv2)) { + ret.push(
  • ); + } + } + if (EventTile.haveTileForEvent(mxEv)) { + ret.push(
  • ); + } + if (resultList[i].context.events_after[0]) { + var mxEv2 = new Matrix.MatrixEvent(resultList[i].context.events_after[0]); + if (EventTile.haveTileForEvent(mxEv2)) { + ret.push(
  • ); + } + } + } + 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]; - if (!EventTile.supportsEventType(mxEv.getType())) { + if (!EventTile.haveTileForEvent(mxEv)) { continue; } diff --git a/src/skins/vector/css/atoms/ImageView.css b/src/skins/vector/css/atoms/ImageView.css new file mode 100644 index 0000000000..22ef343bee --- /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 6422df798e..fc5fd60d2f 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 93012c0f11..a68d190d6a 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 fbc2db207e..7d8ee30294 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 0000000000..d18fdc809c --- /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 1cd2fa465f..eb59711e81 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 10e5b50b60..94f1c9198a 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 0e9c3ca144..0a0db62ea6 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 5b117e41b8..96a9d1db48 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 52c48a795f..6471a86cb6 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 faae142abf..cfeaeaee0c 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 af4934ee2a..44e122762a 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 f7adb6189f..2eeda2417f 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 3a66f0e906..a669c5b2d1 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 511fc94faf..f2c1daadb3 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 f45970febe..604c6a56f1 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 0000000000..b116674bc5 --- /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 2c57a3273a..24b24cc20a 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 d6b1765c3f..578c79e6ea 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 0e7e077e4b..67f00c358d 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 aab0def49a..df699e6490 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 30ef47264a..bf473a4489 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 21985a257d..f53f055657 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 21cb781227..34ebd1dbfb 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 2aab203a4c..d564b08629 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 b69399b72b..2b0aca3d0f 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 ae61ae5821..f932c9a468 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/CompatibilityPage.css b/src/skins/vector/css/pages/CompatibilityPage.css new file mode 100644 index 0000000000..f3f032c975 --- /dev/null +++ b/src/skins/vector/css/pages/CompatibilityPage.css @@ -0,0 +1,19 @@ +.mx_CompatibilityPage { + width: 100%; + height: 100%; + background-color: #e55; +} + +.mx_CompatibilityPage_box { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: auto; + width: 500px; + height: 300px; + border: 1px solid; + padding: 10px; + background-color: #fcc; +} \ No newline at end of file diff --git a/src/skins/vector/css/pages/MatrixChat.css b/src/skins/vector/css/pages/MatrixChat.css index 6fa0415af7..f649aa2454 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 0000000000..f30c591f91 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 0000000000..db605a6c73 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 0000000000..9a133bcdec 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 0000000000..5a95583f82 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 0000000000..2a80f65dbe 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 0000000000..833e68281f --- /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 0000000000..de1ea60d54 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 0000000000..a7805e0596 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 0000000000..87dcfd41a8 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 c963a4b7b3..2bda8ff5bf 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 12abcc264b..efb0065de9 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 1fe5d347db..18a4684e47 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 3df8655bcf..81236f91bc 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 247b0030cd..b7307a11c7 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 bbcb16a4b7..03cab69c4a 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 2686885f79..6f373d3f3d 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 be277a1c8a..af018efa6d 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 0000000000..83932267f8 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 0000000000..cbbd289dcf 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 6526f2362e..b5e5875768 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 e6dbeb17fd..a22156ab21 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 d2c99855d0..2f98d29048 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 0000000000..8931cba75f 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 3be13bc712..cb2e0a62d0 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 445a3909e4..264b3c9bc3 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 0000000000..8ba2310a06 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 7754f58717..c11c0c452d 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 428501f00f..7457bcd0f1 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 8dba10cf30..e715656c0e 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 676348c04c..a842f7c855 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 ( - +
    +
    +
    +
    + +
    +
    +
    +
    +
    + { this.props.mxEvent.getContent().body } +
    +
    + Uploaded on { DateUtils.formatDate(new Date(this.props.mxEvent.getTs())) } by { this.props.mxEvent.getSender() } +
    + +
    + Download this file
    + ({ filesize(this.props.mxEvent.getContent().info.size) }{ res }) +
    +
    + +
    + Redact +
    +
    +
    +
    +
    +
    +
    +
    +
    ); } }); diff --git a/src/skins/vector/views/atoms/MessageTimestamp.js b/src/skins/vector/views/atoms/MessageTimestamp.js index 98cfe4a10b..5795e55657 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 ( - { this.formatDate(date) } + { DateUtils.formatDate(date) } ); }, diff --git a/src/skins/vector/views/atoms/RoomAvatar.js b/src/skins/vector/views/atoms/RoomAvatar.js index 3b5d463498..a1d87f7fa4 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 2644769ccd..8c28058d10 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 (
    - +
    { label }
    diff --git a/src/skins/vector/views/molecules/EventTile.js b/src/skins/vector/views/molecules/EventTile.js index 389b8b4012..c5cb81951b 100644 --- a/src/skins/vector/views/molecules/EventTile.js +++ b/src/skins/vector/views/molecules/EventTile.js @@ -24,12 +24,15 @@ var sdk = require('matrix-react-sdk') var EventTileController = require('matrix-react-sdk/lib/controllers/molecules/EventTile') var ContextualMenu = require('../../../../ContextualMenu'); +var TextForEvent = require('matrix-react-sdk/lib/TextForEvent'); + var eventTileTypes = { 'm.room.message': 'molecules.MessageTile', 'm.room.member' : 'molecules.EventAsTextTile', 'm.call.invite' : 'molecules.EventAsTextTile', 'm.call.answer' : 'molecules.EventAsTextTile', 'm.call.hangup' : 'molecules.EventAsTextTile', + 'm.room.name' : 'molecules.EventAsTextTile', 'm.room.topic' : 'molecules.EventAsTextTile', }; @@ -38,8 +41,13 @@ module.exports = React.createClass({ mixins: [EventTileController], statics: { - supportsEventType: function(et) { - return eventTileTypes[et] !== undefined; + haveTileForEvent: function(e) { + if (eventTileTypes[e.getType()] == undefined) return false; + if (eventTileTypes[e.getType()] == 'molecules.EventAsTextTile') { + return TextForEvent.textForEvent(e) !== ''; + } else { + return true; + } } }, @@ -76,7 +84,7 @@ module.exports = React.createClass({ // This shouldn't happen: the caller should check we support this type // before trying to instantiate us if (!EventTileType) { - return null; + throw new Error("Event type not supported"); } var classes = classNames({ @@ -88,12 +96,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 = var editButton = ( ); @@ -108,7 +117,7 @@ module.exports = React.createClass({ if (this.props.mxEvent.sender) { avatar = (
    - +
    ); } @@ -120,10 +129,10 @@ module.exports = React.createClass({
    { avatar } { sender } -
    +
    { timestamp } { editButton } - +
    ); diff --git a/src/skins/vector/views/molecules/MImageTile.js b/src/skins/vector/views/molecules/MImageTile.js index ed61a39019..0667dabd10 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 ( - {content.body} + {content.body}
    diff --git a/src/skins/vector/views/molecules/MNoticeTile.js b/src/skins/vector/views/molecules/MNoticeTile.js index aa88612784..a0cedb1ddd 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 or whatever will break the HTML badly. + bodyList.push(); + bodyList.push(); + lastOffset = offset + safeSearchTerm.length; + } + bodyList.push(); + } + else { + while ((offset = originalBody.indexOf(this.props.searchTerm, lastOffset)) >= 0) { + bodyList.push({ originalBody.substring(lastOffset, offset) }); + bodyList.push({ this.props.searchTerm }); + lastOffset = offset + this.props.searchTerm.length; + } + bodyList.push({ originalBody.substring(lastOffset) }); + } + body = bodyList; + } + else { + if (content.format === "org.matrix.custom.html") { + var safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams); + body = ; + } + else { + body = originalBody; + } + } + return ( - {content.body} + { body } ); }, diff --git a/src/skins/vector/views/molecules/MTextTile.js b/src/skins/vector/views/molecules/MTextTile.js index 50555f949f..12bafa37ba 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 or whatever will break the HTML badly. + bodyList.push(); + bodyList.push(); + lastOffset = offset + safeSearchTerm.length; + } + bodyList.push(); + } + else { + while ((offset = originalBody.indexOf(this.props.searchTerm, lastOffset)) >= 0) { + bodyList.push({ originalBody.substring(lastOffset, offset) }); + bodyList.push({ this.props.searchTerm }); + lastOffset = offset + this.props.searchTerm.length; + } + bodyList.push({ originalBody.substring(lastOffset) }); + } + body = bodyList; + } + else { + if (content.format === "org.matrix.custom.html") { + var safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams); + body = ; + } + else { + body = originalBody; + } + } + return ( - {content.body} + { body } ); }, diff --git a/src/skins/vector/views/molecules/MemberInfo.js b/src/skins/vector/views/molecules/MemberInfo.js index a2a3874ac7..5f8e806d24 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 =
    Leave room
    ; + interactButton =
    Leave room
    ; } else { - interactButton =
    Start chat
    ; + interactButton =
    Start chat
    ; } if (this.state.creatingRoom) { @@ -40,36 +51,50 @@ module.exports = React.createClass({ } if (this.state.can.kick) { - kickButton =
    + kickButton =
    Kick
    ; } if (this.state.can.ban) { - banButton =
    + banButton =
    Ban
    ; } if (this.state.can.mute) { var muteLabel = this.state.muted ? "Unmute" : "Mute"; - muteButton =
    + muteButton =
    {muteLabel}
    ; } if (this.state.can.modifyLevel) { var giveOpLabel = this.state.isTargetMod ? "Revoke Mod" : "Make Mod"; - giveModButton =
    + giveModButton =
    {giveOpLabel}
    } + var MemberAvatar = sdk.getComponent('atoms.MemberAvatar'); return ( -
    - {interactButton} - {muteButton} - {kickButton} - {banButton} - {giveModButton} - {spinner} +
    + +
    + +
    +

    { this.props.member.name }

    +
    + { this.props.member.userId } +
    +
    + power: { this.props.member.powerLevelNorm }% +
    +
    + {interactButton} + {muteButton} + {kickButton} + {banButton} + {giveModButton} + {spinner} +
    ); } diff --git a/src/skins/vector/views/molecules/MemberTile.js b/src/skins/vector/views/molecules/MemberTile.js index 4c34fcb7a7..25ba3db94c 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 = ; - } + // if (this.props.member && this.props.member.powerLevelNorm > 0) { + // var img = "img/p/p" + Math.floor(20 * this.props.member.powerLevelNorm / 100) + ".png"; + // power = ; + // } 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 ? : null; + //var leave = isMyUser ? : 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 =
    - { leave } -
    { this.props.member.userId }
    + +
    { name }
    { presence }
    } @@ -177,7 +170,7 @@ module.exports = React.createClass({ return (
    - + { power }
    { nameEl } diff --git a/src/skins/vector/views/molecules/MessageComposer.js b/src/skins/vector/views/molecules/MessageComposer.js index c94cade5fb..25f69bda3b 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({
    - +
    -