diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles index 0b4266c0b5..30cc55377c 100644 --- a/.eslintignore.errorfiles +++ b/.eslintignore.errorfiles @@ -1,8 +1,5 @@ # autogenerated file: run scripts/generate-eslint-error-ignore-file to update. -src/autocomplete/AutocompleteProvider.js -src/autocomplete/Autocompleter.js -src/autocomplete/UserProvider.js src/component-index.js src/components/structures/BottomLeftMenu.js src/components/structures/CompatibilityPage.js @@ -18,7 +15,6 @@ src/components/structures/ScrollPanel.js src/components/structures/SearchBox.js src/components/structures/TimelinePanel.js src/components/structures/UploadBar.js -src/components/structures/UserSettings.js src/components/views/avatars/BaseAvatar.js src/components/views/avatars/MemberAvatar.js src/components/views/create_room/RoomAlias.js @@ -52,9 +48,7 @@ src/components/views/rooms/LinkPreviewWidget.js src/components/views/rooms/MemberDeviceInfo.js src/components/views/rooms/MemberInfo.js src/components/views/rooms/MemberList.js -src/components/views/rooms/MemberTile.js src/components/views/rooms/MessageComposer.js -src/components/views/rooms/MessageComposerInput.js src/components/views/rooms/PinnedEventTile.js src/components/views/rooms/RoomList.js src/components/views/rooms/RoomPreviewBar.js diff --git a/.travis.yml b/.travis.yml index 0def6d50f7..0746cc0dff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,8 +14,22 @@ node_js: addons: chrome: stable install: - - npm install -# install synapse prerequisites for end to end tests - - sudo apt-get install build-essential python2.7-dev libffi-dev python-pip python-setuptools sqlite3 libssl-dev python-virtualenv libjpeg-dev libxslt1-dev -script: - ./scripts/travis.sh + - ./scripts/travis/install-deps.sh +matrix: + include: + - name: Linting Checks + script: + # run the linter, but exclude any files known to have errors or warnings. + - npm run lintwithexclusions + - name: End-to-End Tests + if: branch = develop + install: + - sudo apt-get install build-essential python2.7-dev libffi-dev python-pip python-setuptools sqlite3 libssl-dev python-virtualenv libjpeg-dev libxslt1-dev + script: + - ./scripts/travis/end-to-end-tests.sh + - name: Unit Tests + script: + - ./scripts/travis/unit-tests.sh + - name: Riot-web Unit Tests + script: + - ./scripts/travis/riot-unit-tests.sh diff --git a/README.md b/README.md index ec95fbd132..c70f68902b 100644 --- a/README.md +++ b/README.md @@ -127,3 +127,36 @@ Github Issues All issues should be filed under https://github.com/vector-im/riot-web/issues for now. + +Development +=========== + +Ensure you have the latest stable Node JS runtime installed (v8.x is the best choice). Then check out +the code and pull in dependencies: + +```bash +git clone https://github.com/matrix-org/matrix-react-sdk.git +cd matrix-react-sdk +git checkout develop +npm install +``` + +`matrix-react-sdk` depends on `matrix-js-sdk`. To make use of changes in the +latter and to ensure tests run against the develop branch of `matrix-js-sdk`, +you should run the following which will sync changes from the JS sdk here. + +```bash +npm link ../matrix-js-sdk +``` + +Command assumes a checked out and installed `matrix-js-sdk` folder in parent +folder. + +Running tests +============= + +Ensure you've followed the above development instructions and then: + +```bash +npm run test +``` diff --git a/package.json b/package.json index 6dc9a6bfcf..7b55a09948 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "start:init": "babel src -d lib --source-maps --copy-files", "lint": "eslint src/", "lintall": "eslint src/ test/", - "lintwithexclusions": "eslint --max-warnings 20 --ignore-path .eslintignore.errorfiles src test", + "lintwithexclusions": "eslint --max-warnings 18 --ignore-path .eslintignore.errorfiles src test", "clean": "rimraf lib", "prepublish": "npm run clean && npm run build && git rev-parse HEAD > git-revision.txt", "test": "karma start --single-run=true --browsers ChromeHeadless", @@ -118,7 +118,7 @@ "babel-preset-react": "^6.24.1", "chokidar": "^1.6.1", "concurrently": "^4.0.1", - "eslint": "^5.8.0", + "eslint": "^5.12.0", "eslint-config-google": "^0.7.1", "eslint-plugin-babel": "^5.2.1", "eslint-plugin-flowtype": "^2.30.0", diff --git a/res/css/_common.scss b/res/css/_common.scss index 97ae5412e1..bec4c02c18 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -30,8 +30,6 @@ body { color: $primary-fg-color; border: 0px; margin: 0px; - /* This should render the fonts the same accross browsers */ - -webkit-font-smoothing: subpixel-antialiased; } .error, .warning { @@ -49,20 +47,14 @@ h2 { a:hover, a:link, a:visited { - color: $accent-color; + color: $accent-color-alt; } input[type=text], input[type=password], textarea { background-color: transparent; - color: $primary-fg-color; -} - -input[type=text].error, input[type=password].error { - border: 1px solid $warning-color; } input[type=text]:focus, input[type=password]:focus, textarea:focus { - border: 1px solid $accent-color; outline: none; box-shadow: none; } @@ -83,11 +75,6 @@ textarea { transition: opacity 0.2s ease-in-out; } -.mx_fadable.mx_fadable_faded { - opacity: 0.3; - pointer-events: none; -} - /* XXX: critical hack to GeminiScrollbar to allow them to work in FF 42 and Chrome 48. Stop the scrollbar view from pushing out the container's overall sizing, which causes flexbox to adapt to the new size and cause the view to keep growing. @@ -314,7 +301,7 @@ textarea { } .mx_textButton { - @mixin mx_DialogButton_small; + @mixin mx_DialogButton_small; } .mx_textButton:hover { diff --git a/res/css/_components.scss b/res/css/_components.scss index 5633b7ec8f..8f6f4d5936 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -1,6 +1,6 @@ // autogenerated by rethemendex.sh @import "./_common.scss"; -@import "./_fonts.scss"; +@import "./structures/_AutoHideScrollbar.scss"; @import "./structures/_CompatibilityPage.scss"; @import "./structures/_ContextualMenu.scss"; @import "./structures/_CreateRoom.scss"; @@ -19,6 +19,7 @@ @import "./structures/_RoomView.scss"; @import "./structures/_SearchBox.scss"; @import "./structures/_TagPanel.scss"; +@import "./structures/_TopLeftMenuButton.scss"; @import "./structures/_UploadBar.scss"; @import "./structures/_UserSettings.scss"; @import "./structures/_ViewSource.scss"; @@ -29,6 +30,7 @@ @import "./views/context_menus/_RoomTileContextMenu.scss"; @import "./views/context_menus/_StatusMessageContextMenu.scss"; @import "./views/context_menus/_TagTileContextMenu.scss"; +@import "./views/context_menus/_TopLeftMenu.scss"; @import "./views/dialogs/_BugReportDialog.scss"; @import "./views/dialogs/_ChangelogDialog.scss"; @import "./views/dialogs/_ChatCreateOrReuseChatDialog.scss"; @@ -48,7 +50,7 @@ @import "./views/dialogs/_ShareDialog.scss"; @import "./views/dialogs/_UnknownDeviceDialog.scss"; @import "./views/dialogs/keybackup/_CreateKeyBackupDialog.scss"; -@import "./views/dialogs/keybackup/_NewRecoveryMethodDialog.scss"; +@import "./views/dialogs/keybackup/_KeyBackupFailedDialog.scss"; @import "./views/dialogs/keybackup/_RestoreKeyBackupDialog.scss"; @import "./views/directory/_NetworkDropdown.scss"; @import "./views/elements/_AccessibleButton.scss"; @@ -62,6 +64,7 @@ @import "./views/elements/_MemberEventListSummary.scss"; @import "./views/elements/_ProgressBar.scss"; @import "./views/elements/_ReplyThread.scss"; +@import "./views/elements/_ResizeHandle.scss"; @import "./views/elements/_RichText.scss"; @import "./views/elements/_RoleButton.scss"; @import "./views/elements/_Spinner.scss"; @@ -88,6 +91,7 @@ @import "./views/messages/_UnknownBody.scss"; @import "./views/rooms/_AppsDrawer.scss"; @import "./views/rooms/_Autocomplete.scss"; +@import "./views/rooms/_AuxPanel.scss"; @import "./views/rooms/_EntityTile.scss"; @import "./views/rooms/_EventTile.scss"; @import "./views/rooms/_LinkPreviewWidget.scss"; @@ -112,6 +116,7 @@ @import "./views/rooms/_SearchableEntityList.scss"; @import "./views/rooms/_Stickers.scss"; @import "./views/rooms/_TopUnreadMessagesBar.scss"; +@import "./views/rooms/_WhoIsTypingTile.scss"; @import "./views/settings/_DevicesPanel.scss"; @import "./views/settings/_IntegrationsManager.scss"; @import "./views/settings/_KeyBackupPanel.scss"; diff --git a/res/css/structures/_AutoHideScrollbar.scss b/res/css/structures/_AutoHideScrollbar.scss new file mode 100644 index 0000000000..60aea7ce8f --- /dev/null +++ b/res/css/structures/_AutoHideScrollbar.scss @@ -0,0 +1,66 @@ +/* +Copyright 2018 New Vector 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. +*/ + +/* +1. for browsers that support native overlay auto-hiding scrollbars +*/ +.mx_AutoHideScrollbar { + overflow-x: hidden; + overflow-y: auto; + -ms-overflow-style: -ms-autohiding-scrollbar; +} +/* +2. webkit also supports overflow:overlay where the scrollbars don't take any space +in the layout but they don't autohide, so do that only on hover +*/ +body.mx_scrollbar_overlay_noautohide .mx_AutoHideScrollbar { + overflow-y: hidden; +} + +body.mx_scrollbar_overlay_noautohide .mx_AutoHideScrollbar:hover { + overflow-y: overlay; +} +/* +3. as a last fallback, compensate for the scrollbar taking up space in the layout +by having giving the child element (.mx_AutoHideScrollbar_offset) a +negative right margin of the width of the scrollbar when the container +is overflowing. This is what Firefox ends up using. Overflow is detected +in javascript, and adds the mx_AutoHideScrollbar_overflow class to the container. +This only works in Firefox, which should be fine as this fallback is only needed there. +*/ +body.mx_scrollbar_nooverlay { + .mx_AutoHideScrollbar { + overflow-y: hidden; + } + + .mx_AutoHideScrollbar:hover { + overflow-y: auto; + } + + /* + offset scrollbar width with negative margin-right + + include before and after psuedo-elements here so they can + be used to do something interesting like scroll-indicating + gradients (see IndicatorScrollBar) + */ + .mx_AutoHideScrollbar:hover.mx_AutoHideScrollbar_overflow > .mx_AutoHideScrollbar_offset, + .mx_AutoHideScrollbar:hover.mx_AutoHideScrollbar_overflow::before, + .mx_AutoHideScrollbar:hover.mx_AutoHideScrollbar_overflow::after + { + margin-right: calc(-1 * var(--scrollbar-width)); + } +} diff --git a/res/css/structures/_ContextualMenu.scss b/res/css/structures/_ContextualMenu.scss index 7474c3d107..a01cd896a8 100644 --- a/res/css/structures/_ContextualMenu.scss +++ b/res/css/structures/_ContextualMenu.scss @@ -30,12 +30,11 @@ limitations under the License. } .mx_ContextualMenu { - border: solid 1px $menu-border-color; border-radius: 4px; + box-shadow: 4px 4px 12px 0 rgba(118, 131, 156, 0.6);; background-color: $menu-bg-color; color: $primary-fg-color; position: absolute; - padding: 6px; font-size: 14px; z-index: 5001; } @@ -44,6 +43,10 @@ limitations under the License. right: 8px; } +.mx_ContextualMenu_noChevron { + border-radius: unset !important; +} + .mx_ContextualMenu_chevron_right { position: absolute; right: -8px; @@ -51,7 +54,7 @@ limitations under the License. width: 0; height: 0; border-top: 8px solid transparent; - border-left: 8px solid $menu-border-color; + border-left: 8px solid $menu-bg-color; border-bottom: 8px solid transparent; } @@ -78,7 +81,7 @@ limitations under the License. width: 0; height: 0; border-top: 8px solid transparent; - border-right: 8px solid $menu-border-color; + border-right: 8px solid $menu-bg-color; border-bottom: 8px solid transparent; } @@ -105,7 +108,7 @@ limitations under the License. width: 0; height: 0; border-left: 8px solid transparent; - border-bottom: 8px solid $menu-border-color; + border-bottom: 8px solid $menu-bg-color; border-right: 8px solid transparent; } @@ -132,7 +135,7 @@ limitations under the License. width: 0; height: 0; border-left: 8px solid transparent; - border-top: 8px solid $menu-border-color; + border-top: 8px solid $menu-bg-color; border-right: 8px solid transparent; } diff --git a/res/css/structures/_FilePanel.scss b/res/css/structures/_FilePanel.scss index 87dc0aa756..677fa34c6f 100644 --- a/res/css/structures/_FilePanel.scss +++ b/res/css/structures/_FilePanel.scss @@ -16,11 +16,7 @@ limitations under the License. .mx_FilePanel { order: 2; - flex: 1 1 0; - - width: 100%; - overflow-y: auto; } diff --git a/res/css/structures/_GroupView.scss b/res/css/structures/_GroupView.scss index 02e5a948e9..398c51ba91 100644 --- a/res/css/structures/_GroupView.scss +++ b/res/css/structures/_GroupView.scss @@ -15,10 +15,6 @@ limitations under the License. */ .mx_GroupView { - max-width: 960px; - width: 100%; - margin-left: auto; - margin-right: auto; display: flex; flex-direction: column; overflow: hidden; @@ -29,7 +25,6 @@ limitations under the License. } .mx_GroupView_header { - max-width: 960px; min-height: 70px; align-items: center; display: flex; @@ -39,6 +34,8 @@ limitations under the License. .mx_GroupView_header_view { border-bottom: 1px solid $primary-hairline-color; padding-bottom: 0px; + padding-left: 8px; + padding-right: 8px; } .mx_GroupView_header_avatar, .mx_GroupView_header_info { @@ -162,6 +159,11 @@ limitations under the License. line-height: 2em; } +.mx_GroupView > .mx_MainSplit { + flex: 1; + display: flex; +} + .mx_GroupView_body { flex-grow: 1; } diff --git a/res/css/structures/_LeftPanel.scss b/res/css/structures/_LeftPanel.scss index 7cf6dd1119..941417eccc 100644 --- a/res/css/structures/_LeftPanel.scss +++ b/res/css/structures/_LeftPanel.scss @@ -15,32 +15,22 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_LeftPanel { - position: relative; - - display: flex; - flex-direction: column; -} - .mx_LeftPanel_container { display: flex; - /* LeftPanel 235px */ - flex: 0 0 235px; + /* LeftPanel 260px */ + min-width: 260px; + flex: 0 0 auto; } -.mx_LeftPanel_container.mx_LeftPanel_container_hasTagPanel { - /* TagPanel 60px + LeftPanel 235px */ - flex: 0 0 295px; +.mx_LeftPanel_container.collapsed { + min-width: unset; + /* Collapsed LeftPanel 70px */ + flex: 0 0 70px; } -.mx_LeftPanel_container_collapsed { - /* Collapsed LeftPanel 60px */ - flex: 0 0 60px; -} - -.mx_LeftPanel_container_collapsed.mx_LeftPanel_container_hasTagPanel { - /* TagPanel 60px + Collapsed LeftPanel 60px */ - flex: 0 0 120px; +.mx_LeftPanel_container.collapsed.mx_LeftPanel_container_hasTagPanel { + /* TagPanel 70px + Collapsed LeftPanel 70px */ + flex: 0 0 140px; } .mx_LeftPanel_hideButton { @@ -51,8 +41,12 @@ limitations under the License. cursor: pointer; } -.mx_LeftPanel_callView { - +.mx_LeftPanel { + flex: 1; + overflow-x: hidden; + display: flex; + flex-direction: column; + min-height: 0; } .mx_LeftPanel .mx_AppTile_mini { @@ -68,7 +62,7 @@ limitations under the License. z-index: 6; } -.mx_LeftPanel.collapsed .mx_BottomLeftMenu { +.mx_LeftPanel_container.collapsed .mx_BottomLeftMenu { flex: 0 0 160px; margin-bottom: 9px; } @@ -91,12 +85,6 @@ limitations under the License. pointer-events: none; } -.collapsed .mx_RoleButton { - margin-right: 0px ! important; - padding-top: 3px ! important; - padding-bottom: 3px ! important; -} - .mx_BottomLeftMenu_options > div { display: inline-block; } @@ -115,7 +103,7 @@ limitations under the License. margin-right: 0px; } -.mx_LeftPanel.collapsed .mx_BottomLeftMenu_settings { +.mx_LeftPanel_container.collapsed .mx_BottomLeftMenu_settings { float: none; } @@ -124,7 +112,7 @@ limitations under the License. flex: 0 0 50px; } - .mx_LeftPanel.collapsed .mx_BottomLeftMenu { + .mx_LeftPanel_container.collapsed .mx_BottomLeftMenu { flex: 0 0 160px; } diff --git a/res/css/structures/_MatrixChat.scss b/res/css/structures/_MatrixChat.scss index eae1f56180..f2ce7e1d5c 100644 --- a/res/css/structures/_MatrixChat.scss +++ b/res/css/structures/_MatrixChat.scss @@ -54,6 +54,7 @@ limitations under the License. order: 2; flex: 1; + min-height: 0; } .mx_MatrixChat_syncError { @@ -68,26 +69,12 @@ limitations under the License. transform: translateX(-50%); } -.mx_MatrixChat .mx_LeftPanel { - order: 1; - - background-color: $secondary-accent-color; - - flex: 0 0 235px; -} - -.mx_MatrixChat .mx_LeftPanel.collapsed { - flex: 0 0 60px; -} - -.mx_MatrixChat .mx_MatrixChat_middlePanel { - order: 2; - - padding-left: 20px; - padding-right: 22px; +/* not the left panel, and not the resize handle, so the roomview/groupview/... */ +.mx_MatrixChat > :not(.mx_LeftPanel_container):not(.mx_ResizeHandle) { background-color: $primary-bg-color; - flex: 1; + flex: 1 1 0; + min-width: 0; /* Experimental fix for https://github.com/vector-im/vector-web/issues/947 and https://github.com/vector-im/vector-web/issues/946. @@ -96,21 +83,9 @@ limitations under the License. */ overflow-x: auto; - display: flex; - /* To fix https://github.com/vector-im/riot-web/issues/3298 where Safari needed height 100% all the way down to the HomePage. Height does not have to be auto, empirically. */ height: 100%; } - -.mx_MatrixChat .mx_RightPanel { - order: 3; - - flex: 0 0 235px; -} - -.mx_MatrixChat .mx_RightPanel.collapsed { - flex: 0 0 122px; -} diff --git a/res/css/structures/_MyGroups.scss b/res/css/structures/_MyGroups.scss index 6d140721c8..f9433909a5 100644 --- a/res/css/structures/_MyGroups.scss +++ b/res/css/structures/_MyGroups.scss @@ -15,10 +15,6 @@ limitations under the License. */ .mx_MyGroups { - max-width: 960px; - margin-left: auto; - margin-right: auto; - display: flex; flex-direction: column; } @@ -34,6 +30,11 @@ limitations under the License. flex-wrap: wrap; } +.mx_MyGroups > :not(.mx_RoomHeader) { + max-width: 960px; + margin: 40px; +} + .mx_MyGroups_headerCard { flex: 1 0 50%; margin-bottom: 30px; @@ -43,14 +44,31 @@ limitations under the License. } .mx_MyGroups_headerCard .mx_MyGroups_headerCard_button { + flex: 0 0 auto; margin-right: 13px; - height: 50px; + height: 40px; + width: 40px; + border-radius: 20px; + background-color: $roomheader-addroom-color; + position: relative; + + &:before { + background-color: $accent-fg-color; + mask: url('../../img/icons-create-room.svg'); + mask-repeat: no-repeat; + mask-position: center; + mask-size: 80%; + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + } } -.mx_MyGroups_headerCard_button object { - /* Otherwise the SVG object absorbs clicks and the button doesn't work */ - pointer-events: none; -} + + .mx_MyGroups_headerCard_header { font-weight: bold; diff --git a/res/css/structures/_NotificationPanel.scss b/res/css/structures/_NotificationPanel.scss index a899808d57..b171aa3e36 100644 --- a/res/css/structures/_NotificationPanel.scss +++ b/res/css/structures/_NotificationPanel.scss @@ -16,11 +16,7 @@ limitations under the License. .mx_NotificationPanel { order: 2; - flex: 1 1 0; - - width: 100%; - overflow-y: auto; } @@ -97,4 +93,4 @@ limitations under the License. .mx_NotificationPanel .mx_EventTile_content { margin-right: 0px; -} \ No newline at end of file +} diff --git a/res/css/structures/_RightPanel.scss b/res/css/structures/_RightPanel.scss index 554aabfcd1..592eea067e 100644 --- a/res/css/structures/_RightPanel.scss +++ b/res/css/structures/_RightPanel.scss @@ -15,8 +15,10 @@ limitations under the License. */ .mx_RightPanel { + overflow-x: hidden; + flex: 0 0 auto; position: relative; - + min-width: 250px; display: flex; flex-direction: column; } @@ -25,59 +27,57 @@ limitations under the License. order: 1; border-bottom: 1px solid $primary-hairline-color; - margin-right: 20px; - flex: 0 0 70px; + flex: 0 0 52px; } /** Fixme - factor this out with the main header **/ .mx_RightPanel_headerButtonGroup { - margin-top: 6px; + height: 100%; display: flex; - width: 100%; background-color: $primary-bg-color; - margin-left: 0px; + padding: 0 9px; + align-items: center; } .mx_RightPanel_headerButton { cursor: pointer; flex: 0 0 auto; vertical-align: top; - padding-left: 4px; + margin-top: 4px; + padding-left: 5px; padding-right: 5px; text-align: center; position: relative; + border-bottom: 2px solid transparent; } .mx_RightPanel_headerButton object { pointer-events: none; - padding-bottom: 3px; -} - -.mx_RightPanel_headerButton_badgeHighlight .mx_RightPanel_headerButton_badge { - color: $warning-color; } .mx_RightPanel_headerButton_highlight { - width: 25px; - height: 5px; - border-radius: 5px; - background-color: $accent-color; - opacity: 0.2; + border-color: $button-bg-color; } .mx_RightPanel_headerButton_badge { - font-size: 11px; - color: $accent-color; + font-size: 8px; + border-radius: 8px; + color: $accent-fg-color; + background-color: $accent-color; font-weight: bold; - padding-bottom: 2px; + position: absolute; + top: -4px; + left: 20px; + padding: 2px 4px; } .mx_RightPanel_collapsebutton { flex: 1; text-align: right; - margin-top: 20px; + height: 16px; + border: none; } .mx_RightPanel .mx_MemberList, @@ -92,46 +92,3 @@ limitations under the License. order: 2; margin: auto; } - -.mx_RightPanel_footer { - order: 3; - - border-top: 1px solid $primary-hairline-color; - margin-right: 20px; - - flex: 0 0 60px; -} - -.mx_RightPanel_footer .mx_RightPanel_invite { - font-size: 14px; - color: $primary-fg-color; - padding-top: 13px; - padding-left: 5px; - cursor: pointer; - display: flex; - align-items: center; -} - -.collapsed .mx_RightPanel_footer .mx_RightPanel_invite { - display: none; -} - -.mx_RightPanel_invite .mx_RightPanel_icon object { - pointer-events: none; -} - -.mx_RightPanel_invite .mx_RightPanel_message { - padding-left: 10px; - line-height: 18px; -} - -.mx_MatrixChat_useCompactLayout { - .mx_RightPanel_footer { - flex: 0 0 50px; - } - - .mx_RightPanel_footer .mx_RightPanel_invite { - line-height: 25px; - padding-top: 8px; - } -} diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss index 6798f75a14..b7fe19ca89 100644 --- a/res/css/structures/_RoomSubList.scss +++ b/res/css/structures/_RoomSubList.scss @@ -14,235 +14,189 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_RoomSubList { - display: table; - table-layout: fixed; - width: 100%; +/* a word of explanation about the flex-shrink values employed here: + there are 3 priotized categories of screen real-estate grabbing, + each with a flex-shrink difference of 4 order of magnitude, + so they ideally wouldn't affect each other. + lowest category: .mx_RoomSubList + flex-shrink: 10000000 + distribute size of items within the same categery by their size + middle category: .mx_RoomSubList.resized-sized + flex-shrink: 1000 + applied when using the resizer, will have a max-height set to it, + to limit the size + highest category: .mx_RoomSubList.resized-all + flex-shrink: 1 + small flex-shrink value (1), is only added if you can drag the resizer so far + so in practice you can only assign this category if there is enough space. +*/ - background-color: $roomsublist-background; +.mx_RoomSubList { + min-height: 31px; + flex: 0 10000 auto; + display: flex; + flex-direction: column; +} + +.mx_RoomSubList.resized-sized { + /* + flex-basis to 0 so sublists + are not shrinking/growing relative + to their content (as would be the case with auto), + as this intervenes with sizing an item exactly + when not available space is available + in the flex container + */ + flex: 1 1 0; +} + +.mx_RoomSubList_nonEmpty { + min-height: 74px; + + .mx_AutoHideScrollbar_offset { + padding-bottom: 4px; + } +} + +.mx_RoomSubList_hidden { + flex: none !important; } .mx_RoomSubList_labelContainer { - height: 31px; /* mx_RoomSubList_label height including border */ - width: 235px; /* LHS Panel width */ - position: relative; + display: flex; + flex-direction: row; + align-items: center; + flex: 0 0 auto; + margin: 0 16px; + height: 36px; } .mx_RoomSubList_label { - position: relative; + flex: 1; + cursor: pointer; + display: flex; + align-items: center; + padding: 0 6px; +} + +.mx_RoomSubList_label > span { + flex: 1 1 auto; text-transform: uppercase; color: $roomsublist-label-fg-color; - font-weight: 600; + font-weight: 700; font-size: 12px; - width: 203px; /* padding + width = LHS Panel width */ - height: 19px; /* height + padding = 31px = mx_RoomSubList_label height */ - padding-left: 16px; /* gutter */ - padding-right: 16px; /* gutter */ - padding-top: 6px; - padding-bottom: 6px; - cursor: pointer; - background-color: $secondary-accent-color; -} - -.mx_RoomSubList_label.mx_RoomSubList_fixed { - position: fixed; - top: 0; - z-index: 5; - /* pointer-events: none; */ -} - -.collapsed .mx_RoomSubList_label { - height: 17px; - width: 28px; /* collapsed LHS Panel width */ -} - -.collapsed .mx_RoomSubList_labelContainer { - width: 28px; /* collapsed LHS Panel width */ -} - -.mx_RoomSubList_roomCount { - display: inline-block; - font-size: 12px; - font-weight: normal; - color: $accent-color; - padding-left: 5px; - text-transform: none; -} - -.collapsed .mx_RoomSubList_roomCount { - display: none; + margin-left: 8px; } .mx_RoomSubList_badge { - display: inline-block; - min-width: 15px; - height: 15px; - position: absolute; - right: 8px; /*gutter */ - top: 7px; + flex: 0 0 auto; border-radius: 8px; color: $accent-fg-color; font-weight: 600; - font-size: 10px; - text-align: center; - padding-top: 1px; - padding-left: 4px; - padding-right: 4px; - background-color: $accent-color; + font-size: 12px; + padding: 0 5px; + background-color: $roomtile-name-color; } -.mx_RoomSubList_label .mx_RoomSubList_badge:hover { - filter: brightness($focus-brightness); +.mx_RoomSubList_addRoom, .mx_RoomSubList_badge { + margin-left: 7px; } -/* -.collapsed .mx_RoomSubList_badge { - display: none; +.mx_RoomSubList_addRoom { + background-color: $roomheader-addroom-color; + color: $roomsublist-background; + background-image: url('../../img/icons-room-add.svg'); + background-repeat: no-repeat; + background-position: center; + border-radius: 10px; // 16/2 + 2 padding + height: 16px; + flex: 0 0 16px; + background-clip: content-box; } -*/ .mx_RoomSubList_badgeHighlight { background-color: $warning-color; } -/* This is the bottom of the speech bubble */ -.mx_RoomSubList_badgeHighlight:after { - content: ""; - position: absolute; - display: block; - width: 0; - height: 0; - margin-left: 5px; - border-top: 5px solid $warning-color; - border-right: 7px solid transparent; -} - -/* Hide the bottom of speech bubble */ -.collapsed .mx_RoomSubList_badgeHighlight:after { - display: none; -} - .mx_RoomSubList_chevron { pointer-events: none; - position: absolute; - right: 41px; - top: 11px; + background-image: url('../../img/topleft-chevron.svg'); + background-repeat: no-repeat; + transition: transform 0.2s ease-in; + width: 10px; + height: 10px; + background-position: center; + margin-left: 2px; } .mx_RoomSubList_chevronDown { - width: 0; - height: 0; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - border-top: 6px solid $roomsublist-chevron-color; + transform: rotateZ(0deg); } .mx_RoomSubList_chevronUp { - width: 0; - height: 0; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - border-bottom: 6px solid $roomsublist-chevron-color; + transform: rotateZ(180deg); } .mx_RoomSubList_chevronRight { - width: 0; - height: 0; - border-top: 5px solid transparent; - border-left: 6px solid $roomsublist-chevron-color; - border-bottom: 5px solid transparent; + transform: rotateZ(-90deg); } -/* The overflow section */ -.mx_RoomSubList_ellipsis { - display: block; - line-height: 11px; - height: 18px; - position: relative; - cursor: pointer; - font-size: 13px; - - background-color: $secondary-accent-color; +.mx_RoomSubList_scroll { + /* let rooms list grab as much space as it needs (auto), + potentially overflowing and showing a scrollbar */ + flex: 0 1 auto; + padding: 0 8px; } -.collapsed .mx_RoomSubList_ellipsis { - height: 20px; +// overflow indicators +.mx_RoomSubList:not(.resized-all) > .mx_RoomSubList_scroll { + &.mx_IndicatorScrollbar_topOverflow::before, + &.mx_IndicatorScrollbar_bottomOverflow::after { + position: sticky; + left: 0; + right: 0; + height: 30px; + content: ""; + display: block; + z-index: 100; + pointer-events: none; + } + + &.mx_IndicatorScrollbar_topOverflow > .mx_AutoHideScrollbar_offset { + margin-top: -30px; + } + &.mx_IndicatorScrollbar_bottomOverflow > .mx_AutoHideScrollbar_offset { + margin-bottom: -30px; + } + + &.mx_IndicatorScrollbar_topOverflow::before { + top: 0; + background: linear-gradient(to top, rgba(242,245,248,0), rgba(242,245,248,1)); + } + + &.mx_IndicatorScrollbar_bottomOverflow::after { + bottom: 0; + background: linear-gradient(to bottom, rgba(242,245,248,0), rgba(242,245,248,1)); + } } -.mx_RoomSubList_line { - display: inline-block; - width: 159px; - border-top: dotted 2px $accent-color; - vertical-align: middle; +.collapsed { + + .mx_RoomSubList_scroll { + padding: 0; + } + + .mx_RoomSubList_labelContainer { + margin-right: 14px; + margin-left: 2px; + } + + .mx_RoomSubList_addRoom { + margin-left: 3px; + margin-right: 10px; + } + + .mx_RoomSubList_label > span { + display: none; + } } - -.collapsed .mx_RoomSubList_line { - display: none; -} - -.mx_RoomSubList_more { - display: inline-block; - text-transform: uppercase; - font-size: 10px; - font-weight: 600; - text-align: left; - color: $accent-color; - padding-left: 7px; - padding-right: 7px; - padding-left: 7px; - vertical-align: middle; -} - -.collapsed .mx_RoomSubList_more { - display: none; -} - -.mx_RoomSubList_moreBadge { - display: inline-block; - min-width: 15px; - height: 13px; - position: absolute; - right: 8px; /*gutter */ - top: -2px; - border-radius: 8px; - border: solid 1px $accent-color; - color: $accent-fg-color; - font-weight: 600; - font-size: 10px; - text-align: center; - padding-top: 1px; - padding-left: 3px; - padding-right: 3px; - background-color: $primary-bg-color; - vertical-align: middle; -} - -.mx_RoomSubList_moreBadge.mx_RoomSubList_moreBadgeNotify { - background-color: $accent-color; - border: 0; - padding-top: 3px; - padding-left: 4px; - padding-right: 4px; -} - -.mx_RoomSubList_moreBadge.mx_RoomSubList_moreBadgeHighlight { - background-color: $warning-color; - border: 0; - padding-top: 3px; - padding-left: 4px; - padding-right: 4px; -} - -.collapsed .mx_RoomSubList_moreBadge { - position: static; - margin-left: 16px; - margin-top: 2px; -} - -.mx_RoomSubList_ellipsis .mx_RoomSubList_chevronDown { - position: relative; - top: 4px; - left: 2px; -} - - diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 02418f70db..4a84a55cd4 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -16,23 +16,12 @@ limitations under the License. .mx_RoomView { word-wrap: break-word; - position: relative; - display: flex; - width: 100%; - flex-direction: column; } -.mx_RoomView .mx_RoomHeader { - order: 1; - - flex: 0 0 70px; -} - .mx_RoomView_fileDropTarget { min-width: 0px; - max-width: 960px; width: 100%; font-size: 18px; text-align: center; @@ -50,7 +39,7 @@ limitations under the License. border: 2px #e1dddd solid; border-bottom: none; position: absolute; - top: 70px; + top: 52px; bottom: 0px; z-index: 3000; } @@ -63,42 +52,53 @@ limitations under the License. } .mx_RoomView_auxPanel { - order: 2; - min-width: 0px; - max-width: 960px; width: 100%; margin: 0px auto; overflow: auto; - border-bottom: 1px solid $primary-hairline-color; - flex: 0 0 auto; } +.mx_RoomView_auxPanel_fullHeight { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 3000; + background-color: $primary-bg-color; +} + .mx_RoomView_auxPanel_apps { max-width: 1920px ! important; } -.mx_RoomView_body { - order: 3; +.mx_RoomView .mx_MainSplit { flex: 1 1 0; - flex-direction: column; display: flex; } -.mx_RoomView_body .mx_RoomView_topUnreadMessagesBar { - order: 1; +.mx_RoomView_body { + position: relative; //for .mx_RoomView_auxPanel_fullHeight + display: flex; + flex-direction: column; + flex: 1; } -.mx_RoomView_body .mx_RoomView_messagePanel { - order: 2; +.mx_RoomView_body .mx_RoomView_timeline { + /* offset parent for mx_RoomView_topUnreadMessagesBar */ + position: relative; + flex: 1; + display: flex; + flex-direction: column; } -.mx_RoomView_body .mx_RoomView_messagePanelSpinner { - order: 2; - margin: auto; +.mx_RoomView_body { + .mx_RoomView_messagePanel, .mx_RoomView_messagePanelSpinner, .mx_RoomView_messagePanelSearchSpinner{ + order: 2; + } } .mx_RoomView_body .mx_RoomView_statusArea { @@ -114,10 +114,30 @@ limitations under the License. overflow-y: auto; } -.mx_RoomView_messageListWrapper { - max-width: 960px; - margin: auto; +.mx_RoomView_messagePanelSearchSpinner { + flex: 1; + background-image: url('../../img/typing-indicator-2x.gif'); + background-position: center 367px; + background-size: 25px; + background-repeat: no-repeat; + position: relative; +} +.mx_RoomView_messagePanelSearchSpinner:before { + background-color: $greyed-fg-color; + mask: url('../../img/feather-icons/search-input.svg'); + mask-repeat: no-repeat; + mask-position: center; + mask-size: 50px; + content: ''; + position: absolute; + top: 286px; + left: 0; + right: 0; + height: 50px; +} + +.mx_RoomView_messageListWrapper { min-height: 100%; display: flex; @@ -127,8 +147,15 @@ limitations under the License. justify-content: flex-end; } -.mx_RoomView_searchResultsPanel .mx_RoomView_messageListWrapper { - justify-content: flex-start; +.mx_RoomView_searchResultsPanel { + .mx_RoomView_messageListWrapper { + justify-content: flex-start; + } + + a { + text-decoration: none; + color: inherit; + } } .mx_RoomView_empty { @@ -142,9 +169,8 @@ limitations under the License. } .mx_RoomView_MessageList { - width: 100%; list-style-type: none; - padding: 0px; + padding: 18px; } .mx_RoomView_MessageList li { @@ -184,7 +210,6 @@ hr.mx_RoomView_myReadMarker { } .mx_RoomView_statusAreaBox { - max-width: 960px; margin: auto; min-height: 50px; } diff --git a/res/css/structures/_SearchBox.scss b/res/css/structures/_SearchBox.scss index 6f08fd47b2..e559236569 100644 --- a/res/css/structures/_SearchBox.scss +++ b/res/css/structures/_SearchBox.scss @@ -14,55 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_SearchBox { - height: 24px; - margin-left: 16px; - margin-right: 16px; - padding-top: 24px; - padding-bottom: 22px; - - border-bottom: 1px solid $panel-divider-color; - - display: flex; -} - -.mx_SearchBox_searchButton { - margin-right: 10px; - margin-top: 5px; - pointer-events: none; -} - .mx_SearchBox_closeButton { cursor: pointer; - margin-top: -5px; -} - -.mx_SearchBox_search { - flex: 1 1 auto; - width: 0px; - font-family: $font-family; - font-size: 12px; - margin-top: -2px; - height: 24px; - border: 0px ! important; - /* border-bottom: 1px solid rgba(0, 0, 0, 0.1) ! important; */ - border: 0px; -} - -.mx_SearchBox_minimise, -.mx_SearchBox_maximise { - margin-top: 3px; - cursor: pointer; -} - -.mx_SearchBox_minimise { - margin-left: 10px; -} - -.mx_SearchBox_maximise { - margin-left: 9px; -} - -.mx_SearchBox object { - pointer-events: none; + background-image: url('../../img/icons-close.svg'); + background-repeat: no-repeat; + width: 16px; + height: 16px; + background-position: center; + padding: 9px; } diff --git a/res/css/structures/_TagPanel.scss b/res/css/structures/_TagPanel.scss index 415aafd924..77eefc7e10 100644 --- a/res/css/structures/_TagPanel.scss +++ b/res/css/structures/_TagPanel.scss @@ -15,8 +15,9 @@ limitations under the License. */ .mx_TagPanel { - flex: 0 0 60px; - background-color: $tertiary-accent-color; + flex: 0 0 70px; + background-color: $tagpanel-bg-color; + cursor: pointer; display: flex; flex-direction: column; @@ -38,6 +39,8 @@ limitations under the License. display: flex; justify-content: center; align-items: flex-start; + + display: none; } .mx_TagPanel .mx_TagPanel_clearButton object { @@ -50,6 +53,7 @@ limitations under the License. height: 0px; width: 42px; border-bottom: 1px solid $panel-divider-color; + display: none; } .mx_TagPanel .mx_TagPanel_scroller { @@ -60,19 +64,21 @@ limitations under the License. display: flex; flex-direction: column; align-items: center; + margin-top: 5px; height: 100%; } .mx_TagPanel .mx_TagTile { - padding: 6px 3px; - opacity: 0.5; + padding-top: 9px; + padding-bottom: 9px; +// opacity: 0.5; position: relative; } .mx_TagPanel .mx_TagTile:focus, .mx_TagPanel .mx_TagTile:hover, .mx_TagPanel .mx_TagTile.mx_TagTile_selected { - opacity: 1; +// opacity: 1; } .mx_TagPanel .mx_TagTile.mx_TagTile_selected { @@ -83,12 +89,12 @@ limitations under the License. .mx_TagPanel .mx_TagTile.mx_TagTile_selected .mx_TagTile_avatar .mx_BaseAvatar { border: 3px solid $accent-color; background-color: $accent-color; - border-radius: 60px; + border-radius: 40px; /* In case this is a "initial" avatar */ display: block; - height: 35px; - width: 35px; + height: 40px; + width: 40px; } .mx_TagPanel .mx_TagTile.mx_AccessibleButton:focus { @@ -119,11 +125,38 @@ limitations under the License. } .mx_TagPanel_groupsButton { - margin-bottom: 17px; - margin-top: 18px; - height: 25px; + flex: 0; + margin: 17px 0 3px 0; } -.mx_TagPanel_groupsButton object { - pointer-events: none; +.mx_TagPanel_groupsButton > .mx_GroupsButton:before { + mask: url('../../img/feather-icons/users.svg'); + mask-position: center 11px; +} + +.mx_TagPanel_groupsButton > .mx_TagPanel_report:before { + mask: url('../../img/feather-icons/life-buoy.svg'); + mask-position: center 9px; +} + +.mx_TagPanel_groupsButton > .mx_AccessibleButton { + margin-bottom: 12px; + height: 40px; + width: 40px; + border-radius: 20px; + background-color: $roomheader-addroom-color; + position: relative; + /* overwrite mx_RoleButton inline-block */ + display: block !important; + + &:before { + background-color: $tagpanel-bg-color; + mask-repeat: no-repeat; + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + } } diff --git a/res/css/structures/_TopLeftMenuButton.scss b/res/css/structures/_TopLeftMenuButton.scss new file mode 100644 index 0000000000..43a1e27ee4 --- /dev/null +++ b/res/css/structures/_TopLeftMenuButton.scss @@ -0,0 +1,44 @@ +/* +Copyright 2018 New Vector 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_TopLeftMenuButton { + flex: 0 0 52px; + border-bottom: 1px solid $panel-divider-color; + color: $topleftmenu-color; + background-color: $primary-bg-color; + display: flex; + align-items: center; + min-width: 0; + padding: 0 7px; + overflow: hidden; +} + +.mx_TopLeftMenuButton .mx_BaseAvatar { + margin: 0 7px; +} + +.mx_TopLeftMenuButton_name { + margin: 0 7px; + font-size: 18px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + font-weight: 600; +} + +.mx_TopLeftMenuButton_chevron { + margin: 0 7px; +} diff --git a/res/css/views/avatars/_MemberStatusMessageAvatar.scss b/res/css/views/avatars/_MemberStatusMessageAvatar.scss index c857b9807b..c101a5d8a8 100644 --- a/res/css/views/avatars/_MemberStatusMessageAvatar.scss +++ b/res/css/views/avatars/_MemberStatusMessageAvatar.scss @@ -14,7 +14,16 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_MemberStatusMessageAvatar_hasStatus { - border: 2px solid $accent-color; - border-radius: 40px; +.mx_MessageComposer_avatar .mx_BaseAvatar { + padding: 2px; + border: 1px solid transparent; + border-radius: 15px; +} + +.mx_MessageComposer_avatar .mx_BaseAvatar_initial { + left: 2px; +} + +.mx_MemberStatusMessageAvatar_hasStatus .mx_BaseAvatar { + border-color: $accent-color; } diff --git a/res/css/views/context_menus/_MessageContextMenu.scss b/res/css/views/context_menus/_MessageContextMenu.scss index 85e8080c88..d15d566bdb 100644 --- a/res/css/views/context_menus/_MessageContextMenu.scss +++ b/res/css/views/context_menus/_MessageContextMenu.scss @@ -14,6 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_MessageContextMenu { + padding: 6px; +} + .mx_MessageContextMenu_field { padding: 3px 6px 3px 6px; cursor: pointer; diff --git a/res/css/views/context_menus/_RoomTileContextMenu.scss b/res/css/views/context_menus/_RoomTileContextMenu.scss index 598f8ac249..f832691be4 100644 --- a/res/css/views/context_menus/_RoomTileContextMenu.scss +++ b/res/css/views/context_menus/_RoomTileContextMenu.scss @@ -14,6 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_RoomTileContextMenu { + padding: 6px; +} + .mx_RoomTileContextMenu_tag_field, .mx_RoomTileContextMenu_leave { padding-top: 8px; padding-right: 20px; diff --git a/res/css/views/context_menus/_StatusMessageContextMenu.scss b/res/css/views/context_menus/_StatusMessageContextMenu.scss index 873ad99495..972f608caf 100644 --- a/res/css/views/context_menus/_StatusMessageContextMenu.scss +++ b/res/css/views/context_menus/_StatusMessageContextMenu.scss @@ -14,42 +14,52 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_StatusMessageContextMenu_message { - display: inline-block; - border-radius: 3px 0 0 3px; +.mx_StatusMessageContextMenu { + padding: 10px; +} + +.mx_StatusMessageContextMenu_form { + display: flex; + flex-direction: column; +} + +input.mx_StatusMessageContextMenu_message { + border-radius: 4px; border: 1px solid $input-border-color; - font-size: 13px; - padding: 7px 7px 7px 9px; - width: 135px; - background-color: $primary-bg-color !important; + padding: 6.5px 11px; + background-color: $primary-bg-color; + font-weight: normal; + margin: 0 0 10px; } -.mx_StatusMessageContextMenu_submit { - display: inline-block; +.mx_StatusMessageContextMenu_message::placeholder { + color: $memberstatus-placeholder-color; } -.mx_StatusMessageContextMenu_submitFaded { - opacity: 0.5; +.mx_StatusMessageContextMenu_actionContainer { + display: flex; } -.mx_StatusMessageContextMenu_submit img { - vertical-align: middle; - margin-left: 8px; +.mx_StatusMessageContextMenu_submit, +.mx_StatusMessageContextMenu_clear { + @mixin mx_DialogButton; + align-self: start; + font-size: 12px; + padding: 6px 1em; + border: 1px solid transparent; + margin-right: 10px; } -.mx_StatusMessageContextMenu hr { - border: 0.5px solid $menu-border-color; -} - -.mx_StatusMessageContextMenu_clearIcon { - margin: 5px 15px 5px 5px; - vertical-align: middle; +.mx_StatusMessageContextMenu_submit[disabled] { + opacity: 0.49; } .mx_StatusMessageContextMenu_clear { - padding: 2px; + color: $warning-color; + background-color: transparent; + border: 1px solid $warning-color; } -.mx_StatusMessageContextMenu_hasStatus .mx_StatusMessageContextMenu_clear { - color: $warning-color; +.mx_StatusMessageContextMenu_actionContainer .mx_Spinner { + justify-content: start; } diff --git a/res/css/views/context_menus/_TopLeftMenu.scss b/res/css/views/context_menus/_TopLeftMenu.scss new file mode 100644 index 0000000000..fb2c972fe3 --- /dev/null +++ b/res/css/views/context_menus/_TopLeftMenu.scss @@ -0,0 +1,40 @@ +/* +Copyright 2018 New Vector 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_TopLeftMenu { + min-width: 180px; + + .mx_TopLeftMenu_section:not(:last-child) { + border-bottom: 1px solid $menu-border-color; + } + + .mx_TopLeftMenu_section { + list-style: none; + margin: 5px 0; + padding: 0; + + li { + cursor: pointer; + white-space: nowrap; + padding: 5px 20px; + } + + li:hover { + background-color: $menu-selected-color; + } + } + +} diff --git a/res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss b/res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss index 424ffbd0a8..3f4c8d2da4 100644 --- a/res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss +++ b/res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss @@ -14,8 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_CreateKeyBackupDialog { - padding-right: 40px; +.mx_CreateKeyBackupDialog .mx_Dialog_title { + /* TODO: Consider setting this for all dialog titles. */ + margin-bottom: 1em; } .mx_CreateKeyBackupDialog_primaryContainer { @@ -79,3 +80,8 @@ limitations under the License. display: flex; align-items: center; } + +.mx_CreateKeyBackupDialog_recoveryKeyButtons button { + flex: 1; + white-space: nowrap; +} diff --git a/res/css/views/dialogs/keybackup/_NewRecoveryMethodDialog.scss b/res/css/views/dialogs/keybackup/_KeyBackupFailedDialog.scss similarity index 82% rename from res/css/views/dialogs/keybackup/_NewRecoveryMethodDialog.scss rename to res/css/views/dialogs/keybackup/_KeyBackupFailedDialog.scss index 370f82d9ab..4a050b6fc4 100644 --- a/res/css/views/dialogs/keybackup/_NewRecoveryMethodDialog.scss +++ b/res/css/views/dialogs/keybackup/_KeyBackupFailedDialog.scss @@ -14,17 +14,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_NewRecoveryMethodDialog .mx_Dialog_title { +.mx_KeyBackupFailedDialog .mx_Dialog_title { margin-bottom: 32px; } -.mx_NewRecoveryMethodDialog_title { +.mx_KeyBackupFailedDialog_title { position: relative; padding-left: 45px; padding-bottom: 10px; &:before { - mask: url("../../../img/e2e/lock-warning.svg"); + mask: url("../../img/e2e/lock-warning.svg"); mask-repeat: no-repeat; background-color: $primary-fg-color; content: ""; @@ -36,6 +36,6 @@ limitations under the License. } } -.mx_NewRecoveryMethodDialog .mx_Dialog_buttons { +.mx_KeyBackupFailedDialog .mx_Dialog_buttons { margin-top: 36px; } diff --git a/res/css/views/elements/_AccessibleButton.scss b/res/css/views/elements/_AccessibleButton.scss index edf455049b..d6702a232c 100644 --- a/res/css/views/elements/_AccessibleButton.scss +++ b/res/css/views/elements/_AccessibleButton.scss @@ -16,9 +16,8 @@ limitations under the License. .mx_AccessibleButton:focus { outline: 0; - filter: brightness($focus-brightness); } .mx_AccessibleButton { cursor: pointer; -} \ No newline at end of file +} diff --git a/res/css/views/elements/_ResizeHandle.scss b/res/css/views/elements/_ResizeHandle.scss new file mode 100644 index 0000000000..42ff6e3825 --- /dev/null +++ b/res/css/views/elements/_ResizeHandle.scss @@ -0,0 +1,46 @@ +/* +Copyright 2018 New Vector 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_ResizeHandle { + cursor: row-resize; + flex: 0 0 auto; + z-index: 100; +} + +.mx_ResizeHandle.mx_ResizeHandle_horizontal { + margin: 0 -5px; + padding: 0 5px; + cursor: col-resize; +} + +.mx_ResizeHandle.mx_ResizeHandle_vertical { + margin: -5px 0; + padding: 5px 0; + cursor: row-resize; +} + +.mx_ResizeHandle > div { + background: $panel-divider-color; +} + +.mx_ResizeHandle.mx_ResizeHandle_horizontal > div { + width: 1px; + height: 100%; +} + +.mx_ResizeHandle.mx_ResizeHandle_vertical > div { + height: 1px; +} diff --git a/res/css/views/elements/_RichText.scss b/res/css/views/elements/_RichText.scss index cea4b7897d..0847aa7c9b 100644 --- a/res/css/views/elements/_RichText.scss +++ b/res/css/views/elements/_RichText.scss @@ -19,6 +19,11 @@ cursor: pointer; } +/* More specific to override `.markdown-body a` text-decoration */ +.mx_EventTile_content .markdown-body a.mx_Pill { + text-decoration: none; +} + /* More specific to override `.markdown-body a` color */ .mx_EventTile_content .markdown-body a.mx_UserPill, .mx_UserPill { @@ -31,7 +36,9 @@ background-color: $accent-color ! important; } +/* More specific to override `.markdown-body a` color */ .mx_EventTile_highlight .mx_EventTile_content .markdown-body a.mx_UserPill_me, +.mx_EventTile_content .markdown-body a.mx_AtRoomPill, .mx_EventTile_content .mx_AtRoomPill, .mx_MessageComposer_input .mx_AtRoomPill { color: $accent-fg-color; @@ -72,6 +79,11 @@ .mx_Markdown_ITALIC { font-style: italic; + + // compensate for Nunito italics being terrible + // https://github.com/google/fonts/issues/1726 + transform: skewX(-14deg); + display: inline-block; } .mx_Markdown_CODE { diff --git a/res/css/views/groups/_GroupRoomList.scss b/res/css/views/groups/_GroupRoomList.scss index fb41ebaa9e..fefd17849c 100644 --- a/res/css/views/groups/_GroupRoomList.scss +++ b/res/css/views/groups/_GroupRoomList.scss @@ -18,4 +18,10 @@ limitations under the License. position: relative; color: $primary-fg-color; cursor: pointer; + display: flex; + align-items: center; +} + +.mx_GroupRoomList_wrapper { + padding: 10px; } diff --git a/res/css/views/messages/_DateSeparator.scss b/res/css/views/messages/_DateSeparator.scss index f676d24bef..f8738f10e3 100644 --- a/res/css/views/messages/_DateSeparator.scss +++ b/res/css/views/messages/_DateSeparator.scss @@ -16,10 +16,21 @@ limitations under the License. .mx_DateSeparator { clear: both; - margin-top: 32px; - margin-bottom: 8px; - margin-left: 63px; - padding-bottom: 6px; - border-bottom: 1px solid $primary-hairline-color; + margin: 4px 0; + display: flex; + align-items: center; + font-size: 14px; + color: $roomtopic-color; } +.mx_DateSeparator > hr { + flex: 1 1 0; + height: 0; + border: none; + border-bottom: 1px solid $panel-divider-color; +} + +.mx_DateSeparator > date { + margin: 0 25px; + flex: 0 0 auto; +} diff --git a/res/css/views/messages/_MImageBody.scss b/res/css/views/messages/_MImageBody.scss index a5a1e66a3b..86bc022829 100644 --- a/res/css/views/messages/_MImageBody.scss +++ b/res/css/views/messages/_MImageBody.scss @@ -56,4 +56,6 @@ limitations under the License. border-radius: 5px; background: $imagebody-giflabel; border: 2px solid $imagebody-giflabel-border; + color: $imagebody-giflabel-color; + pointer-events: none; } diff --git a/res/css/views/messages/_SenderProfile.scss b/res/css/views/messages/_SenderProfile.scss index 060709b82e..a4a2aba11f 100644 --- a/res/css/views/messages/_SenderProfile.scss +++ b/res/css/views/messages/_SenderProfile.scss @@ -13,3 +13,41 @@ 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_SenderProfile_name { + font-weight: 600; +} + +.mx_SenderProfile_color1 { + color: $username-variant1-color; +} + +.mx_SenderProfile_color2 { + color: $username-variant2-color; +} + +.mx_SenderProfile_color3 { + color: $username-variant3-color; +} + +.mx_SenderProfile_color4 { + color: $username-variant4-color; +} + +.mx_SenderProfile_color5 { + color: $username-variant5-color; +} + +.mx_SenderProfile_color6 { + color: $username-variant6-color; +} + +.mx_SenderProfile_color7 { + color: $username-variant7-color; +} + +.mx_SenderProfile_color8 { + color: $username-variant8-color; +} + + diff --git a/res/css/views/rooms/_AuxPanel.scss b/res/css/views/rooms/_AuxPanel.scss new file mode 100644 index 0000000000..34ef5e01d4 --- /dev/null +++ b/res/css/views/rooms/_AuxPanel.scss @@ -0,0 +1,50 @@ +/* +Copyright 2018 New Vector 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. +*/ + +.m_RoomView_auxPanel_stateViews { + padding: 5px; + padding-left: 19px; + border-bottom: 1px solid #e5e5e5; +} + +.m_RoomView_auxPanel_stateViews_span a { + text-decoration: none; + color: inherit; +} + +.m_RoomView_auxPanel_stateViews_span[data-severity=warning] { + font-weight: bold; + color: orange; +} + +.m_RoomView_auxPanel_stateViews_span[data-severity=alert] { + font-weight: bold; + color: red; +} + +.m_RoomView_auxPanel_stateViews_span[data-severity=normal] { + font-weight: normal; +} + +.m_RoomView_auxPanel_stateViews_span[data-severity=notice] { + font-weight: normal; + color: $settings-grey-fg-color; +} + +.m_RoomView_auxPanel_stateViews_delim { + padding: 0 5px; + color: $settings-grey-fg-color; +} diff --git a/res/css/views/rooms/_EntityTile.scss b/res/css/views/rooms/_EntityTile.scss index 90d5dc9aa5..c4d4d944a6 100644 --- a/res/css/views/rooms/_EntityTile.scss +++ b/res/css/views/rooms/_EntityTile.scss @@ -15,12 +15,19 @@ limitations under the License. */ .mx_EntityTile { - display: table-row; - position: relative; + display: flex; + align-items: center; color: $primary-fg-color; cursor: pointer; } +.mx_EntityTile:hover { + background-image: url('../../img/member_chevron.png'); + background-position: center right 10px; + background-repeat: no-repeat; + padding-right: 30px; +} + .mx_EntityTile_invite { display: table-cell; vertical-align: middle; @@ -30,12 +37,10 @@ limitations under the License. .mx_EntityTile_avatar, .mx_GroupRoomTile_avatar { - display: table-cell; padding-left: 3px; padding-right: 12px; padding-top: 4px; padding-bottom: 4px; - vertical-align: middle; width: 36px; height: 36px; position: relative; @@ -51,32 +56,21 @@ limitations under the License. .mx_EntityTile_name, .mx_GroupRoomTile_name { - display: table-cell; - vertical-align: middle; + flex: 1 1 0; overflow: hidden; font-size: 14px; text-overflow: ellipsis; white-space: nowrap; - max-width: 155px; } .mx_EntityTile_details { - display: table-cell; - padding-right: 14px; - vertical-align: middle; + overflow: hidden; } .mx_EntityTile_name_hover { font-size: 13px; } -.mx_EntityTile_chevron { - margin-top: 8px; - margin-right: -4px; - margin-left: 6px; - float: right; -} - .mx_EntityTile_ellipsis .mx_EntityTile_name { font-style: italic; color: $primary-fg-color; @@ -87,6 +81,7 @@ limitations under the License. color: $primary-fg-color; } +/* .mx_EntityTile_unavailable .mx_EntityTile_avatar, .mx_EntityTile_unavailable .mx_EntityTile_name, .mx_EntityTile_unavailable .mx_EntityTile_name_hover, @@ -110,6 +105,7 @@ limitations under the License. { opacity: 0.25; } +*/ .mx_EntityTile_subtext { font-size: 11px; @@ -118,5 +114,3 @@ limitations under the License. white-space: nowrap; text-overflow: clip; } - - diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index f74e2e0850..7fb6812c79 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -55,11 +55,6 @@ limitations under the License. line-height: 22px; } -.mx_EventTile .mx_SenderProfile .mx_SenderProfile_name, -.mx_EventTile .mx_SenderProfile .mx_SenderProfile_aux { - opacity: 0.5; -} - .mx_EventTile .mx_SenderProfile .mx_Flair { opacity: 0.7; margin-left: 5px; @@ -450,13 +445,21 @@ limitations under the License. } .mx_EventTile_content .markdown-body a { - color: $accent-color; + color: $accent-color-alt; + text-decoration: underline; } .mx_EventTile_content .markdown-body .hljs { display: inline ! important; } +// compensate for Nunito italics being terrible +// https://github.com/google/fonts/issues/1726 +.mx_EventTile_content .markdown-body em { + transform: skewX(-14deg); + display: inline-block; +} + /* end of overrides */ .mx_MatrixChat_useCompactLayout { diff --git a/res/css/views/rooms/_MemberInfo.scss b/res/css/views/rooms/_MemberInfo.scss index 2270e83743..4af181a464 100644 --- a/res/css/views/rooms/_MemberInfo.scss +++ b/res/css/views/rooms/_MemberInfo.scss @@ -15,14 +15,35 @@ limitations under the License. */ .mx_MemberInfo { - margin-top: 20px; - padding-right: 20px; - height: 100%; + display: flex; + flex-direction: column; + flex: 1; overflow-y: auto; } +.mx_MemberInfo_name { + align-items: center; + display: flex; +} + +.mx_MemberInfo_cancel { + height: 16px; + padding: 10px 15px; + cursor: pointer; +} + +.mx_MemberInfo_name h2 { + flex: 1; +} + .mx_MemberInfo h2 { - margin-top: 6px; + font-size: 18px; + font-weight: 600; + margin: 16px 0; +} + +.mx_MemberInfo_container { + padding: 8px; } .mx_MemberInfo .mx_RoomTile_nameContainer { @@ -37,14 +58,16 @@ limitations under the License. width: 160px; } -.mx_MemberInfo_cancel { - float: right; - margin-right: 10px; - cursor: pointer; +.mx_MemberInfo_avatar { + background: $tagpanel-bg-color; } -.mx_MemberInfo_avatar { - clear: both; +.mx_MemberInfo_avatar > img { + height: auto; + width: 100%; + max-height: 30vh; + object-fit: contain; + display: block; } .mx_MemberInfo_avatar .mx_BaseAvatar { @@ -70,7 +93,6 @@ limitations under the License. .mx_MemberInfo_profileField { font-size: 13px; position: relative; - background-color: $primary-bg-color; } .mx_MemberInfo_buttons { @@ -117,3 +139,6 @@ limitations under the License. white-space: nowrap; text-overflow: clip; } +.mx_MemberInfo .mx_MemberInfo_scrollContainer { + flex: 1; +} diff --git a/res/css/views/rooms/_MemberList.scss b/res/css/views/rooms/_MemberList.scss index cfac8797b9..6f9491b22f 100644 --- a/res/css/views/rooms/_MemberList.scss +++ b/res/css/views/rooms/_MemberList.scss @@ -17,20 +17,24 @@ limitations under the License. .mx_MemberList, .mx_GroupMemberList, .mx_GroupRoomList { - height: 100%; - - margin-top: 12px; - margin-right: 20px; - flex: 1; - display: flex; - flex-direction: column; -} -.mx_MemberList .mx_Spinner { - flex: 0 0 auto; + .mx_Spinner { + flex: 1 0 auto; + } + + h2 { + text-transform: uppercase; + color: $h3-color; + font-weight: 600; + font-size: 13px; + padding-left: 3px; + padding-right: 12px; + margin-top: 8px; + margin-bottom: 4px; + } } .mx_MemberList_chevron { @@ -49,65 +53,39 @@ limitations under the License. .mx_MemberList_query, .mx_GroupMemberList_query, .mx_GroupRoomList_query { - font-family: $font-family; - border-radius: 3px; - border: 1px solid $input-border-color; - padding: 9px; - color: $primary-fg-color; - background-color: $primary-bg-color; - margin-left: 3px; - font-size: 14px; - margin-bottom: 8px; - width: 189px; + flex: 1 1 0; } -.mx_MemberList_query::-moz-placeholder, -.mx_GroupMemberList_query::-moz-placeholder, -.mx_GroupRoomList_query::-moz-placeholder { - color: $primary-fg-color; - opacity: 0.5; - font-size: 14px; -} -.mx_MemberList_query::-webkit-input-placeholder, -.mx_GroupMemberList_query::-webkit-input-placeholder, -.mx_GroupRoomList_query::-webkit-input-placeholder { - color: $primary-fg-color; - opacity: 0.5; - font-size: 14px; -} -.mx_MemberList_joined { - order: 2; - flex: 1 0 0; - - overflow-y: auto; -} - -/* -.mx_MemberList_invited { - order: 3; - flex: 0 0 100px; - overflow-y: auto; -} -*/ - -.mx_GroupMemberList_invited h2, -.mx_MemberList_invited h2 { - text-transform: uppercase; - color: $h3-color; - font-weight: 600; - font-size: 13px; - padding-left: 3px; - padding-right: 12px; - margin-top: 8px; - margin-bottom: 4px; -} - -/* we have to have display: table in order for the horizontal wrapping to work */ .mx_MemberList_wrapper { - display: table; - table-layout: fixed; - width: 100%; + padding: 10px; } + +.mx_MemberList_invite, +.mx_RightPanel_invite { + flex: 0 0 auto; + position: relative; + background-color: $button-bg-color; + border-radius: 4px; + padding: 8px; + margin: 9px; + display: flex; + color: $button-fg-color; + font-weight: 600; + + .mx_RightPanel_icon { + padding-right: 5px; + padding-top: 2px; + } +} + +.mx_MemberList_invite span { + margin: 0 auto; + background-image: url('../../img/feather-icons/user-add.svg'); + background-repeat: no-repeat; + background-position: center left; + padding-left: 25px; + +} diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index e6a532d072..4a052482ad 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -16,7 +16,6 @@ limitations under the License. */ .mx_MessageComposer_wrapper { - max-width: 960px; vertical-align: middle; margin: auto; border-top: 1px solid $primary-hairline-color; @@ -58,13 +57,8 @@ limitations under the License. width: 100%; } -.mx_MessageComposer_row > div:last-child{ - padding-right: 0; -} - .mx_MessageComposer .mx_MessageComposer_avatar { - padding-left: 10px; - padding-right: 28px; + padding: 0 27px; } .mx_MessageComposer .mx_MessageComposer_avatar .mx_BaseAvatar { @@ -77,7 +71,7 @@ limitations under the License. .mx_MessageComposer_e2eIcon { position: absolute; - left: 44px; + left: 60px; } .mx_MessageComposer_noperm_error { @@ -187,7 +181,7 @@ limitations under the License. /*display: table-cell;*/ /*vertical-align: middle;*/ /*padding-left: 10px;*/ - padding-right: 5px; + padding-right: 12px; cursor: pointer; padding-top: 4px; } @@ -216,7 +210,6 @@ limitations under the License. .mx_MessageComposer_formatbar { margin: auto; - max-width: 960px; display: flex; height: 30px; diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss index 9c1349adbc..1359bc5f57 100644 --- a/res/css/views/rooms/_RoomHeader.scss +++ b/res/css/views/rooms/_RoomHeader.scss @@ -14,29 +14,24 @@ See the License for the specific language governing permissions and limitations under the License. */ -/* add 20px to the height of the header when editing */ -.mx_RoomHeader_editing { - flex: 0 0 93px ! important; +.mx_RoomHeader { + flex: 0 0 52px; + border-bottom: 1px solid $primary-hairline-color; } .mx_RoomHeader_wrapper { - max-width: 960px; margin: auto; - height: 70px; + height: 52px; align-items: center; display: flex; -} - -.mx_RoomHeader_leftRow { - margin-left: -2px; - order: 1; - flex: 1; - overflow: hidden; + align-items: center; + min-width: 0; + padding: 0 10px 0 19px; } .mx_RoomHeader_spinner { + flex: 1; height: 36px; - order: 2; padding-left: 12px; padding-right: 12px; } @@ -45,7 +40,6 @@ limitations under the License. @mixin mx_DialogButton; margin-right: 8px; margin-top: -5px; - order: 2; } .mx_RoomHeader_textButton:hover { @@ -57,31 +51,29 @@ limitations under the License. } .mx_RoomHeader_cancelButton { - order: 2; cursor: pointer; padding-left: 12px; padding-right: 12px; } -.mx_RoomHeader_rightRow { - margin-top: 4px; - background-color: $primary-bg-color; +.mx_RoomHeader_buttons { display: flex; align-items: center; - order: 3; + background-color: $primary-bg-color; + padding-right: 5px; } .mx_RoomHeader_info { - display: table-cell; - width: 100%; - vertical-align: middle; + display: flex; + flex: 1; + align-items: center; } .mx_RoomHeader_simpleHeader { - line-height: 70px; - color: $primary-fg-color; - font-size: 22px; - font-weight: bold; + line-height: 52px; + color: $roomheader-color; + font-size: 18px; + font-weight: 600; overflow: hidden; margin-left: 63px; text-overflow: ellipsis; @@ -99,22 +91,20 @@ limitations under the License. } .mx_RoomHeader_name { - vertical-align: middle; - width: 100%; - height: 31px; + flex: 0 1 auto; overflow: hidden; - color: $primary-fg-color; - font-weight: bold; - font-size: 22px; - padding-left: 19px; - padding-right: 16px; - /* why isn't text-overflow working? */ - text-overflow: ellipsis; + color: $roomheader-color; + font-weight: 600; + font-size: 18px; + margin: 0 7px; border-bottom: 1px solid transparent; + display: flex; } .mx_RoomHeader_nametext { - display: inline-block; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; } .mx_RoomHeader_settingsHint { @@ -122,7 +112,6 @@ limitations under the License. } .mx_RoomHeader_searchStatus { - display: inline-block; font-weight: normal; opacity: 0.6; } @@ -166,25 +155,23 @@ limitations under the License. } .mx_RoomHeader_topic { - vertical-align: bottom; - float: left; - max-height: 38px; - color: $settings-grey-fg-color; - font-weight: 300; + flex: 1; + color: $roomtopic-color; + font-weight: 400; font-size: 13px; - margin-left: 19px; - margin-right: 16px; + margin: 0 7px; overflow: hidden; text-overflow: ellipsis; border-bottom: 1px solid transparent; - column-width: 960px; + line-height: 1.2em; + max-height: 2.4em; } .mx_RoomHeader_avatar { - display: table-cell; - width: 48px; - height: 50px; - vertical-align: middle; + flex: 0; + width: 28px; + height: 28px; + margin: 0 7px; } .mx_RoomHeader_avatar .mx_BaseAvatar_image { @@ -192,13 +179,13 @@ limitations under the License. } .mx_RoomHeader_avatarPicker { - margin-top: 23px; position: relative; } .mx_RoomHeader_avatarPicker_edit { - margin-left: 16px; - margin-top: 4px; + position: absolute; + left: 16px; + top: 18px; } .mx_RoomHeader_avatarPicker_edit > label { @@ -210,7 +197,7 @@ limitations under the License. } .mx_RoomHeader_button { - margin-left: 12px; + margin-left: 10px; cursor: pointer; } @@ -218,6 +205,10 @@ limitations under the License. pointer-events: none; } +.mx_RoomHeader_showPanel { + height: 16px; +} + .mx_RoomHeader_voipButton { display: table-cell; } diff --git a/res/css/views/rooms/_RoomList.scss b/res/css/views/rooms/_RoomList.scss index a346a06a20..8f78e3bb7a 100644 --- a/res/css/views/rooms/_RoomList.scss +++ b/res/css/views/rooms/_RoomList.scss @@ -16,8 +16,21 @@ limitations under the License. */ .mx_RoomList { - padding-bottom: 12px; - min-height: 400px; + /* take up remaining space below TopLeftMenu */ + flex: 1 1 auto; + /* use flexbox to layout sublists */ + display: flex; + flex-direction: column; + min-height: 0; +} + +.mx_SearchBox { + flex: none; +} + +/* hide resize handles next to collapsed / empty sublists */ +.mx_RoomList .mx_RoomSubList:not(.mx_RoomSubList_nonEmpty) + .mx_ResizeHandle { + display: none; } .mx_RoomList_expandButton { @@ -27,20 +40,7 @@ limitations under the License. padding-right: 12px; } -/* Evil hacky override until Chrome fixes drop and drag table cells - and we can correctly fix horizontal wrapping in the sidebar again */ -.mx_RoomList_scrollbar .gm-scroll-view { - overflow-x: hidden ! important; - overflow-y: scroll ! important; -} - -/* Make sure the scrollbar is above the sticky headers from RoomList */ -.mx_RoomList_scrollbar .gm-scrollbar.-vertical { - z-index: 6; -} - .mx_RoomList_emptySubListTip_container { - background-color: $secondary-accent-color; padding-left: 18px; padding-right: 18px; padding-top: 8px; diff --git a/res/css/views/rooms/_RoomSettings.scss b/res/css/views/rooms/_RoomSettings.scss index b3858f3ba7..4454cd479c 100644 --- a/res/css/views/rooms/_RoomSettings.scss +++ b/res/css/views/rooms/_RoomSettings.scss @@ -16,8 +16,7 @@ limitations under the License. */ .mx_RoomSettings { - margin-left: 65px; - margin-bottom: 20px; + margin: 40px; } .mx_RoomSettings_upgradeButton, diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index b5ac9aadc6..56ca715b51 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -15,13 +15,30 @@ limitations under the License. */ .mx_RoomTile { - position: relative; + display: flex; + flex-direction: row; + align-items: center; cursor: pointer; - font-size: 13px; - display: block; height: 34px; + margin: 0; + padding: 0 8px 0 10px; + position: relative; +} - background-color: $secondary-accent-color; +.mx_RoomTile_menuButton { + display: none; + flex: 0 0 16px; + height: 16px; + background-image: url('../../img/icon_context.svg'); + background-repeat: no-repeat; + background-position: center; +} + +// toggle menuButton and badge on hover/menu displayed +.mx_LeftPanel_container:not(.collapsed) .mx_RoomTile:hover, .mx_RoomTile_menuDisplayed { + .mx_RoomTile_menuButton { + display: block; + } } .mx_RoomTile_tooltip { @@ -33,9 +50,18 @@ limitations under the License. .mx_RoomTile_nameContainer { - display: inline-block; - width: 180px; + display: flex; + align-items: center; + flex: 1; vertical-align: middle; + min-width: 0; +} + +.mx_RoomTile_labelContainer { + display: flex; + flex-direction: column; + flex: 1; + min-width: 0; } .mx_RoomTile_subtext { @@ -55,11 +81,8 @@ limitations under the License. } .mx_RoomTile_avatar { - display: inline-block; - padding-top: 5px; - padding-bottom: 5px; - padding-left: 16px; - padding-right: 6px; + flex: 0; + padding: 4px; width: 24px; vertical-align: middle; } @@ -78,87 +101,52 @@ limitations under the License. } .mx_RoomTile_name { - display: inline-block; - position: relative; - width: 165px; - vertical-align: middle; - padding-left: 6px; - padding-right: 6px; - padding-top: 2px; - padding-bottom: 3px; + font-size: 14px; + font-weight: 600; + padding: 0 6px; color: $roomtile-name-color; white-space: nowrap; - overflow: hidden; + overflow-x: hidden; text-overflow: ellipsis; } -.mx_RoomTile_invite { -/* color: rgba(69, 69, 69, 0.5); */ -} +.collapsed { + .mx_RoomTile { + margin: 0 2px; + padding: 0 2px; + position: relative; + justify-content: center; + } -.collapsed .mx_RoomTile_nameContainer { - width: 60px; /* colapsed panel width */ -} + .mx_RoomTile_name { + display: none; + } -.collapsed .mx_RoomTile_name { - display: none; -} + .mx_RoomTile_badge { + position: absolute; + right: 6px; + top: 0px; + border-radius: 16px; + z-index: 3; + border: 0.18em solid $secondary-accent-color; + } -.collapsed .mx_RoomTile_badge { - top: 0px; - min-width: 12px; - border-radius: 16px; - padding: 0px 4px 0px 4px; - z-index: 3; -} - -/* Hide the bottom of speech bubble */ -.collapsed .mx_RoomTile_highlight .mx_RoomTile_badge:after { - display: none; -} - -/* This is the bottom of the speech bubble */ -.mx_RoomTile_highlight .mx_RoomTile_badge:after { - content: ""; - position: absolute; - display: block; - width: 0; - height: 0; - margin-left: 5px; - border-top: 5px solid $warning-color; - border-right: 7px solid transparent; + .mx_RoomTile_menuButton { + display: none; //no design for this for now + } } .mx_RoomTile_badge { - display: inline-block; - min-width: 15px; - height: 15px; - position: absolute; - right: 8px; /*gutter */ - top: 9px; - border-radius: 8px; + flex: 0 1 content; + border-radius: 0.8em; + padding: 0 0.4em; color: $accent-fg-color; font-weight: 600; - font-size: 10px; - text-align: center; - padding-top: 1px; - padding-left: 4px; - padding-right: 4px; -} - -.mx_RoomTile .mx_RoomTile_badge.mx_RoomTile_badgeButton, -.mx_RoomTile.mx_RoomTile_menuDisplayed .mx_RoomTile_badge { - letter-spacing: 0.1em; - opacity: 1; -} - -.mx_RoomTile.mx_RoomTile_noBadges .mx_RoomTile_badge.mx_RoomTile_badgeButton, -.mx_RoomTile.mx_RoomTile_menuDisplayed.mx_RoomTile_noBadges .mx_RoomTile_badge { - background-color: $neutral-badge-color; + font-size: 12px; } .mx_RoomTile_unreadNotify .mx_RoomTile_badge { - background-color: $accent-color; + background-color: $roomtile-name-color; } .mx_RoomTile_highlight .mx_RoomTile_badge { @@ -167,9 +155,14 @@ limitations under the License. .mx_RoomTile_unread, .mx_RoomTile_highlight { font-weight: 800; + + .mx_RoomTile_name { + color: $roomtile-selected-color; + } } .mx_RoomTile_selected { + border-radius: 4px; background-color: $roomtile-selected-bg-color; } @@ -187,10 +180,6 @@ limitations under the License. background-color: $roomtile-focused-bg-color; } -.mx_RoomTile .mx_RoomTile_name.mx_RoomTile_badgeShown { - width: 140px; -} - .mx_RoomTile_arrow { position: absolute; right: 0px; @@ -203,4 +192,3 @@ limitations under the License. .mx_RoomTile.mx_RoomTile_transparent:focus { background-color: $roomtile-transparent-focused-color; } - diff --git a/res/css/views/rooms/_RoomTooltip.scss b/res/css/views/rooms/_RoomTooltip.scss index 9988425b8f..295786d2d3 100644 --- a/res/css/views/rooms/_RoomTooltip.scss +++ b/res/css/views/rooms/_RoomTooltip.scss @@ -21,7 +21,7 @@ limitations under the License. width: 0; height: 0; border-top: 8px solid transparent; - border-right: 8px solid $menu-border-color; + border-right: 8px solid $menu-bg-color; border-bottom: 8px solid transparent; } @@ -40,8 +40,8 @@ limitations under the License. .mx_RoomTooltip { display: none; position: fixed; - border: 1px solid $menu-border-color; border-radius: 5px; + box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.21); background-color: $primary-bg-color; z-index: 2000; padding: 5px; diff --git a/res/css/views/rooms/_SearchBar.scss b/res/css/views/rooms/_SearchBar.scss index 079ea16c68..390d6606c1 100644 --- a/res/css/views/rooms/_SearchBar.scss +++ b/res/css/views/rooms/_SearchBar.scss @@ -15,69 +15,52 @@ limitations under the License. */ .mx_SearchBar { - padding-top: 5px; - padding-bottom: 5px; + height: 56px; display: flex; align-items: center; -} + border-bottom: 1px solid $primary-hairline-color; -.mx_SearchBar_input { - display: inline block; - border-radius: 3px 0px 0px 3px; - border: 1px solid $input-border-color; - font-size: 15px; - padding: 9px; - padding-left: 11px; - width: auto; - flex: 1 1 0; -} + .mx_SearchBar_input { + // border: 1px solid $input-border-color; + // font-size: 15px; + flex: 1 1 0; + margin-left: 22px; + } -.mx_SearchBar_searchButton { - cursor: pointer; - margin-right: 10px; - width: 37px; - height: 37px; - border-radius: 0px 3px 3px 0px; - background-color: $accent-color; -} + .mx_SearchBar_searchButton { + cursor: pointer; + width: 37px; + height: 37px; + background-color: $accent-color; + mask: url('../../img/feather-icons/search-input.svg'); + mask-repeat: no-repeat; + mask-position: center; + } -@keyframes pulsate { - 0% { opacity: 1.0; } - 50% { opacity: 0.1; } - 100% { opacity: 1.0; } -} + .mx_SearchBar_button { + border: 0; + margin: 0 0 0 22px; + padding: 5px; + font-size: 15px; + cursor: pointer; + color: $primary-fg-color; + border-bottom: 2px solid $accent-color; + font-weight: 600; + } -.mx_SearchBar_searching img { - animation: pulsate 0.5s ease-out; - animation-iteration-count: infinite; -} + .mx_SearchBar_unselected { + color: $input-darker-fg-color; + border-color: transparent; + } -.mx_SearchBar_button { - display: inline; - border: 0px; - border-radius: 36px; - font-weight: 400; - font-size: 15px; - color: $accent-fg-color; - background-color: $accent-color; - 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: $primary-bg-color; - color: $accent-color; - border: $accent-color 1px solid; -} - -.mx_SearchBar_cancel { - padding-left: 14px; - padding-right: 14px; - cursor: pointer; + .mx_SearchBar_cancel { + background-color: $warning-color; + mask: url('../../img/cancel.svg'); + mask-repeat: no-repeat; + mask-position: center; + mask-size: 14px; + padding: 9px; + margin: 0 12px 0 3px; + cursor: pointer; + } } diff --git a/res/css/views/rooms/_TopUnreadMessagesBar.scss b/res/css/views/rooms/_TopUnreadMessagesBar.scss index 1ee56d9532..67579552c1 100644 --- a/res/css/views/rooms/_TopUnreadMessagesBar.scss +++ b/res/css/views/rooms/_TopUnreadMessagesBar.scss @@ -14,40 +14,48 @@ See the License for the specific language governing permissions and limitations under the License. */ +@charset "utf-8"; + .mx_TopUnreadMessagesBar { - margin: auto; /* centre horizontally */ - max-width: 960px; - padding-top: 10px; - padding-bottom: 10px; - border-bottom: 1px solid $primary-hairline-color; + z-index: 1000; + position: absolute; + top: 24px; + right: 24px; + width: 38px; +} + +.mx_TopUnreadMessagesBar:after { + content: "·"; + position: absolute; + top: -8px; + left: 11px; + width: 16px; + height: 16px; + border-radius: 16px; + font-weight: 600; + font-size: 30px; + line-height: 14px; + text-align: center; + color: $secondary-accent-color; + background-color: $accent-color; } .mx_TopUnreadMessagesBar_scrollUp { - display: inline; - cursor: pointer; - text-decoration: underline; -} - -.mx_TopUnreadMessagesBar_scrollUp img { - padding-left: 10px; - padding-right: 31px; - vertical-align: middle; -} - -.mx_TopUnreadMessagesBar_scrollUp span { - opacity: 0.5; -} - -.mx_TopUnreadMessagesBar_close { - float: right; - padding-right: 14px; - padding-top: 3px; + height: 38px; + border-radius: 19px; + box-sizing: border-box; + background: $primary-bg-color; + border: 1.3px solid $roomtile-name-color; cursor: pointer; } -.mx_MatrixChat_useCompactLayout { - .mx_TopUnreadMessagesBar { - padding-top: 4px; - padding-bottom: 4px; - } +.mx_TopUnreadMessagesBar_scrollUp:before { + content: ""; + position: absolute; + width: 38px; + height: 38px; + mask: url('../../img/icon-jump-to-first-unread.svg'); + mask-repeat: no-repeat; + mask-position: 9px 13px; + background: $roomtile-name-color; } diff --git a/res/css/views/rooms/_WhoIsTypingTile.scss b/res/css/views/rooms/_WhoIsTypingTile.scss new file mode 100644 index 0000000000..217a10be8d --- /dev/null +++ b/res/css/views/rooms/_WhoIsTypingTile.scss @@ -0,0 +1,77 @@ +/* +Copyright 2018 New Vector 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_WhoIsTypingTile { + margin-left: -18px; //offset padding from mx_RoomView_MessageList to center avatars + padding-top: 18px; + display: flex; + align-items: center; +} + +/* position the indicator in the same place horizontally as .mx_EventTile_avatar. */ +.mx_WhoIsTypingTile_avatars { + flex: 0 0 83px; // 18 + 65 + text-align: center; +} + +.mx_WhoIsTypingTile_avatars > :not(:first-child) { + margin-left: -12px; +} + +.mx_WhoIsTypingTile_avatars .mx_BaseAvatar_image { + border: 1px solid $primary-bg-color; +} + +.mx_WhoIsTypingTile_avatars .mx_BaseAvatar_initial { + padding-top: 1px; +} + +.mx_WhoIsTypingTile_remainingAvatarPlaceholder { + display: inline-block; + color: #acacac; + background-color: #ddd; + border: 1px solid $primary-bg-color; + border-radius: 40px; + width: 24px; + height: 24px; + line-height: 24px; + font-size: 0.8em; + vertical-align: top; + text-align: center; +} + +.mx_WhoIsTypingTile_label { + flex: 1; + font-size: 14px; + font-weight: 600; + color: $eventtile-meta-color; +} + +.mx_WhoIsTypingTile_label > span { + background-image: url('../../img/typing-indicator-2x.gif'); + background-size: 25px; + background-position: left bottom; + background-repeat: no-repeat; + padding-bottom: 15px; + display: block; +} + +.mx_MatrixChat_useCompactLayout { + + .mx_WhoIsTypingTile { + padding-top: 4px; + } +} diff --git a/res/fonts/Nunito/XRXQ3I6Li01BKofIMN44Y9vKUTo.ttf b/res/fonts/Nunito/XRXQ3I6Li01BKofIMN44Y9vKUTo.ttf new file mode 100644 index 0000000000..4387fb67c4 Binary files /dev/null and b/res/fonts/Nunito/XRXQ3I6Li01BKofIMN44Y9vKUTo.ttf differ diff --git a/res/fonts/Nunito/XRXQ3I6Li01BKofIMN5cYtvKUTo.ttf b/res/fonts/Nunito/XRXQ3I6Li01BKofIMN5cYtvKUTo.ttf new file mode 100644 index 0000000000..68fb3ff5cb Binary files /dev/null and b/res/fonts/Nunito/XRXQ3I6Li01BKofIMN5cYtvKUTo.ttf differ diff --git a/res/fonts/Nunito/XRXV3I6Li01BKofINeaE.ttf b/res/fonts/Nunito/XRXV3I6Li01BKofINeaE.ttf new file mode 100644 index 0000000000..c40e599260 Binary files /dev/null and b/res/fonts/Nunito/XRXV3I6Li01BKofINeaE.ttf differ diff --git a/res/fonts/Nunito/XRXW3I6Li01BKofA6sKUYevN.ttf b/res/fonts/Nunito/XRXW3I6Li01BKofA6sKUYevN.ttf new file mode 100644 index 0000000000..0c4fd17dfa Binary files /dev/null and b/res/fonts/Nunito/XRXW3I6Li01BKofA6sKUYevN.ttf differ diff --git a/res/fonts/Nunito/XRXW3I6Li01BKofAjsOUYevN.ttf b/res/fonts/Nunito/XRXW3I6Li01BKofAjsOUYevN.ttf new file mode 100644 index 0000000000..339d59ac00 Binary files /dev/null and b/res/fonts/Nunito/XRXW3I6Li01BKofAjsOUYevN.ttf differ diff --git a/res/fonts/Nunito/XRXX3I6Li01BKofIMNaDRss.ttf b/res/fonts/Nunito/XRXX3I6Li01BKofIMNaDRss.ttf new file mode 100644 index 0000000000..b5fcd891af Binary files /dev/null and b/res/fonts/Nunito/XRXX3I6Li01BKofIMNaDRss.ttf differ diff --git a/res/img/feather-icons/face.svg b/res/img/feather-icons/face.svg new file mode 100644 index 0000000000..0a359b2dea --- /dev/null +++ b/res/img/feather-icons/face.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/res/img/feather-icons/files.svg b/res/img/feather-icons/files.svg new file mode 100644 index 0000000000..c66d9ad121 --- /dev/null +++ b/res/img/feather-icons/files.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/res/img/feather-icons/grid.svg b/res/img/feather-icons/grid.svg new file mode 100644 index 0000000000..e6912b0cc7 --- /dev/null +++ b/res/img/feather-icons/grid.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/res/img/feather-icons/life-buoy.svg b/res/img/feather-icons/life-buoy.svg new file mode 100644 index 0000000000..20bd0f0b5d --- /dev/null +++ b/res/img/feather-icons/life-buoy.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/res/img/feather-icons/notifications.svg b/res/img/feather-icons/notifications.svg new file mode 100644 index 0000000000..2fe85e810c --- /dev/null +++ b/res/img/feather-icons/notifications.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/res/img/feather-icons/paperclip.svg b/res/img/feather-icons/paperclip.svg new file mode 100644 index 0000000000..ed2bb88681 --- /dev/null +++ b/res/img/feather-icons/paperclip.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/res/img/feather-icons/phone.svg b/res/img/feather-icons/phone.svg new file mode 100644 index 0000000000..58b257f113 --- /dev/null +++ b/res/img/feather-icons/phone.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/res/img/feather-icons/search-input.svg b/res/img/feather-icons/search-input.svg new file mode 100644 index 0000000000..3be5acb32e --- /dev/null +++ b/res/img/feather-icons/search-input.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/res/img/feather-icons/search.svg b/res/img/feather-icons/search.svg new file mode 100644 index 0000000000..8b14246f64 --- /dev/null +++ b/res/img/feather-icons/search.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/res/img/feather-icons/settings.svg b/res/img/feather-icons/settings.svg new file mode 100644 index 0000000000..ea7ce5c55b --- /dev/null +++ b/res/img/feather-icons/settings.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/res/img/feather-icons/share.svg b/res/img/feather-icons/share.svg new file mode 100644 index 0000000000..a012e1b7a5 --- /dev/null +++ b/res/img/feather-icons/share.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/res/img/feather-icons/user-add.svg b/res/img/feather-icons/user-add.svg new file mode 100644 index 0000000000..cbb25934c1 --- /dev/null +++ b/res/img/feather-icons/user-add.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/res/img/feather-icons/user.svg b/res/img/feather-icons/user.svg new file mode 100644 index 0000000000..a789e580d5 --- /dev/null +++ b/res/img/feather-icons/user.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/res/img/feather-icons/users.svg b/res/img/feather-icons/users.svg new file mode 100644 index 0000000000..b0deac0a9e --- /dev/null +++ b/res/img/feather-icons/users.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/res/img/feather-icons/video.svg b/res/img/feather-icons/video.svg new file mode 100644 index 0000000000..a4c456832f --- /dev/null +++ b/res/img/feather-icons/video.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/res/img/icon-call.svg b/res/img/icon-call.svg index 0ca5c29e9d..98677e3c70 100644 --- a/res/img/icon-call.svg +++ b/res/img/icon-call.svg @@ -1,8 +1,3 @@ - - - - - - - + + diff --git a/res/img/icon-invite-people.svg b/res/img/icon-invite-people.svg index f13a03ed70..73500ebe06 100644 --- a/res/img/icon-invite-people.svg +++ b/res/img/icon-invite-people.svg @@ -1,24 +1,5 @@ - - - - 9BA71BF4-DC4F-42D2-B2D0-9EAE0F7F8D45 - Created with sketchtool. - - - - - - - - - - - - - - - - - - + + + + diff --git a/res/img/icon-jump-to-first-unread.svg b/res/img/icon-jump-to-first-unread.svg new file mode 100644 index 0000000000..652ccec20d --- /dev/null +++ b/res/img/icon-jump-to-first-unread.svg @@ -0,0 +1,16 @@ + + + + + diff --git a/res/img/icon_context.svg b/res/img/icon_context.svg new file mode 100644 index 0000000000..600c5bbd1d --- /dev/null +++ b/res/img/icon_context.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/icon_context_copy.svg b/res/img/icon_context_copy.svg new file mode 100644 index 0000000000..1f9c0b01e8 --- /dev/null +++ b/res/img/icon_context_copy.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/icon_context_edit.svg b/res/img/icon_context_edit.svg new file mode 100644 index 0000000000..6f7f1fd385 --- /dev/null +++ b/res/img/icon_context_edit.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/icon_context_fave.svg b/res/img/icon_context_fave.svg index da7b14a1f4..5c287787bb 100644 --- a/res/img/icon_context_fave.svg +++ b/res/img/icon_context_fave.svg @@ -1,12 +1,3 @@ - - - - - - - - - - - + + diff --git a/res/img/icon_context_leave.svg b/res/img/icon_context_leave.svg new file mode 100644 index 0000000000..3fdd452a59 --- /dev/null +++ b/res/img/icon_context_leave.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/img/icon_context_reply.svg b/res/img/icon_context_reply.svg new file mode 100644 index 0000000000..0a85f8e606 --- /dev/null +++ b/res/img/icon_context_reply.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/icons-apps.svg b/res/img/icons-apps.svg index affd8e6408..e2fe49b005 100644 --- a/res/img/icons-apps.svg +++ b/res/img/icons-apps.svg @@ -1,14 +1,12 @@ - - - - - - - - - - - + + + + + + + + + + diff --git a/res/img/icons-checkmark.svg b/res/img/icons-checkmark.svg deleted file mode 100644 index 3c5392003d..0000000000 --- a/res/img/icons-checkmark.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - Tick - Created with Sketch. - - - - - - - - - - - - diff --git a/res/img/icons-close.svg b/res/img/icons-close.svg index 453b51082f..b2dd44fc26 100644 --- a/res/img/icons-close.svg +++ b/res/img/icons-close.svg @@ -1,23 +1,96 @@ - - - - -icons_create_room -Created with sketchtool. - - - - - - - - - - - - - - + + + +image/svg+xmlicons_create_room + +icons_create_room +Created with sketchtool. + + + + + + + + + + + + + + diff --git a/res/img/icons-create-room.svg b/res/img/icons-create-room.svg index 252bd2df3b..78c45563d1 100644 --- a/res/img/icons-create-room.svg +++ b/res/img/icons-create-room.svg @@ -1,18 +1,4 @@ - - - - 0F9BCC43-B3A7-4C9F-8E34-1F38194362C2 - Created with sketchtool. - - - - - - - - - - - - + + + diff --git a/res/img/icons-files.svg b/res/img/icons-files.svg index 97ba4228e3..ea270fbc73 100644 --- a/res/img/icons-files.svg +++ b/res/img/icons-files.svg @@ -1,29 +1,5 @@ - - - - 7C98C075-AB4D-45A3-85F9-CCD46F84DA7F - Created with sketchtool. - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + diff --git a/res/img/icons-groups-nobg.svg b/res/img/icons-groups-nobg.svg new file mode 100644 index 0000000000..a3d223b76d --- /dev/null +++ b/res/img/icons-groups-nobg.svg @@ -0,0 +1,60 @@ + +image/svg+xml + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/img/icons-notifications.svg b/res/img/icons-notifications.svg index 66a49d6c0c..cde30713e1 100644 --- a/res/img/icons-notifications.svg +++ b/res/img/icons-notifications.svg @@ -1,19 +1,3 @@ - - - - 5E723325-BD0B-454D-BE25-638AF09A97AC - Created with sketchtool. - - - - - - - - - - - - - - \ No newline at end of file + + + diff --git a/res/img/icons-people.svg b/res/img/icons-people.svg index 8854506127..f3761c6c5f 100644 --- a/res/img/icons-people.svg +++ b/res/img/icons-people.svg @@ -1,22 +1,3 @@ - - - - 81230A28-D944-4572-B5DB-C03CAA2B1FCA - Created with sketchtool. - - - - - - - - - - - - - - - - + + diff --git a/res/img/icons-room-add.svg b/res/img/icons-room-add.svg index fc0ab750b6..f0b7584df9 100644 --- a/res/img/icons-room-add.svg +++ b/res/img/icons-room-add.svg @@ -1,23 +1,9 @@ - - - - - - - - - - - - - - + + + + + + + + diff --git a/res/img/icons-room-nobg.svg b/res/img/icons-room-nobg.svg new file mode 100644 index 0000000000..8ca7ab272b --- /dev/null +++ b/res/img/icons-room-nobg.svg @@ -0,0 +1,28 @@ + +image/svg+xml + + + + + + + \ No newline at end of file diff --git a/res/img/icons-search-copy.svg b/res/img/icons-search-copy.svg deleted file mode 100644 index b026718b84..0000000000 --- a/res/img/icons-search-copy.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/res/img/icons-search.svg b/res/img/icons-search.svg index d85709e66c..9d3e98106b 100644 --- a/res/img/icons-search.svg +++ b/res/img/icons-search.svg @@ -1,9 +1,15 @@ - - - - - - - - + + + Shape + Created with Sketch. + + + + + + + + + + \ No newline at end of file diff --git a/res/img/icons-settings-room.svg b/res/img/icons-settings-room.svg index 117d134c95..421eefdefa 100644 --- a/res/img/icons-settings-room.svg +++ b/res/img/icons-settings-room.svg @@ -1,15 +1,6 @@ - - - - 69011392-CE9D-4404-A85C-A8548C5D850B - Created with sketchtool. - - - - - - - - - + + + + + diff --git a/res/img/icons-share.svg b/res/img/icons-share.svg index b27616d5d5..aac19080f4 100644 --- a/res/img/icons-share.svg +++ b/res/img/icons-share.svg @@ -1,6 +1,6 @@ - + diff --git a/res/img/icons-stickers.svg b/res/img/icons-stickers.svg new file mode 100644 index 0000000000..564ebdac97 --- /dev/null +++ b/res/img/icons-stickers.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/img/icons-upload.svg b/res/img/icons-upload.svg index b0101e87a0..3aea924478 100644 --- a/res/img/icons-upload.svg +++ b/res/img/icons-upload.svg @@ -1,12 +1,3 @@ - - - - - - - - - - - + + diff --git a/res/img/icons-video.svg b/res/img/icons-video.svg index d367f49609..c61a782cc4 100644 --- a/res/img/icons-video.svg +++ b/res/img/icons-video.svg @@ -1,20 +1,3 @@ - - - - 05D354CE-86A7-4B6F-B9BE-F1CEBBD81B21 - Created with sketchtool. - - - - - - - - - - - - - - + + diff --git a/res/img/maximise.svg b/res/img/maximise.svg index 79c6c0ab8b..981e3796de 100644 --- a/res/img/maximise.svg +++ b/res/img/maximise.svg @@ -1,19 +1,19 @@ - - - -minimise -Created with sketchtool. - - - - - - - - - - - - + + + +minimise +Created with sketchtool. + + + + + + + + + + + + diff --git a/res/img/minimise.svg b/res/img/minimise.svg index 491756b15a..eecf181f61 100644 --- a/res/img/minimise.svg +++ b/res/img/minimise.svg @@ -5,7 +5,7 @@ Created with sketchtool. - + diff --git a/res/img/search.svg b/res/img/search.svg deleted file mode 100644 index bd4cd9200c..0000000000 --- a/res/img/search.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - icons_search - Created with bin/sketchtool. - - - - - - - - - - - - \ No newline at end of file diff --git a/res/img/topleft-chevron.svg b/res/img/topleft-chevron.svg new file mode 100644 index 0000000000..1cfeaf6352 --- /dev/null +++ b/res/img/topleft-chevron.svg @@ -0,0 +1,86 @@ + + + + + + image/svg+xml + + dropdown + + + + + + dropdown + Created with Sketch. + + + + + + + + + + + + diff --git a/res/img/typing-indicator-2x.gif b/res/img/typing-indicator-2x.gif new file mode 100644 index 0000000000..86e34c7555 Binary files /dev/null and b/res/img/typing-indicator-2x.gif differ diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 5dbc00af4e..997a74e6aa 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -1,6 +1,6 @@ // typical text (dark-on-white in light skin) -$primary-fg-color: #dddddd; +$primary-fg-color: #212121; $primary-bg-color: #2d2d2d; // used for focusing form controls @@ -12,6 +12,7 @@ $light-fg-color: #747474; // button UI (white-on-green in light skin) $accent-fg-color: $primary-bg-color; $accent-color: #76CFA6; +$accent-color-alt: $accent-color; $accent-color-50pct: #76CFA67F; $selection-fg-color: $primary-fg-color; @@ -63,6 +64,10 @@ $primary-hairline-color: #474747; // used for the border of input text fields $input-border-color: #3a3a3a; +$input-darker-bg-color: #c1c9d6; +$input-darker-fg-color: #9fa9ba; +$button-bg-color: #7ac9a1; +$button-fg-color: white; // apart from login forms, which have stronger border $strong-input-border-color: #656565; @@ -73,9 +78,11 @@ $input-fg-color: $primary-fg-color; // context menus $menu-border-color: rgba(187, 187, 187, 0.5); $menu-bg-color: #373737; +$menu-selected-color: #f5f8fa; $avatar-initial-color: #2d2d2d; $avatar-bg-color: #ffffff; +$menu-selected-color: #f5f8fa; $h3-color: $primary-fg-color; @@ -108,6 +115,15 @@ $roomtile-name-color: rgba(186, 186, 186, 0.8); $roomtile-selected-bg-color: #333; $roomtile-focused-bg-color: rgba(255, 255, 255, 0.2); +$username-variant1-color: #1e7ddc; +$username-variant2-color: #a756a8; +$username-variant3-color: #7ac9a1; +$username-variant4-color: #f2809d; +$username-variant5-color: #ffc666; +$username-variant6-color: #76ddd7; +$username-variant7-color: #45529b; +$username-variant8-color: #bfd251; + $roomsublist-background: rgba(0, 0, 0, 0.2); $roomsublist-label-fg-color: $h3-color; $roomsublist-label-bg-color: $tertiary-accent-color; @@ -148,6 +164,7 @@ $lightbox-border-color: #ffffff; $imagebody-giflabel: rgba(1, 1, 1, 0.7); $imagebody-giflabel-border: rgba(1, 1, 1, 0.2); +$imagebody-giflabel-color: rgba(0, 0, 0, 1); // unused? $progressbar-color: #000; diff --git a/res/themes/dharma/css/_dharma.scss b/res/themes/dharma/css/_dharma.scss new file mode 100644 index 0000000000..c70d7f020a --- /dev/null +++ b/res/themes/dharma/css/_dharma.scss @@ -0,0 +1,335 @@ +@import "_fonts.scss"; + +// XXX: check this? +/* Nunito lacks combining diacritics, so these will fall through + to the next font. Helevetica's diacritics however do not combine + nicely with Open Sans (on OSX, at least) and result in a huge + horizontal mess. Arial empirically gets it right, hence prioritising + Arial here. */ +$font-family: 'Nunito', Arial, Helvetica, Sans-Serif; + +// typical text (dark-on-white in light skin) +$primary-fg-color: #454545; +$primary-bg-color: #ffffff; + +// used for dialog box text +$light-fg-color: #747474; + +// used for focusing form controls +$focus-bg-color: #dddddd; + +// button UI (white-on-green in light skin) +$accent-fg-color: #ffffff; +$accent-color: #7ac9a1; +$accent-color-50pct: #92caad; +$accent-color-alt: #238CF5; + +$selection-fg-color: $primary-bg-color; + +$focus-brightness: 105%; + +// red warning colour +$warning-color: #f56679; +// background colour for warnings +$warning-bg-color: #DF2A8B; +$info-bg-color: #2A9EDF; +$mention-user-pill-bg-color: $warning-color; +$other-user-pill-bg-color: rgba(0, 0, 0, 0.1); + +// pinned events indicator +$pinned-unread-color: #ff0064; // $warning-color +$pinned-color: #888; + +// informational plinth +$info-plinth-bg-color: #f7f7f7; +$info-plinth-fg-color: #888; + +$preview-bar-bg-color: #f7f7f7; + +// left-panel style muted accent color +$secondary-accent-color: #f2f5f8; +$tertiary-accent-color: #d3efe1; + +$tagpanel-bg-color: #2e3649; + +// used by RoomDirectory permissions +$plinth-bg-color: $secondary-accent-color; + +// used by RoomDropTarget +$droptarget-bg-color: rgba(255,255,255,0.5); + +// used by AddressSelector +$selected-color: $secondary-accent-color; + +// selected for hoverover & selected event tiles +$event-selected-color: #f7f7f7; + +// used for the hairline dividers in RoomView +$primary-hairline-color: #e5e5e5; + +// used for the border of input text fields +$input-border-color: #e7e7e7; +$input-darker-bg-color: rgba(193, 201, 214, 0.29); +$input-darker-fg-color: #9fa9ba; +$input-lighter-bg-color: #f2f5f8; +$input-lighter-fg-color: $input-darker-fg-color; + +$button-bg-color: #7ac9a1; +$button-fg-color: white; + +// apart from login forms, which have stronger border +$strong-input-border-color: #c7c7c7; + +// used for UserSettings EditableText +$input-underline-color: rgba(151, 151, 151, 0.5); +$input-fg-color: rgba(74, 74, 74, 0.9); + +// context menus +$menu-border-color: #ebedf8; +$menu-bg-color: #fff; +$menu-selected-color: #f5f8fa; + +$avatar-initial-color: #ffffff; +$avatar-bg-color: #ffffff; + +$h3-color: #3d3b39; + +$dialog-background-bg-color: #e9e9e9; +$lightbox-background-bg-color: #000; + +$imagebody-giflabel: rgba(0, 0, 0, 0.7); +$imagebody-giflabel-border: rgba(0, 0, 0, 0.2); +$imagebody-giflabel-color: rgba(255, 255, 255, 1); + +$greyed-fg-color: #888; + +$neutral-badge-color: #dbdbdb; + +$preview-widget-bar-color: #ddd; +$preview-widget-fg-color: $greyed-fg-color; + +$blockquote-bar-color: #ddd; +$blockquote-fg-color: #777; + +$settings-grey-fg-color: #a2a2a2; + +$voip-decline-color: #f48080; +$voip-accept-color: #80f480; + +$rte-bg-color: #e9e9e9; +$rte-code-bg-color: rgba(0, 0, 0, 0.04); +$rte-room-pill-color: #aaa; +$rte-group-pill-color: #aaa; + +$topleftmenu-color: #212121; +$roomheader-color: #45474a; +$roomheader-addroom-color: #91A1C0; +$roomtopic-color: #9fa9ba; +$eventtile-meta-color: $roomtopic-color; + +// ******************** + +$roomtile-name-color: #61708b; +$roomtile-selected-color: #212121; +$roomtile-notified-color: #212121; +$roomtile-selected-bg-color: #fff; +$roomtile-focused-bg-color: #fff; + +$username-variant1-color: #1e7ddc; +$username-variant2-color: #a756a8; +$username-variant3-color: #7ac9a1; +$username-variant4-color: #f2809d; +$username-variant5-color: #ffc666; +$username-variant6-color: #76ddd7; +$username-variant7-color: #45529b; +$username-variant8-color: #bfd251; + +$roomtile-transparent-focused-color: rgba(0, 0, 0, 0.1); + +$roomsublist-background: $secondary-accent-color; +$roomsublist-label-fg-color: $roomtile-name-color; +$roomsublist-label-bg-color: $tertiary-accent-color; +$roomsublist-chevron-color: $accent-color; + +$panel-divider-color: #dee1f3; + +// ******************** + +$widget-menu-bar-bg-color: $tertiary-accent-color; + +// ******************** + +// event tile lifecycle +$event-encrypting-color: #abddbc; +$event-sending-color: #ddd; +$event-notsent-color: #f44; + +// event redaction +$event-redacted-fg-color: #e2e2e2; +$event-redacted-border-color: #cccccc; + +// event timestamp +$event-timestamp-color: #acacac; + +$edit-button-url: "../../img/icon_context_message.svg"; +$copy-button-url: "../../img/icon_copy_message.svg"; + +// e2e +$e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color +$e2e-unverified-color: #e8bf37; +$e2e-warning-color: #ba6363; + +/*** ImageView ***/ +$lightbox-bg-color: #454545; +$lightbox-fg-color: #ffffff; +$lightbox-border-color: #ffffff; + +// unused? +$progressbar-color: #000; + +$room-warning-bg-color: #fff8e3; + +$memberstatus-placeholder-color: $roomtile-name-color; + +/*** form elements ***/ + +// .mx_textinput is a container for a text input +// + some other controls like buttons, ... +// it has the appearance of a text box so the controls +// appear to be part of the input + +.mx_MatrixChat { + + :not(.mx_textinput) > input[type=text], + :not(.mx_textinput) > input[type=search], + .mx_textinput { + display: block; + margin: 9px; + box-sizing: border-box; + background-color: transparent; + color: $input-darker-fg-color; + border-radius: 4px; + border: 1px solid #c1c1c1; + flex: 0 0 auto; + } + + .mx_textinput { + display: flex; + align-items: center; + + > input[type=text], + > input[type=search] { + border: none; + flex: 1; + color: $primary-fg-color; + }, + input::placeholder { + color: $roomsublist-label-fg-color; + } + } +} + +input[type=text], +input[type=search], +input[type=password] { + padding: 9px; + font-family: $font-family; + font-size: 14px; + font-weight: 600; + min-width: 0; +} + +/*** panels ***/ +.dark-panel { + background-color: $secondary-accent-color; +} + +.dark-panel { + :not(.mx_textinput) > input[type=text], + :not(.mx_textinput) > input[type=search], + .mx_textinput { + color: $input-darker-fg-color; + background-color: $input-darker-bg-color; + border: none; + } +} + +.light-panel { + :not(.mx_textinput) > input[type=text], + :not(.mx_textinput) > input[type=search], + .mx_textinput { + color: $input-lighter-fg-color; + background-color: $input-lighter-bg-color; + border: none; + } +} + +input[type=text].mx_textinput_icon, +input[type=search].mx_textinput_icon { + padding-left: 36px; + background-repeat: no-repeat; + background-position: 10px center; +} + + +// FIXME THEME - Tint by CSS rather than referencing a duplicate asset +input[type=text].mx_textinput_icon.mx_textinput_search, +input[type=search].mx_textinput_icon.mx_textinput_search { + background-image: url('../../img/feather-icons/search-input.svg'); +} + +// dont search UI as not all browsers support it, +// we implement it ourselves where needed instead +input[type=search]::-webkit-search-decoration, +input[type=search]::-webkit-search-cancel-button, +input[type=search]::-webkit-search-results-button, +input[type=search]::-webkit-search-results-decoration { + display: none; +} + +.input[type=text]::-webkit-input-placeholder, +.input[type=text]::-moz-placeholder, +.input[type=search]::-webkit-input-placeholder, +.input[type=search]::-moz-placeholder { + color: #a5aab2; +} +// ***** Mixins! ***** + +@define-mixin mx_DialogButton { + /* align images in buttons (eg spinners) */ + vertical-align: middle; + border: 0px; + border-radius: 4px; + font-family: $font-family; + font-size: 14px; + color: $button-fg-color; + background-color: $button-bg-color; + width: auto; + padding: 7px; + padding-left: 1.5em; + padding-right: 1.5em; + cursor: pointer; + display: inline-block; + outline: none; +} + +@define-mixin mx_DialogButton_hover { +} + +@define-mixin mx_DialogButton_danger { + background-color: $accent-color; +} + +@define-mixin mx_DialogButton_small { + @mixin mx_DialogButton; + font-size: 15px; + padding: 0px 1.5em 0px 1.5em; +} + +@define-mixin mx_DialogButton_secondary { + // flip colours for the secondary ones + font-weight: 600; + border: 1px solid $accent-color ! important; + color: $accent-color; + background-color: $accent-fg-color; +} diff --git a/res/themes/dharma/css/_fonts.scss b/res/themes/dharma/css/_fonts.scss new file mode 100644 index 0000000000..bb45432262 --- /dev/null +++ b/res/themes/dharma/css/_fonts.scss @@ -0,0 +1,64 @@ +/* + * Nunito. + * Includes extended Latin and Vietnamese character sets + * Current URLs are v9, derived from the contents of + * https://fonts.googleapis.com/css?family=Nunito:400,400i,600,600i,700,700i&subset=latin-ext,vietnamese + */ + +/* the 'src' links are relative to the bundle.css, which is in a subdirectory. + */ +@font-face { + font-family: 'Nunito'; + font-style: italic; + font-weight: 400; + src: url('../../fonts/Nunito/XRXX3I6Li01BKofIMNaDRss.ttf') format('truetype'); +} +@font-face { + font-family: 'Nunito'; + font-style: italic; + font-weight: 600; + src: url('../../fonts/Nunito/XRXQ3I6Li01BKofIMN5cYtvKUTo.ttf') format('truetype'); +} +@font-face { + font-family: 'Nunito'; + font-style: italic; + font-weight: 700; + src: url('../../fonts/Nunito/XRXQ3I6Li01BKofIMN44Y9vKUTo.ttf') format('truetype'); +} +@font-face { + font-family: 'Nunito'; + font-style: normal; + font-weight: 400; + src: url('../../fonts/Nunito/XRXV3I6Li01BKofINeaE.ttf') format('truetype'); +} +@font-face { + font-family: 'Nunito'; + font-style: normal; + font-weight: 600; + src: url('../../fonts/Nunito/XRXW3I6Li01BKofA6sKUYevN.ttf') format('truetype'); +} +@font-face { + font-family: 'Nunito'; + font-style: normal; + font-weight: 700; + src: url('../../fonts/Nunito/XRXW3I6Li01BKofAjsOUYevN.ttf') format('truetype'); +} + +/* + * Fira Mono + * Used for monospace copy, i.e. code + */ + +@font-face { + font-family: 'Fira Mono'; + src: url('../../fonts/Fira_Mono/FiraMono-Regular.ttf') format('truetype'); + font-weight: 400; + font-style: normal; +} + +@font-face { + font-family: 'Fira Mono'; + src: url('../../fonts/Fira_Mono/FiraMono-Bold.ttf') format('truetype'); + font-weight: 700; + font-style: normal; +} diff --git a/res/themes/dharma/css/dharma.scss b/res/themes/dharma/css/dharma.scss new file mode 100644 index 0000000000..0f4db55fd2 --- /dev/null +++ b/res/themes/dharma/css/dharma.scss @@ -0,0 +1,3 @@ +@import "_dharma.scss"; +@import "../../../../res/css/_components.scss"; + diff --git a/res/themes/light/css/_base.scss b/res/themes/light/css/_base.scss index c275b94fb5..96c179f6f5 100644 --- a/res/themes/light/css/_base.scss +++ b/res/themes/light/css/_base.scss @@ -1,3 +1,5 @@ +@import "_fonts.scss"; + /* Open Sans lacks combining diacritics, so these will fall through to the next font. Helevetica's diacritics however do not combine nicely with Open Sans (on OSX, at least) and result in a huge @@ -18,6 +20,7 @@ $focus-bg-color: #dddddd; // button UI (white-on-green in light skin) $accent-fg-color: #ffffff; $accent-color: #76CFA6; +$accent-color-alt: $accent-color; $accent-color-50pct: #76CFA67F; $selection-fg-color: $primary-bg-color; @@ -46,6 +49,8 @@ $preview-bar-bg-color: #f7f7f7; $secondary-accent-color: #eaf5f0; $tertiary-accent-color: #d3efe1; +$tagpanel-bg-color: $tertiary-accent-color; + // used by RoomDirectory permissions $plinth-bg-color: $secondary-accent-color; @@ -64,6 +69,10 @@ $primary-hairline-color: #e5e5e5; // used for the border of input text fields $input-border-color: #f0f0f0; +$input-darker-bg-color: #c1c9d6; +$input-darker-fg-color: #9fa9ba; +$button-bg-color: #7ac9a1; +$button-fg-color: white; // apart from login forms, which have stronger border $strong-input-border-color: #c7c7c7; @@ -74,6 +83,7 @@ $input-fg-color: rgba(74, 74, 74, 0.9); // context menus $menu-border-color: rgba(187, 187, 187, 0.5); $menu-bg-color: #f6f6f6; +$menu-selected-color: #f5f8fa; $avatar-initial-color: #ffffff; $avatar-bg-color: #ffffff; @@ -103,12 +113,28 @@ $rte-code-bg-color: rgba(0, 0, 0, 0.04); $rte-room-pill-color: #aaa; $rte-group-pill-color: #aaa; +$topleftmenu-color: $primary-fg-color; +$roomheader-color: $primary-fg-color; +$roomheader-addroom-color: $primary-bg-color; +$roomtopic-color: $settings-grey-fg-color; +$eventtile-meta-color: $roomtopic-color; // ******************** $roomtile-name-color: rgba(69, 69, 69, 0.8); +$roomtile-selected-color: $roomtile-name-color; +$roomtile-notified-color: $roomtile-name-color; $roomtile-selected-bg-color: rgba(255, 255, 255, 0.8); $roomtile-focused-bg-color: rgba(255, 255, 255, 0.9); +$username-variant1-color: #1e7ddc; +$username-variant2-color: #a756a8; +$username-variant3-color: #7ac9a1; +$username-variant4-color: #f2809d; +$username-variant5-color: #ffc666; +$username-variant6-color: #76ddd7; +$username-variant7-color: #45529b; +$username-variant8-color: #bfd251; + $roomtile-transparent-focused-color: rgba(0, 0, 0, 0.1); $roomsublist-background: rgba(0, 0, 0, 0.05); @@ -151,12 +177,15 @@ $lightbox-border-color: #ffffff; $imagebody-giflabel: rgba(0, 0, 0, 0.7); $imagebody-giflabel-border: rgba(0, 0, 0, 0.2); +$imagebody-giflabel-color: rgba(255, 255, 255, 1); // unused? $progressbar-color: #000; $room-warning-bg-color: #fff8e3; +$memberstatus-placeholder-color: $roomtile-name-color; + // ***** Mixins! ***** @define-mixin mx_DialogButton { diff --git a/res/css/_fonts.scss b/res/themes/light/css/_fonts.scss similarity index 100% rename from res/css/_fonts.scss rename to res/themes/light/css/_fonts.scss diff --git a/scripts/fetchdep.sh b/scripts/fetchdep.sh index 4f42859439..95fc4b0603 100755 --- a/scripts/fetchdep.sh +++ b/scripts/fetchdep.sh @@ -1,7 +1,5 @@ #!/bin/sh -set -e - org="$1" repo="$2" defbranch="$3" @@ -10,16 +8,20 @@ defbranch="$3" rm -r "$repo" || true -curbranch="$TRAVIS_PULL_REQUEST_BRANCH" -[ -z "$curbranch" ] && curbranch="$TRAVIS_BRANCH" -[ -z "$curbranch" ] && curbranch=`"echo $GIT_BRANCH" | sed -e 's/^origin\///'` # jenkins +clone() { + branch=$1 + if [ -n "$branch" ] + then + echo "Trying to use the branch $branch" + git clone https://github.com/$org/$repo.git $repo --branch "$branch" && exit 0 + fi +} -if [ -n "$curbranch" ] -then - echo "Determined branch to be $curbranch" - - git clone https://github.com/$org/$repo.git $repo --branch "$curbranch" && exit 0 -fi - -echo "Checking out default branch $defbranch" -git clone https://github.com/$org/$repo.git $repo --branch $defbranch +# Try the PR author's branch in case it exists on the deps as well. +clone $TRAVIS_PULL_REQUEST_BRANCH +# Try the target branch of the push or PR. +clone $TRAVIS_BRANCH +# Try the current branch from Jenkins. +clone `"echo $GIT_BRANCH" | sed -e 's/^origin\///'` +# Use the default branch as the last resort. +clone $defbranch diff --git a/.travis-test-riot.sh b/scripts/travis/build.sh similarity index 61% rename from .travis-test-riot.sh rename to scripts/travis/build.sh index d1c2804b2a..a353e38a06 100755 --- a/.travis-test-riot.sh +++ b/scripts/travis/build.sh @@ -24,18 +24,4 @@ rm -r node_modules/matrix-react-sdk ln -s "$REACT_SDK_DIR" node_modules/matrix-react-sdk npm run build -npm run test popd - -if [ "$TRAVIS_BRANCH" = "develop" ] -then - # run end to end tests - scripts/fetchdep.sh matrix-org matrix-react-end-to-end-tests master - pushd matrix-react-end-to-end-tests - ln -s $REACT_SDK_DIR/$RIOT_WEB_DIR riot/riot-web - # PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh - # CHROME_PATH=$(which google-chrome-stable) ./run.sh - ./install.sh - ./run.sh --travis - popd -fi diff --git a/scripts/travis/end-to-end-tests.sh b/scripts/travis/end-to-end-tests.sh new file mode 100755 index 0000000000..361b053d2b --- /dev/null +++ b/scripts/travis/end-to-end-tests.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# script which is run by the travis build (after `npm run test`). +# +# clones riot-web develop and runs the tests against our version of react-sdk. + +set -ev + +RIOT_WEB_DIR=riot-web +REACT_SDK_DIR=`pwd` + +scripts/travis/build.sh +# run end to end tests +scripts/fetchdep.sh matrix-org matrix-react-end-to-end-tests master +pushd matrix-react-end-to-end-tests +ln -s $REACT_SDK_DIR/$RIOT_WEB_DIR riot/riot-web +# PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh +# CHROME_PATH=$(which google-chrome-stable) ./run.sh +./install.sh +./run.sh --travis +popd diff --git a/scripts/travis.sh b/scripts/travis/install-deps.sh similarity index 57% rename from scripts/travis.sh rename to scripts/travis/install-deps.sh index 48410ea904..04cd728157 100755 --- a/scripts/travis.sh +++ b/scripts/travis/install-deps.sh @@ -1,7 +1,7 @@ #!/bin/sh set -ex - +npm install scripts/fetchdep.sh matrix-org matrix-js-sdk rm -r node_modules/matrix-js-sdk || true ln -s ../matrix-js-sdk node_modules/matrix-js-sdk @@ -9,9 +9,3 @@ ln -s ../matrix-js-sdk node_modules/matrix-js-sdk cd matrix-js-sdk npm install cd .. - -npm run test -./.travis-test-riot.sh - -# run the linter, but exclude any files known to have errors or warnings. -npm run lintwithexclusions diff --git a/scripts/travis/riot-unit-tests.sh b/scripts/travis/riot-unit-tests.sh new file mode 100755 index 0000000000..a2f0d61112 --- /dev/null +++ b/scripts/travis/riot-unit-tests.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# +# script which is run by the travis build (after `npm run test`). +# +# clones riot-web develop and runs the tests against our version of react-sdk. + +set -ev + +RIOT_WEB_DIR=riot-web + +scripts/travis/build.sh +pushd "$RIOT_WEB_DIR" +npm run test +popd diff --git a/scripts/travis/unit-tests.sh b/scripts/travis/unit-tests.sh new file mode 100755 index 0000000000..a8e0a63b31 --- /dev/null +++ b/scripts/travis/unit-tests.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# +# script which is run by the travis build (after `npm run test`). +# +# clones riot-web develop and runs the tests against our version of react-sdk. + +set -ev + +scripts/travis/build.sh +npm run test diff --git a/src/Notifier.js b/src/Notifier.js index 8550f3bf95..80e8be1084 100644 --- a/src/Notifier.js +++ b/src/Notifier.js @@ -289,11 +289,6 @@ const Notifier = { const room = MatrixClientPeg.get().getRoom(ev.getRoomId()); const actions = MatrixClientPeg.get().getPushActionsForEvent(ev); if (actions && actions.notify) { - dis.dispatch({ - action: "event_notification", - event: ev, - room: room, - }); if (this.isEnabled()) { this._displayPopupNotification(ev, room); } diff --git a/src/PageTypes.js b/src/PageTypes.js index 66d930c288..60111723fb 100644 --- a/src/PageTypes.js +++ b/src/PageTypes.js @@ -20,7 +20,6 @@ export default { HomePage: "home_page", RoomView: "room_view", UserSettings: "user_settings", - CreateRoom: "create_room", RoomDirectory: "room_directory", UserView: "user_view", GroupView: "group_view", diff --git a/src/PhasedRollOut.js b/src/PhasedRollOut.js index a9029d07e6..b17ed37974 100644 --- a/src/PhasedRollOut.js +++ b/src/PhasedRollOut.js @@ -15,21 +15,7 @@ limitations under the License. */ import SdkConfig from './SdkConfig'; - -function hashCode(str) { - let hash = 0; - let i; - let chr; - if (str.length === 0) { - return hash; - } - for (i = 0; i < str.length; i++) { - chr = str.charCodeAt(i); - hash = ((hash << 5) - hash) + chr; - hash |= 0; - } - return Math.abs(hash); -} +import {hashCode} from './utils/FormattingUtils'; export function phasedRollOutExpiredForUser(username, feature, now, rollOutConfig = SdkConfig.get().phasedRollOut) { if (!rollOutConfig) { diff --git a/src/Presence.js b/src/Presence.js index b1e85e4bc7..849efdef1c 100644 --- a/src/Presence.js +++ b/src/Presence.js @@ -17,21 +17,33 @@ limitations under the License. const MatrixClientPeg = require("./MatrixClientPeg"); const dis = require("./dispatcher"); +import Timer from './utils/Timer'; // Time in ms after that a user is considered as unavailable/away const UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins const PRESENCE_STATES = ["online", "offline", "unavailable"]; class Presence { + + constructor() { + this._activitySignal = null; + this._unavailableTimer = null; + this._onAction = this._onAction.bind(this); + this._dispatcherRef = null; + } /** * Start listening the user activity to evaluate his presence state. * Any state change will be sent to the Home Server. */ - start() { - this.running = true; - if (undefined === this.state) { - this._resetTimer(); - this.dispatcherRef = dis.register(this._onAction.bind(this)); + async start() { + this._unavailableTimer = new Timer(UNAVAILABLE_TIME_MS); + // the user_activity_start action starts the timer + this._dispatcherRef = dis.register(this._onAction); + while (this._unavailableTimer) { + try { + await this._unavailableTimer.finished(); + this.setState("unavailable"); + } catch(e) { /* aborted, stop got called */ } } } @@ -39,13 +51,14 @@ class Presence { * Stop tracking user activity */ stop() { - this.running = false; - if (this.timer) { - clearInterval(this.timer); - this.timer = undefined; - dis.unregister(this.dispatcherRef); + if (this._dispatcherRef) { + dis.unregister(this._dispatcherRef); + this._dispatcherRef = null; + } + if (this._unavailableTimer) { + this._unavailableTimer.abort(); + this._unavailableTimer = null; } - this.state = undefined; } /** @@ -56,21 +69,25 @@ class Presence { return this.state; } + _onAction(payload) { + if (payload.action === 'user_activity') { + this.setState("online"); + this._unavailableTimer.restart(); + } + } + /** * Set the presence state. * If the state has changed, the Home Server will be notified. * @param {string} newState the new presence state (see PRESENCE enum) */ - setState(newState) { + async setState(newState) { if (newState === this.state) { return; } if (PRESENCE_STATES.indexOf(newState) === -1) { throw new Error("Bad presence state: " + newState); } - if (!this.running) { - return; - } const old_state = this.state; this.state = newState; @@ -78,42 +95,14 @@ class Presence { return; // don't try to set presence when a guest; it won't work. } - const self = this; - MatrixClientPeg.get().setPresence(this.state).done(function() { + try { + await MatrixClientPeg.get().setPresence(this.state); console.log("Presence: %s", newState); - }, function(err) { + } catch(err) { console.error("Failed to set presence: %s", err); - self.state = old_state; - }); - } - - /** - * Callback called when the user made no action on the page for UNAVAILABLE_TIME ms. - * @private - */ - _onUnavailableTimerFire() { - this.setState("unavailable"); - } - - _onAction(payload) { - if (payload.action === "user_activity") { - this._resetTimer(); + this.state = old_state; } } - - /** - * Callback called when the user made an action on the page - * @private - */ - _resetTimer() { - const self = this; - this.setState("online"); - // Re-arm the timer - clearTimeout(this.timer); - this.timer = setTimeout(function() { - self._onUnavailableTimerFire(); - }, UNAVAILABLE_TIME_MS); - } } module.exports = new Presence(); diff --git a/src/SlashCommands.js b/src/SlashCommands.js index 24328d6372..47c6c26b45 100644 --- a/src/SlashCommands.js +++ b/src/SlashCommands.js @@ -135,6 +135,18 @@ export const CommandMap = { }, }), + roomname: new Command({ + name: 'roomname', + args: '', + description: _td('Sets the room name'), + runFn: function(roomId, args) { + if (args) { + return success(MatrixClientPeg.get().setRoomName(roomId, args)); + } + return reject(this.getUsage()); + }, + }), + invite: new Command({ name: 'invite', args: '', diff --git a/src/TextForEvent.js b/src/TextForEvent.js index 96cccf07fb..2a37295f83 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -129,6 +129,11 @@ function textForRoomNameEvent(ev) { }); } +function textForTombstoneEvent(ev) { + const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); + return _t('%(senderDisplayName)s upgraded this room.', {senderDisplayName}); +} + function textForServerACLEvent(ev) { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const prevContent = ev.getPrevContent(); @@ -433,6 +438,7 @@ const stateHandlers = { 'm.room.power_levels': textForPowerEvent, 'm.room.pinned_events': textForPinnedEvent, 'm.room.server_acl': textForServerACLEvent, + 'm.room.tombstone': textForTombstoneEvent, 'im.vector.modular.widgets': textForWidgetEvent, }; diff --git a/src/Tinter.js b/src/Tinter.js index 9c2afd4fab..de9ae94097 100644 --- a/src/Tinter.js +++ b/src/Tinter.js @@ -154,6 +154,8 @@ class Tinter { } tint(primaryColor, secondaryColor, tertiaryColor) { + return; + // eslint-disable-next-line no-unreachable this.currentTint[0] = primaryColor; this.currentTint[1] = secondaryColor; this.currentTint[2] = tertiaryColor; @@ -390,7 +392,7 @@ class Tinter { // XXX: we could just move this all into TintableSvg, but as it's so similar // to the CSS fixup stuff in Tinter (just that the fixups are stored in TintableSvg) // keeping it here for now. - calcSvgFixups(svgs, forceColors) { + calcSvgFixups(svgs) { // go through manually fixing up SVG colours. // we could do this by stylesheets, but keeping the stylesheets // updated would be a PITA, so just brute-force search for the @@ -418,21 +420,13 @@ class Tinter { const tag = tags[j]; for (let k = 0; k < this.svgAttrs.length; k++) { const attr = this.svgAttrs[k]; - for (let m = 0; m < this.keyHex.length; m++) { // dev note: don't use L please. - // We use a different attribute from the one we're setting - // because we may also be using forceColors. If we were to - // check the keyHex against a forceColors value, it may not - // match and therefore not change when we need it to. - const valAttrName = "mx-val-" + attr; - let attribute = tag.getAttribute(valAttrName); - if (!attribute) attribute = tag.getAttribute(attr); // fall back to the original - if (attribute && (attribute.toUpperCase() === this.keyHex[m] || attribute.toLowerCase() === this.keyRgb[m])) { + for (let l = 0; l < this.keyHex.length; l++) { + if (tag.getAttribute(attr) && + tag.getAttribute(attr).toUpperCase() === this.keyHex[l]) { fixups.push({ node: tag, attr: attr, - refAttr: valAttrName, - index: m, - forceColors: forceColors, + index: l, }); } } @@ -448,9 +442,7 @@ class Tinter { if (DEBUG) console.log("applySvgFixups start for " + fixups); for (let i = 0; i < fixups.length; i++) { const svgFixup = fixups[i]; - const forcedColor = svgFixup.forceColors ? svgFixup.forceColors[svgFixup.index] : null; - svgFixup.node.setAttribute(svgFixup.attr, forcedColor ? forcedColor : this.colors[svgFixup.index]); - svgFixup.node.setAttribute(svgFixup.refAttr, this.colors[svgFixup.index]); + svgFixup.node.setAttribute(svgFixup.attr, this.colors[svgFixup.index]); } if (DEBUG) console.log("applySvgFixups end"); } diff --git a/src/UserActivity.js b/src/UserActivity.js index c628ab4186..145b23e36e 100644 --- a/src/UserActivity.js +++ b/src/UserActivity.js @@ -15,32 +15,73 @@ limitations under the License. */ import dis from './dispatcher'; +import Timer from './utils/Timer'; -const MIN_DISPATCH_INTERVAL_MS = 500; -const CURRENTLY_ACTIVE_THRESHOLD_MS = 2000; +// important this is larger than the timeouts of timers +// used with UserActivity.timeWhileActive, +// such as READ_MARKER_INVIEW_THRESHOLD_MS, +// READ_MARKER_OUTOFVIEW_THRESHOLD_MS, +// READ_RECEIPT_INTERVAL_MS in TimelinePanel +const CURRENTLY_ACTIVE_THRESHOLD_MS = 2 * 60 * 1000; /** * This class watches for user activity (moving the mouse or pressing a key) - * and dispatches the user_activity action at times when the user is interacting - * with the app (but at a much lower frequency than mouse move events) + * and starts/stops attached timers while the user is active. */ class UserActivity { + constructor() { + this._attachedTimers = []; + this._activityTimeout = new Timer(CURRENTLY_ACTIVE_THRESHOLD_MS); + this._onUserActivity = this._onUserActivity.bind(this); + this._onDocumentBlurred = this._onDocumentBlurred.bind(this); + this._onPageVisibilityChanged = this._onPageVisibilityChanged.bind(this); + this.lastScreenX = 0; + this.lastScreenY = 0; + } + + /** + * Runs the given timer while the user is active, aborting when the user becomes inactive. + * Can be called multiple times with the same already running timer, which is a NO-OP. + * Can be called before the user becomes active, in which case it is only started + * later on when the user does become active. + * @param {Timer} timer the timer to use + */ + timeWhileActive(timer) { + // important this happens first + const index = this._attachedTimers.indexOf(timer); + if (index === -1) { + this._attachedTimers.push(timer); + // remove when done or aborted + timer.finished().finally(() => { + const index = this._attachedTimers.indexOf(timer); + if (index !== -1) { // should never be -1 + this._attachedTimers.splice(index, 1); + } + // as we fork the promise here, + // avoid unhandled rejection warnings + }).catch((err) => {}); + } + if (this.userCurrentlyActive()) { + timer.start(); + } + } + /** * Start listening to user activity */ start() { - document.onmousedown = this._onUserActivity.bind(this); - document.onmousemove = this._onUserActivity.bind(this); - document.onkeydown = this._onUserActivity.bind(this); + document.onmousedown = this._onUserActivity; + document.onmousemove = this._onUserActivity; + document.onkeydown = this._onUserActivity; + document.addEventListener("visibilitychange", this._onPageVisibilityChanged); + document.addEventListener("blur", this._onDocumentBlurred); + document.addEventListener("focus", this._onUserActivity); // can't use document.scroll here because that's only the document // itself being scrolled. Need to use addEventListener's useCapture. // also this needs to be the wheel event, not scroll, as scroll is // fired when the view scrolls down for a new message. - window.addEventListener('wheel', this._onUserActivity.bind(this), + window.addEventListener('wheel', this._onUserActivity, { passive: true, capture: true }); - this.lastActivityAtTs = new Date().getTime(); - this.lastDispatchAtTs = 0; - this.activityEndTimer = undefined; } /** @@ -50,8 +91,12 @@ class UserActivity { document.onmousedown = undefined; document.onmousemove = undefined; document.onkeydown = undefined; - window.removeEventListener('wheel', this._onUserActivity.bind(this), + window.removeEventListener('wheel', this._onUserActivity, { passive: true, capture: true }); + + document.removeEventListener("visibilitychange", this._onPageVisibilityChanged); + document.removeEventListener("blur", this._onDocumentBlurred); + document.removeEventListener("focus", this._onUserActivity); } /** @@ -60,10 +105,22 @@ class UserActivity { * @returns {boolean} true if user is currently/very recently active */ userCurrentlyActive() { - return this.lastActivityAtTs > new Date().getTime() - CURRENTLY_ACTIVE_THRESHOLD_MS; + return this._activityTimeout.isRunning(); } - _onUserActivity(event) { + _onPageVisibilityChanged(e) { + if (document.visibilityState === "hidden") { + this._activityTimeout.abort(); + } else { + this._onUserActivity(e); + } + } + + _onDocumentBlurred() { + this._activityTimeout.abort(); + } + + async _onUserActivity(event) { if (event.screenX && event.type === "mousemove") { if (event.screenX === this.lastScreenX && event.screenY === this.lastScreenY) { // mouse hasn't actually moved @@ -73,30 +130,20 @@ class UserActivity { this.lastScreenY = event.screenY; } - this.lastActivityAtTs = new Date().getTime(); - if (this.lastDispatchAtTs < this.lastActivityAtTs - MIN_DISPATCH_INTERVAL_MS) { - this.lastDispatchAtTs = this.lastActivityAtTs; - dis.dispatch({ - action: 'user_activity', - }); - if (!this.activityEndTimer) { - this.activityEndTimer = setTimeout(this._onActivityEndTimer.bind(this), MIN_DISPATCH_INTERVAL_MS); - } - } - } - - _onActivityEndTimer() { - const now = new Date().getTime(); - const targetTime = this.lastActivityAtTs + MIN_DISPATCH_INTERVAL_MS; - if (now >= targetTime) { - dis.dispatch({ - action: 'user_activity_end', - }); - this.activityEndTimer = undefined; + dis.dispatch({action: 'user_activity'}); + if (!this._activityTimeout.isRunning()) { + this._activityTimeout.start(); + dis.dispatch({action: 'user_activity_start'}); + this._attachedTimers.forEach((t) => t.start()); + try { + await this._activityTimeout.finished(); + } catch (_e) { /* aborted */ } + this._attachedTimers.forEach((t) => t.abort()); } else { - this.activityEndTimer = setTimeout(this._onActivityEndTimer.bind(this), targetTime - now); + this._activityTimeout.restart(); } } } + module.exports = new UserActivity(); diff --git a/src/WhoIsTyping.js b/src/WhoIsTyping.js index 0edad8d4a5..78ca77ce5a 100644 --- a/src/WhoIsTyping.js +++ b/src/WhoIsTyping.js @@ -63,16 +63,16 @@ module.exports = { if (whoIsTyping.length == 0) { return ''; } else if (whoIsTyping.length == 1) { - return _t('%(displayName)s is typing', {displayName: whoIsTyping[0].name}); + return _t('%(displayName)s is typing …', {displayName: whoIsTyping[0].name}); } const names = whoIsTyping.map(function(m) { return m.name; }); if (othersCount>=1) { - return _t('%(names)s and %(count)s others are typing', {names: names.slice(0, limit - 1).join(', '), count: othersCount}); + return _t('%(names)s and %(count)s others are typing …', {names: names.slice(0, limit - 1).join(', '), count: othersCount}); } else { const lastPerson = names.pop(); - return _t('%(names)s and %(lastPerson)s are typing', {names: names.join(', '), lastPerson: lastPerson}); + return _t('%(names)s and %(lastPerson)s are typing …', {names: names.join(', '), lastPerson: lastPerson}); } }, }; diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js index a097e84cdb..4fb5df214b 100644 --- a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js +++ b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js @@ -1,5 +1,5 @@ /* -Copyright 2018 New Vector Ltd +Copyright 2018, 2019 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import { scorePassword } from '../../../../utils/PasswordScorer'; import FileSaver from 'file-saver'; -import { _t, _td } from '../../../../languageHandler'; +import { _t } from '../../../../languageHandler'; const PHASE_PASSPHRASE = 0; const PHASE_PASSPHRASE_CONFIRM = 1; @@ -32,6 +32,7 @@ const PHASE_DONE = 5; const PHASE_OPTOUT_CONFIRM = 6; const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc. +const PASSPHRASE_FEEDBACK_DELAY = 500; // How long after keystroke to offer passphrase feedback, ms. // XXX: copied from ShareDialog: factor out into utils function selectText(target) { @@ -63,6 +64,13 @@ export default React.createClass({ componentWillMount: function() { this._recoveryKeyNode = null; this._keyBackupInfo = null; + this._setZxcvbnResultTimeout = null; + }, + + componentWillUnmount: function() { + if (this._setZxcvbnResultTimeout !== null) { + clearTimeout(this._setZxcvbnResultTimeout); + } }, _collectRecoveryKeyNode: function(n) { @@ -102,7 +110,7 @@ export default React.createClass({ info = await MatrixClientPeg.get().createKeyBackupVersion( this._keyBackupInfo, ); - await MatrixClientPeg.get().backupAllGroupSessions(info.version); + await MatrixClientPeg.get().scheduleAllGroupSessionsForBackup(); this.setState({ phase: PHASE_DONE, }); @@ -150,9 +158,23 @@ export default React.createClass({ this.setState({phase: PHASE_PASSPHRASE_CONFIRM}); }, - _onPassPhraseKeyPress: function(e) { - if (e.key === 'Enter' && this._passPhraseIsValid()) { - this._onPassPhraseNextClick(); + _onPassPhraseKeyPress: async function(e) { + if (e.key === 'Enter') { + // If we're waiting for the timeout before updating the result at this point, + // skip ahead and do it now, otherwise we'll deny the attempt to proceed + // even if the user entered a valid passphrase + if (this._setZxcvbnResultTimeout !== null) { + clearTimeout(this._setZxcvbnResultTimeout); + this._setZxcvbnResultTimeout = null; + await new Promise((resolve) => { + this.setState({ + zxcvbnResult: scorePassword(this.state.passPhrase), + }, resolve); + }); + } + if (this._passPhraseIsValid()) { + this._onPassPhraseNextClick(); + } } }, @@ -177,10 +199,11 @@ export default React.createClass({ passPhrase: '', passPhraseConfirm: '', phase: PHASE_PASSPHRASE, + zxcvbnResult: null, }); }, - _onKeepItSafeGotItClick: function() { + _onKeepItSafeBackClick: function() { this.setState({ phase: PHASE_SHOWKEY, }); @@ -189,11 +212,20 @@ export default React.createClass({ _onPassPhraseChange: function(e) { this.setState({ passPhrase: e.target.value, - // precompute this and keep it in state: zxcvbn is fast but - // we use it in a couple of different places so no point recomputing - // it unnecessarily. - zxcvbnResult: scorePassword(e.target.value), }); + + if (this._setZxcvbnResultTimeout !== null) { + clearTimeout(this._setZxcvbnResultTimeout); + } + this._setZxcvbnResultTimeout = setTimeout(() => { + this._setZxcvbnResultTimeout = null; + this.setState({ + // precompute this and keep it in state: zxcvbn is fast but + // we use it in a couple of different places so no point recomputing + // it unnecessarily. + zxcvbnResult: scorePassword(this.state.passPhrase), + }); + }, PASSPHRASE_FEEDBACK_DELAY); }, _onPassPhraseConfirmChange: function(e) { @@ -246,6 +278,7 @@ export default React.createClass({ value={this.state.passPhrase} className="mx_CreateKeyBackupDialog_passPhraseInput" placeholder={_t("Enter a passphrase...")} + autoFocus={true} />
{strengthMeter} @@ -294,14 +327,22 @@ export default React.createClass({ _renderPhasePassPhraseConfirm: function() { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + let matchText; + if (this.state.passPhraseConfirm === this.state.passPhrase) { + matchText = _t("That matches!"); + } else if (!this.state.passPhrase.startsWith(this.state.passPhraseConfirm)) { + // only tell them they're wrong if they've actually gone wrong. + // Security concious readers will note that if you left riot-web unattended + // on this screen, this would make it easy for a malicious person to guess + // your passphrase one letter at a time, but they could get this faster by + // just opening the browser's developer tools and reading it. + // Note that not having typed anything at all will not hit this clause and + // fall through so empty box === no hint. + matchText = _t("That doesn't match."); + } + let passPhraseMatch = null; - if (this.state.passPhraseConfirm.length > 0) { - let matchText; - if (this.state.passPhraseConfirm === this.state.passPhrase) { - matchText = _t("That matches!"); - } else { - matchText = _t("That doesn't match."); - } + if (matchText) { passPhraseMatch =
{matchText}
@@ -342,11 +383,12 @@ export default React.createClass({ }, _renderPhaseShowKey: function() { - const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); - let bodyText; if (this.state.setPassPhrase) { - bodyText = _t("As a safety net, you can use it to restore your encrypted message history if you forget your Recovery Passphrase."); + bodyText = _t( + "As a safety net, you can use it to restore your encrypted message " + + "history if you forget your Recovery Passphrase.", + ); } else { bodyText = _t("As a safety net, you can use it to restore your encrypted message history."); } @@ -354,7 +396,7 @@ export default React.createClass({ return

{_t("Make a copy of this Recovery Key and keep it safe.")}

{bodyText}

-

+

{_t("Your Recovery Key")}
@@ -371,13 +413,7 @@ export default React.createClass({
-

-
- +
; }, @@ -402,17 +438,17 @@ export default React.createClass({
  • {_t("Save it on a USB key or backup drive", {}, {b: s => {s}})}
  • {_t("Copy it to your personal cloud storage", {}, {b: s => {s}})}
  • - + + +
    ; }, _renderBusyPhase: function(text) { const Spinner = sdk.getComponent('views.elements.Spinner'); return
    -

    {_t(text)}

    ; }, @@ -420,8 +456,10 @@ export default React.createClass({ _renderPhaseDone: function() { const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return
    -

    {_t("Backup created")}

    -

    {_t("Your encryption keys are now being backed up to your Homeserver.")}

    +

    {_t( + "Your encryption keys are now being backed up in the background " + + "to your Homeserver. The initial backup could take several minutes. " + + "You can view key backup upload progress in Settings.")}

    {content} diff --git a/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js b/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js index 194842ffa5..db86178b5a 100644 --- a/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js +++ b/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js @@ -1,5 +1,5 @@ /* -Copyright 2018 New Vector Ltd +Copyright 2018-2019 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -24,9 +24,15 @@ import Modal from "../../../../Modal"; export default class NewRecoveryMethodDialog extends React.PureComponent { static propTypes = { + // As returned by js-sdk getKeyBackupVersion() + newVersionInfo: PropTypes.object, onFinished: PropTypes.func.isRequired, } + onOkClick = () => { + this.props.onFinished(); + } + onGoToSettingsClick = () => { this.props.onFinished(); dis.dispatch({ action: 'view_user_settings' }); @@ -41,8 +47,7 @@ export default class NewRecoveryMethodDialog extends React.PureComponent { // sending our own new keys to it. let backupSigStatus; try { - const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion(); - backupSigStatus = await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo); + backupSigStatus = await MatrixClientPeg.get().isKeyBackupTrusted(this.props.newVersionInfo); } catch (e) { console.log("Unable to fetch key backup status", e); return; @@ -71,39 +76,62 @@ export default class NewRecoveryMethodDialog extends React.PureComponent { render() { const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog"); const DialogButtons = sdk.getComponent("views.elements.DialogButtons"); - const title = + + const title = {_t("New Recovery Method")} ; + const newMethodDetected =

    {_t( + "A new recovery passphrase and key for Secure " + + "Messages have been detected.", + )}

    ; + + const hackWarning =

    {_t( + "If you didn't set the new recovery method, an " + + "attacker may be trying to access your account. " + + "Change your account password and set a new recovery " + + "method immediately in Settings.", + )}

    ; + + let content; + if (MatrixClientPeg.get().getKeyBackupEnabled()) { + content =
    + {newMethodDetected} +

    {_t( + "This device is encrypting history using the new recovery method.", + )}

    + {hackWarning} + +
    ; + } else { + content =
    + {newMethodDetected} +

    {_t( + "Setting up Secure Messages on this device " + + "will re-encrypt this device's message history with " + + "the new recovery method.", + )}

    + {hackWarning} + +
    ; + } + return ( - -
    -

    {_t( - "A new recovery passphrase and key for Secure " + - "Messages has been detected.", - )}

    -

    {_t( - "Setting up Secure Messages on this device " + - "will re-encrypt this device's message history with " + - "the new recovery method.", - )}

    -

    {_t( - "If you didn't set the new recovery method, an " + - "attacker may be trying to access your account. " + - "Change your account password and set a new recovery " + - "method immediately in Settings.", - )}

    - -
    + {content}
    ); } diff --git a/src/async-components/views/dialogs/keybackup/RecoveryMethodRemovedDialog.js b/src/async-components/views/dialogs/keybackup/RecoveryMethodRemovedDialog.js new file mode 100644 index 0000000000..1975fbe6d6 --- /dev/null +++ b/src/async-components/views/dialogs/keybackup/RecoveryMethodRemovedDialog.js @@ -0,0 +1,80 @@ +/* +Copyright 2019 New Vector 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. +*/ + +import React from "react"; +import PropTypes from "prop-types"; +import sdk from "../../../../index"; +import dis from "../../../../dispatcher"; +import { _t } from "../../../../languageHandler"; +import Modal from "../../../../Modal"; + +export default class RecoveryMethodRemovedDialog extends React.PureComponent { + static propTypes = { + onFinished: PropTypes.func.isRequired, + } + + onGoToSettingsClick = () => { + this.props.onFinished(); + dis.dispatch({ action: 'view_user_settings' }); + } + + onSetupClick = () => { + this.props.onFinished(); + Modal.createTrackedDialogAsync("Key Backup", "Key Backup", + import("./CreateKeyBackupDialog"), + ); + } + + render() { + const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog"); + const DialogButtons = sdk.getComponent("views.elements.DialogButtons"); + + const title = + {_t("Recovery Method Removed")} + ; + + return ( + +
    +

    {_t( + "This device has detected that your recovery passphrase and key " + + "for Secure Messages have been removed.", + )}

    +

    {_t( + "If you did this accidentally, you can setup Secure Messages on " + + "this device which will re-encrypt this device's message " + + "history with a new recovery method.", + )}

    +

    {_t( + "If you didn't remove the recovery method, an " + + "attacker may be trying to access your account. " + + "Change your account password and set a new recovery " + + "method immediately in Settings.", + )}

    + +
    +
    + ); + } +} diff --git a/src/autocomplete/AutocompleteProvider.js b/src/autocomplete/AutocompleteProvider.js index f9fb61d3a3..98ae83c526 100644 --- a/src/autocomplete/AutocompleteProvider.js +++ b/src/autocomplete/AutocompleteProvider.js @@ -41,8 +41,12 @@ export default class AutocompleteProvider { /** * Of the matched commands in the query, returns the first that contains or is contained by the selection, or null. + * @param {string} query The query string + * @param {SelectionRange} selection Selection to search + * @param {boolean} force True if the user is forcing completion + * @return {object} { command, range } where both objects fields are null if no match */ - getCurrentCommand(query: string, selection: SelectionRange, force: boolean = false): ?string { + getCurrentCommand(query: string, selection: SelectionRange, force: boolean = false) { let commandRegex = this.commandRegex; if (force && this.shouldForceComplete()) { diff --git a/src/autocomplete/Autocompleter.js b/src/autocomplete/Autocompleter.js index e7b89fe576..af2744950f 100644 --- a/src/autocomplete/Autocompleter.js +++ b/src/autocomplete/Autocompleter.js @@ -60,8 +60,8 @@ const PROVIDER_COMPLETION_TIMEOUT = 3000; export default class Autocompleter { constructor(room: Room) { this.room = room; - this.providers = PROVIDERS.map((p) => { - return new p(room); + this.providers = PROVIDERS.map((Prov) => { + return new Prov(room); }); } diff --git a/src/autocomplete/UserProvider.js b/src/autocomplete/UserProvider.js index 2eae053d72..d4a5ec5e74 100644 --- a/src/autocomplete/UserProvider.js +++ b/src/autocomplete/UserProvider.js @@ -41,7 +41,7 @@ export default class UserProvider extends AutocompleteProvider { users: Array = null; room: Room = null; - constructor(room) { + constructor(room: Room) { super(USER_REGEX, FORCED_USER_REGEX); this.room = room; this.matcher = new QueryMatcher([], { diff --git a/src/components/structures/AutoHideScrollbar.js b/src/components/structures/AutoHideScrollbar.js new file mode 100644 index 0000000000..47ae24ba0f --- /dev/null +++ b/src/components/structures/AutoHideScrollbar.js @@ -0,0 +1,137 @@ +/* +Copyright 2018 New Vector 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. +*/ + +import React from "react"; + +// derived from code from github.com/noeldelgado/gemini-scrollbar +// Copyright (c) Noel Delgado (pixelia.me) +function getScrollbarWidth(alternativeOverflow) { + const div = document.createElement('div'); + div.style.position = 'absolute'; + div.style.top = '-9999px'; + div.style.width = '100px'; + div.style.height = '100px'; + div.style.overflow = "scroll"; + if (alternativeOverflow) { + div.style.overflow = alternativeOverflow; + } + div.style.msOverflowStyle = '-ms-autohiding-scrollbar'; + document.body.appendChild(div); + const scrollbarWidth = (div.offsetWidth - div.clientWidth); + document.body.removeChild(div); + return scrollbarWidth; +} + +function install() { + const scrollbarWidth = getScrollbarWidth(); + if (scrollbarWidth !== 0) { + const hasForcedOverlayScrollbar = getScrollbarWidth('overlay') === 0; + // overflow: overlay on webkit doesn't auto hide the scrollbar + if (hasForcedOverlayScrollbar) { + document.body.classList.add("mx_scrollbar_overlay_noautohide"); + } else { + document.body.classList.add("mx_scrollbar_nooverlay"); + const style = document.createElement('style'); + style.type = 'text/css'; + style.innerText = + `body.mx_scrollbar_nooverlay { --scrollbar-width: ${scrollbarWidth}px; }`; + document.head.appendChild(style); + } + } +} + +const installBodyClassesIfNeeded = (function() { + let installed = false; + return function() { + if (!installed) { + install(); + installed = true; + } + }; +})(); + +export default class AutoHideScrollbar extends React.Component { + constructor(props) { + super(props); + this.onOverflow = this.onOverflow.bind(this); + this.onUnderflow = this.onUnderflow.bind(this); + this._collectContainerRef = this._collectContainerRef.bind(this); + this._needsOverflowListener = null; + } + + onOverflow() { + this.containerRef.classList.add("mx_AutoHideScrollbar_overflow"); + this.containerRef.classList.remove("mx_AutoHideScrollbar_underflow"); + } + + onUnderflow() { + this.containerRef.classList.remove("mx_AutoHideScrollbar_overflow"); + this.containerRef.classList.add("mx_AutoHideScrollbar_underflow"); + } + + checkOverflow() { + if (!this._needsOverflowListener) { + return; + } + if (this.containerRef.scrollHeight > this.containerRef.clientHeight) { + this.onOverflow(); + } else { + this.onUnderflow(); + } + } + + componentDidUpdate() { + this.checkOverflow(); + } + + componentDidMount() { + installBodyClassesIfNeeded(); + this._needsOverflowListener = + document.body.classList.contains("mx_scrollbar_nooverlay"); + if (this._needsOverflowListener) { + this.containerRef.addEventListener("overflow", this.onOverflow); + this.containerRef.addEventListener("underflow", this.onUnderflow); + } + this.checkOverflow(); + } + + _collectContainerRef(ref) { + if (ref && !this.containerRef) { + this.containerRef = ref; + } + if (this.props.wrappedRef) { + this.props.wrappedRef(ref); + } + } + + componentWillUnmount() { + if (this._needsOverflowListener && this.containerRef) { + this.containerRef.removeEventListener("overflow", this.onOverflow); + this.containerRef.removeEventListener("underflow", this.onUnderflow); + } + } + + render() { + return (
    +
    + { this.props.children } +
    +
    ); + } +} diff --git a/src/components/structures/ContextualMenu.js b/src/components/structures/ContextualMenu.js index 7295fd45d3..d551a6fe27 100644 --- a/src/components/structures/ContextualMenu.js +++ b/src/components/structures/ContextualMenu.js @@ -49,7 +49,7 @@ export default class ContextualMenu extends React.Component { menuHeight: PropTypes.number, chevronOffset: PropTypes.number, menuColour: PropTypes.string, - chevronFace: PropTypes.string, // top, bottom, left, right + chevronFace: PropTypes.string, // top, bottom, left, right or none // Function to be called on menu close onFinished: PropTypes.func, menuPaddingTop: PropTypes.number, @@ -113,7 +113,6 @@ export default class ContextualMenu extends React.Component { render() { const position = {}; let chevronFace = null; - const props = this.props; if (props.top) { @@ -137,6 +136,8 @@ export default class ContextualMenu extends React.Component { if (props.chevronFace) { chevronFace = props.chevronFace; } + const hasChevron = chevronFace && chevronFace !== "none"; + if (chevronFace === 'top' || chevronFace === 'bottom') { chevronOffset.left = props.chevronOffset; } else { @@ -174,11 +175,14 @@ export default class ContextualMenu extends React.Component { `; } - const chevron =
    ; + const chevron = hasChevron ? +
    : + undefined; const className = 'mx_ContextualMenu_wrapper'; const menuClasses = classNames({ 'mx_ContextualMenu': true, + 'mx_ContextualMenu_noChevron': chevronFace === 'none', 'mx_ContextualMenu_left': chevronFace === 'left', 'mx_ContextualMenu_right': chevronFace === 'right', 'mx_ContextualMenu_top': chevronFace === 'top', diff --git a/src/components/structures/CreateRoom.js b/src/components/structures/CreateRoom.js deleted file mode 100644 index a8aac71479..0000000000 --- a/src/components/structures/CreateRoom.js +++ /dev/null @@ -1,284 +0,0 @@ -/* -Copyright 2015, 2016 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'; - -import React from 'react'; -import PropTypes from 'prop-types'; -import { _t } from '../../languageHandler'; -import sdk from '../../index'; -import MatrixClientPeg from '../../MatrixClientPeg'; -const PresetValues = { - PrivateChat: "private_chat", - PublicChat: "public_chat", - Custom: "custom", -}; - -module.exports = React.createClass({ - displayName: 'CreateRoom', - - propTypes: { - onRoomCreated: PropTypes.func, - collapsedRhs: PropTypes.bool, - }, - - phases: { - CONFIG: "CONFIG", // We're waiting for user to configure and hit create. - CREATING: "CREATING", // We're sending the request. - CREATED: "CREATED", // We successfully created the room. - ERROR: "ERROR", // There was an error while trying to create room. - }, - - getDefaultProps: function() { - return { - onRoomCreated: function() {}, - }; - }, - - getInitialState: function() { - return { - phase: this.phases.CONFIG, - error_string: "", - is_private: true, - share_history: false, - default_preset: PresetValues.PrivateChat, - topic: '', - room_name: '', - invited_users: [], - }; - }, - - onCreateRoom: function() { - const options = {}; - - if (this.state.room_name) { - options.name = this.state.room_name; - } - - if (this.state.topic) { - options.topic = this.state.topic; - } - - if (this.state.preset) { - if (this.state.preset != PresetValues.Custom) { - options.preset = this.state.preset; - } else { - options.initial_state = [ - { - type: "m.room.join_rules", - content: { - "join_rule": this.state.is_private ? "invite" : "public", - }, - }, - { - type: "m.room.history_visibility", - content: { - "history_visibility": this.state.share_history ? "shared" : "invited", - }, - }, - ]; - } - } - - options.invite = this.state.invited_users; - - const alias = this.getAliasLocalpart(); - if (alias) { - options.room_alias_name = alias; - } - - const cli = MatrixClientPeg.get(); - if (!cli) { - // TODO: Error. - console.error("Cannot create room: No matrix client."); - return; - } - - const deferred = cli.createRoom(options); - - if (this.state.encrypt) { - // TODO - } - - this.setState({ - phase: this.phases.CREATING, - }); - - const self = this; - - deferred.then(function(resp) { - self.setState({ - phase: self.phases.CREATED, - }); - self.props.onRoomCreated(resp.room_id); - }, function(err) { - self.setState({ - phase: self.phases.ERROR, - error_string: err.toString(), - }); - }); - }, - - getPreset: function() { - return this.refs.presets.getPreset(); - }, - - getName: function() { - return this.refs.name_textbox.getName(); - }, - - getTopic: function() { - return this.refs.topic.getTopic(); - }, - - getAliasLocalpart: function() { - return this.refs.alias.getAliasLocalpart(); - }, - - getInvitedUsers: function() { - return this.refs.user_selector.getUserIds(); - }, - - onPresetChanged: function(preset) { - switch (preset) { - case PresetValues.PrivateChat: - this.setState({ - preset: preset, - is_private: true, - share_history: false, - }); - break; - case PresetValues.PublicChat: - this.setState({ - preset: preset, - is_private: false, - share_history: true, - }); - break; - case PresetValues.Custom: - this.setState({ - preset: preset, - }); - break; - } - }, - - onPrivateChanged: function(ev) { - this.setState({ - preset: PresetValues.Custom, - is_private: ev.target.checked, - }); - }, - - onShareHistoryChanged: function(ev) { - this.setState({ - preset: PresetValues.Custom, - share_history: ev.target.checked, - }); - }, - - onTopicChange: function(ev) { - this.setState({ - topic: ev.target.value, - }); - }, - - onNameChange: function(ev) { - this.setState({ - room_name: ev.target.value, - }); - }, - - onInviteChanged: function(invited_users) { - this.setState({ - invited_users: invited_users, - }); - }, - - onAliasChanged: function(alias) { - this.setState({ - alias: alias, - }); - }, - - onEncryptChanged: function(ev) { - this.setState({ - encrypt: ev.target.checked, - }); - }, - - render: function() { - const curr_phase = this.state.phase; - if (curr_phase == this.phases.CREATING) { - const Loader = sdk.getComponent("elements.Spinner"); - return ( - - ); - } else { - let error_box = ""; - if (curr_phase == this.phases.ERROR) { - error_box = ( -
    - { _t('An error occurred: %(error_string)s', {error_string: this.state.error_string}) } -
    - ); - } - - const CreateRoomButton = sdk.getComponent("create_room.CreateRoomButton"); - const RoomAlias = sdk.getComponent("create_room.RoomAlias"); - const Presets = sdk.getComponent("create_room.Presets"); - const UserSelector = sdk.getComponent("elements.UserSelector"); - const SimpleRoomHeader = sdk.getComponent("rooms.SimpleRoomHeader"); - - const domain = MatrixClientPeg.get().getDomain(); - - return ( -
    - -
    -
    -