Merge branch 'develop' into release-v1.0.0

This commit is contained in:
J. Ryan Stinnett 2019-02-14 11:10:22 +00:00
commit 1ef14f2118
123 changed files with 2813 additions and 1144 deletions

View file

@ -50,8 +50,54 @@ a:visited {
color: $accent-color-alt;
}
input[type=text],
input[type=search],
input[type=password] {
padding: 9px;
font-family: $font-family;
font-size: 14px;
font-weight: 600;
min-width: 0;
}
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('$(res)/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;
}
// Override Firefox's UA style so we get a consistent look across browsers
input::placeholder,
textarea::placeholder {
opacity: initial;
}
input[type=text], input[type=password], textarea {
background-color: transparent;
color: $primary-fg-color;
}
input[type=text]:focus, input[type=password]:focus, textarea:focus {
@ -62,6 +108,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
/* Required by Firefox */
textarea {
font-family: $font-family;
color: $primary-fg-color;
}
/* Prevent ugly dotted highlight around selected elements in Firefox */
@ -242,7 +289,7 @@ textarea {
font-weight: 600;
border: 1px solid $accent-color ! important;
color: $accent-color;
background-color: $accent-fg-color;
background-color: $button-secondary-bg-color;
}
.mx_Dialog button:hover, .mx_Dialog input[type="submit"]:hover {

View file

@ -7,6 +7,7 @@
@import "./structures/_CustomRoomTagPanel.scss";
@import "./structures/_FilePanel.scss";
@import "./structures/_GroupView.scss";
@import "./structures/_HeaderButtons.scss";
@import "./structures/_HomePage.scss";
@import "./structures/_LeftPanel.scss";
@import "./structures/_MatrixChat.scss";
@ -57,6 +58,7 @@
@import "./views/dialogs/_DevtoolsDialog.scss";
@import "./views/dialogs/_EncryptedEventDialog.scss";
@import "./views/dialogs/_GroupAddressPicker.scss";
@import "./views/dialogs/_IncomingSasDialog.scss";
@import "./views/dialogs/_RestoreKeyBackupDialog.scss";
@import "./views/dialogs/_RoomSettingsDialog.scss";
@import "./views/dialogs/_RoomUpgradeDialog.scss";
@ -126,6 +128,7 @@
@import "./views/rooms/_PinnedEventsPanel.scss";
@import "./views/rooms/_PresenceLabel.scss";
@import "./views/rooms/_ReplyPreview.scss";
@import "./views/rooms/_RoomBreadcrumbs.scss";
@import "./views/rooms/_RoomDropTarget.scss";
@import "./views/rooms/_RoomHeader.scss";
@import "./views/rooms/_RoomList.scss";

View file

@ -74,6 +74,7 @@ body.mx_scrollbar_nooverlay {
// or fallback for webkit browsers
::-webkit-scrollbar {
width: 6px;
height: 6px;
background-color: $scrollbar-track-color;
}

View file

@ -31,7 +31,7 @@ limitations under the License.
.mx_ContextualMenu {
border-radius: 4px;
box-shadow: 4px 4px 12px 0 rgba(118, 131, 156, 0.6);;
box-shadow: 4px 4px 12px 0 $menu-box-shadow-color;
background-color: $menu-bg-color;
color: $primary-fg-color;
position: absolute;

View file

@ -44,13 +44,22 @@ limitations under the License.
}
.mx_GroupHeader_button {
margin-left: 12px;
margin-left: 5px;
margin-right: 5px;
cursor: pointer;
height: 20px;
width: 20px;
background-color: $groupheader-button-color;
mask-repeat: no-repeat;
mask-size: contain;
}
.mx_GroupHeader_button object {
// prevents clicks from being swallowed by svg in 'object' tag
pointer-events: none;
.mx_GroupHeader_editButton {
mask-image: url('$(res)/img/icons-settings-room.svg');
}
.mx_GroupHeader_shareButton {
mask-image: url('$(res)/img/icons-share.svg');
}
.mx_GroupView_editable {

View file

@ -0,0 +1,28 @@
/*
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.
*/
.mx_HeaderButtons {
display: flex;
}
.mx_HeaderButtons::before {
content: "";
background-color: $header-divider-color;
opacity: 0.5;
margin: 0 15px;
border-radius: 1px;
width: 1px;
}

View file

@ -25,9 +25,7 @@ limitations under the License.
.mx_RightPanel_header {
order: 1;
border-bottom: 1px solid $primary-hairline-color;
flex: 0 0 52px;
}
@ -45,20 +43,55 @@ limitations under the License.
cursor: pointer;
flex: 0 0 auto;
vertical-align: top;
margin-top: 4px;
padding-left: 5px;
padding-right: 5px;
margin-left: 5px;
margin-right: 5px;
text-align: center;
position: relative;
border-bottom: 2px solid transparent;
height: 20px;
width: 20px;
position: relative;
}
.mx_RightPanel_headerButton object {
pointer-events: none;
.mx_RightPanel_headerButton::before {
content: '';
position: absolute;
top: 0;
left: 0;
height: 20px;
width: 20px;
background-color: $rightpanel-button-color;
mask-repeat: no-repeat;
mask-size: contain;
}
.mx_RightPanel_headerButton_highlight {
border-color: $button-bg-color;
.mx_RightPanel_membersButton::before {
mask-image: url('$(res)/img/feather-icons/user.svg');
}
.mx_RightPanel_filesButton::before {
mask-image: url('$(res)/img/feather-icons/files.svg');
}
.mx_RightPanel_notifsButton::before {
mask-image: url('$(res)/img/feather-icons/notifications.svg');
}
.mx_RightPanel_groupMembersButton::before {
mask-image: url('$(res)/img/icons-people.svg');
}
.mx_RightPanel_roomsButton::before {
mask-image: url('$(res)/img/icons-room-nobg.svg');
}
.mx_RightPanel_headerButton_highlight::after {
content: '';
position: absolute;
bottom: -6px;
left: 0;
right: 0;
height: 2px;
background-color: $button-bg-color;
}
.mx_RightPanel_headerButton_badge {

View file

@ -152,7 +152,7 @@ limitations under the License.
&.mx_IndicatorScrollbar_topOverflow::before {
top: 0;
transition: background-image 0.1s ease-in;
background: linear-gradient(to top, rgba(242,245,248,0), rgba(242,245,248,1));
background: linear-gradient(to top, $panel-gradient);
}
/*

View file

@ -28,8 +28,8 @@ limitations under the License.
}
.mx_TabbedView_tabLabels {
width: 150px;
max-width: 150px;
width: 170px;
max-width: 170px;
color: $tab-label-fg-color;
position: fixed;
}
@ -39,9 +39,8 @@ limitations under the License.
cursor: pointer;
display: block;
border-radius: 3px;
font-size: 12px;
font-weight: 600;
min-height: 20px; // use min-height instead of height to allow the label to overflow a bit
font-size: 14px;
min-height: 24px; // use min-height instead of height to allow the label to overflow a bit
margin-bottom: 6px;
position: relative;
}
@ -55,8 +54,8 @@ limitations under the License.
margin-left: 6px;
margin-right: 9px;
margin-top: 1px;
width: 14px;
height: 14px;
width: 16px;
height: 16px;
display: inline-block;
}
@ -64,9 +63,9 @@ limitations under the License.
display: inline-block;
background-color: $tab-label-icon-bg-color;
mask-repeat: no-repeat;
mask-size: 14px;
mask-size: 16px;
width: 14px;
height: 18px;
height: 22px;
mask-position: center;
content: '';
vertical-align: middle;
@ -81,7 +80,7 @@ limitations under the License.
}
.mx_TabbedView_tabPanel {
margin-left: 220px; // 150px sidebar + 70px padding
margin-left: 240px; // 170px sidebar + 70px padding
flex-grow: 1;
display: flex;
flex-direction: column;

View file

@ -38,7 +38,7 @@ limitations under the License.
height: 40px;
width: 40px;
border-radius: 20px;
background-color: $roomheader-addroom-color;
background-color: $tagpanel-button-color;
position: relative;
/* overwrite mx_RoleButton inline-block */
display: block !important;

View file

@ -79,7 +79,7 @@ limitations under the License.
.mx_Login_type_container {
display: flex;
margin-bottom: 14px;
color: $primary-fg-color;
color: $authpage-primary-color;
}
.mx_Login_type_label {

View file

@ -21,19 +21,41 @@ limitations under the License.
padding: 25px 60px;
box-sizing: border-box;
font-size: 12px;
color: $authpage-body-color;
color: $authpage-secondary-color;
}
.mx_AuthBody h2 {
font-size: 24px;
font-weight: 600;
margin-top: 8px;
color: $authpage-primary-color;
}
.mx_AuthBody h3 {
font-size: 14px;
font-weight: 600;
color: $primary-fg-color;
color: $authpage-primary-color;
}
.mx_AuthBody input[type=text],
.mx_AuthBody input[type=password] {
color: $authpage-primary-color;
}
.mx_AuthBody .mx_Field input,
.mx_AuthBody .mx_Field select {
color: $authpage-primary-color;
background-color: $authpage-body-bg-color;
}
.mx_AuthBody .mx_Field label {
color: $authpage-primary-color;
}
.mx_AuthBody .mx_Field input:focus + label,
.mx_AuthBody .mx_Field input:not(:placeholder-shown) + label,
.mx_AuthBody .mx_Field select + label /* Always show a select's label on top to not collide with the value */ {
background-color: $authpage-body-bg-color;
}
.mx_AuthBody_editServerDetails {

View file

@ -34,7 +34,7 @@ limitations under the License.
.mx_ServerTypeSelector_label {
text-align: center;
font-weight: 600;
color: $primary-fg-color;
color: $authpage-primary-color;
margin: 8px 0;
}
@ -54,7 +54,7 @@ limitations under the License.
height: 18px;
margin-bottom: 12px;
font-weight: 600;
color: $primary-fg-color;
color: $authpage-primary-color;
}
.mx_ServerTypeSelector_logo > div {

View file

@ -31,10 +31,18 @@ limitations under the License.
mask-image: url('$(res)/img/feather-icons/home.svg');
}
li.mx_TopLeftMenu_icon_welcome::after {
mask-image: url('$(res)/img/feather-icons/gift.svg');
}
li.mx_TopLeftMenu_icon_settings::after {
mask-image: url('$(res)/img/feather-icons/settings.svg');
}
li.mx_TopLeftMenu_icon_signin::after {
mask-image: url('$(res)/img/feather-icons/sign-in.svg');
}
li.mx_TopLeftMenu_icon_signout::after {
mask-image: url('$(res)/img/feather-icons/sign-out.svg');
}

View file

@ -43,10 +43,9 @@ limitations under the License.
.mx_CreateGroupDialog_prefix,
.mx_CreateGroupDialog_suffix {
height: 35px;
padding: 0px 5px;
line-height: 37px;
background-color: $input-border-color;
background-color: $input-darker-bg-color;
border: 1px solid $input-border-color;
text-align: center;
}

View file

@ -0,0 +1,24 @@
/*
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.
*/
.mx_IncomingSasDialog_opponentProfile_image {
position: relative;
}
.mx_IncomingSasDialog_opponentProfile h2 {
display: inline-block;
margin-left: 10px;
}

View file

@ -16,8 +16,8 @@ limitations under the License.
.mx_SettingsDialog {
.mx_Dialog {
max-width: 900px;
width: 80%;
max-width: 1000px;
width: 90%;
height: 80%;
border-radius: 4px;
padding-top: 0;
@ -30,7 +30,7 @@ limitations under the License.
.mx_TabbedView .mx_SettingsTab {
box-sizing: border-box;
min-width: 550px;
min-width: 580px;
padding-right: 130px;
// Put some padding on the bottom to avoid the settings tab from

View file

@ -28,28 +28,28 @@ limitations under the License.
flex-direction: column;
}
.mx_UnknownDeviceDialog ul {
list-style: none;
padding: 0;
}
// userid
.mx_UnknownDeviceDialog p {
font-weight: bold;
font-size: 16px;
}
.mx_UnknownDeviceDialog .mx_DeviceVerifyButtons {
float: right;
flex-direction: row !important;
}
.mx_UnknownDeviceDialog .mx_Dialog_content {
margin-bottom: 24px;
}
.mx_UnknownDeviceDialog .mx_MemberDeviceInfo {
float: right;
clear: both;
padding: 0px;
padding-top: 8px;
.mx_UnknownDeviceDialog_deviceList > li {
padding: 4px;
}
.mx_UnknownDeviceDialog .mx_MemberDeviceInfo_textButton {
@mixin mx_DialogButton_small;
background-color: $primary-bg-color;
color: $accent-color;
}
.mx_UnknownDeviceDialog .mx_UnknownDeviceDialog_deviceList li {
height: 40px;
border-bottom: 1px solid $primary-hairline-color;
.mx_UnknownDeviceDialog_deviceList > li > * {
padding-bottom: 0;
}

View file

@ -40,8 +40,7 @@ limitations under the License.
}
.mx_AccessibleButton_kind_primary.mx_AccessibleButton_disabled {
color: $button-primary-disabled-fg-color;
background-color: $button-primary-disabled-bg-color;
opacity: 0.4;
}
.mx_AccessibleButton_hasKind.mx_AccessibleButton_kind_primary_sm {
@ -51,8 +50,7 @@ limitations under the License.
}
.mx_AccessibleButton_kind_primary_sm.mx_AccessibleButton_disabled {
color: $button-primary-disabled-fg-color;
background-color: $button-primary-disabled-bg-color;
opacity: 0.4;
}
.mx_AccessibleButton_kind_danger {
@ -74,4 +72,4 @@ limitations under the License.
.mx_AccessibleButton_kind_danger_sm.mx_AccessibleButton_disabled {
color: $button-danger-disabled-fg-color;
background-color: $button-danger-disabled-bg-color;
}
}

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
/* TODO: Consider unifying with general input styles in _dharma.scss */
/* TODO: Consider unifying with general input styles in _light.scss */
.mx_Field {
position: relative;
@ -31,6 +31,7 @@ limitations under the License.
transition: border-color 0.25s;
border: 1px solid $input-border-color;
padding: 8px 9px;
color: $primary-fg-color;
background-color: $primary-bg-color;
}

View file

@ -14,6 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
/*
the tile title bar is 5 (top border) + 12 (title, buttons) + 5 (bottom padding) px = 22px
the body is assumed to be 300px (assumed by at least the sticker pickerm, perhaps elsewhere),
so the body height would be 300px - 22px (room for title bar) = 278px
BUT! the sticker picker also assumes it's a little less high than that because the iframe
for the sticker picker doesn't have any padding or margin on it's bottom.
so subtracking another 5px, which brings us at 273px.
*/
$AppsDrawerBodyHeight: 273px;
.mx_AppsDrawer {
margin: 5px;
}
@ -83,7 +93,7 @@ limitations under the License.
}
.mx_AppTile_persistedWrapper {
height: 280px;
height: $AppsDrawerBodyHeight;
}
.mx_AppTile_mini .mx_AppTile_persistedWrapper {
@ -189,7 +199,7 @@ limitations under the License.
}
.mx_AppTileBody{
height: 280px;
height: $AppsDrawerBodyHeight;
width: 100%;
overflow: hidden;
}
@ -208,7 +218,7 @@ limitations under the License.
.mx_AppTileBody iframe {
width: 100%;
height: 280px;
height: $AppsDrawerBodyHeight;
overflow: hidden;
border: none;
padding: 0;
@ -332,7 +342,7 @@ form.mx_Custom_Widget_Form div {
align-items: center;
font-weight: bold;
position: relative;
height: 280px;
height: $AppsDrawerBodyHeight;
}
.mx_AppLoading .mx_Spinner {

View file

@ -533,7 +533,7 @@ limitations under the License.
}
.mx_EventTile_e2eIcon {
top: 7px;
top: 3px;
}
.mx_EventTile_editButton {

View file

@ -180,27 +180,36 @@ limitations under the License.
color: $accent-color;
}
.mx_MessageComposer_upload,
.mx_MessageComposer_hangup,
.mx_MessageComposer_voicecall,
.mx_MessageComposer_videocall,
.mx_MessageComposer_apps,
.mx_MessageComposer_stickers {
/*display: table-cell;*/
/*vertical-align: middle;*/
/*padding-left: 10px;*/
padding-right: 12px;
.mx_MessageComposer_button {
margin-right: 12px;
cursor: pointer;
padding-top: 4px;
height: 20px;
width: 20px;
background-color: $composer-button-color;
mask-repeat: no-repeat;
mask-size: contain;
mask-position: center;
}
.mx_MessageComposer_upload object,
.mx_MessageComposer_hangup object,
.mx_MessageComposer_voicecall object,
.mx_MessageComposer_videocall object,
.mx_MessageComposer_apps object,
.mx_MessageComposer_stickers object {
pointer-events: none;
.mx_MessageComposer_upload {
mask-image: url('$(res)/img/feather-icons/paperclip.svg');
}
.mx_MessageComposer_hangup {
mask-image: url('$(res)/img/hangup.svg');
}
.mx_MessageComposer_voicecall {
mask-image: url('$(res)/img/feather-icons/phone.svg');
}
.mx_MessageComposer_videocall {
mask-image: url('$(res)/img/feather-icons/video.svg');
}
.mx_MessageComposer_stickers {
mask-image: url('$(res)/img/feather-icons/face.svg');
}
.mx_MessageComposer_formatting {

View file

@ -0,0 +1,54 @@
/*
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.
*/
.mx_RoomBreadcrumbs {
overflow-x: auto;
position: relative;
height: 32px;
margin: 8px;
margin-bottom: 0;
overflow-x: hidden;
display: flex;
flex-direction: row;
> * {
margin-left: 4px;
}
&::after {
content: "";
position: absolute;
width: 15px;
top: 0;
right: 0;
height: 100%;
background: linear-gradient(to right, $panel-gradient);
}
.mx_RoomBreadcrumbs_animate {
margin-left: 0;
transition: transform 0.3s, width 0.3s;
width: 32px;
transform: scale(1);
}
.mx_RoomBreadcrumbs_preAnimate {
width: 0;
transform: scale(0);
}
}

View file

@ -58,7 +58,6 @@ limitations under the License.
.mx_RoomHeader_buttons {
display: flex;
align-items: center;
background-color: $primary-bg-color;
padding-right: 5px;
}
@ -116,10 +115,6 @@ limitations under the License.
opacity: 0.6;
}
.mx_RoomHeader_settingsButton object {
pointer-events: none;
}
.mx_RoomHeader_name,
.mx_RoomHeader_avatar,
.mx_RoomHeader_avatarPicker,
@ -160,6 +155,7 @@ limitations under the License.
font-weight: 400;
font-size: 13px;
margin: 0 7px;
margin-top: 4px; // to align baseline of topic with room name
overflow: hidden;
text-overflow: ellipsis;
border-bottom: 1px solid transparent;
@ -199,10 +195,32 @@ limitations under the License.
.mx_RoomHeader_button {
margin-left: 10px;
cursor: pointer;
height: 20px;
width: 20px;
background-color: $roomheader-button-color;
mask-repeat: no-repeat;
mask-size: contain;
}
.mx_RoomHeader_button object {
pointer-events: none;
.mx_RoomHeader_settingsButton {
mask-image: url('$(res)/img/feather-icons/settings.svg');
}
.mx_RoomHeader_forgetButton {
mask-image: url('$(res)/img/leave.svg');
width: 26px;
}
.mx_RoomHeader_searchButton {
mask-image: url('$(res)/img/feather-icons/search.svg');
}
.mx_RoomHeader_shareButton {
mask-image: url('$(res)/img/feather-icons/share.svg');
}
.mx_RoomHeader_manageIntegsButton {
mask-image: url('$(res)/img/feather-icons/grid.svg');
}
.mx_RoomHeader_showPanel {
@ -219,6 +237,7 @@ limitations under the License.
.mx_RoomHeader_pinnedButton {
position: relative;
mask-image: url('$(res)/img/icons-pin.svg');
}
.mx_RoomHeader_pinsIndicator {

View file

@ -154,9 +154,8 @@ limitations under the License.
}
.mx_RoomTile_unread, .mx_RoomTile_highlight {
font-weight: 700 ! important;
.mx_RoomTile_name {
// font-weight: 700; // bold is too loud in the end
color: $roomtile-selected-color;
}
}
@ -176,7 +175,7 @@ limitations under the License.
}
.mx_RoomTile:focus {
filter: none ! important;
filter: none !important;
background-color: $roomtile-focused-bg-color;
}

View file

@ -7,8 +7,12 @@
height: 300px;
}
.mx_Stickers_content .mx_AppTileFullWidth {
border: none;
#mx_persistedElement_stickerPicker .mx_AppTileFullWidth {
height: unset;
box-sizing: border-box;
border-left: none;
border-right: none;
border-bottom: none;
}
.mx_Stickers_contentPlaceholder {

View file

@ -21,7 +21,7 @@ limitations under the License.
}
.mx_SettingsTab_subheading {
font-size: 14px;
font-size: 16px;
display: block;
font-family: $font-family;
font-weight: 600;
@ -32,7 +32,7 @@ limitations under the License.
.mx_SettingsTab_subsectionText {
color: $settings-subsection-fg-color;
font-size: 12px;
font-size: 14px;
padding-bottom: 12px;
display: block;
margin: 0 100px 0 0; // Align with the rest of the view
@ -40,16 +40,17 @@ limitations under the License.
.mx_SettingsTab_section .mx_SettingsFlag {
margin-right: 100px;
height: 25px;
margin-bottom: 10px;
}
.mx_SettingsTab_section .mx_SettingsFlag .mx_SettingsFlag_label {
vertical-align: bottom;
vertical-align: middle;
display: inline-block;
font-size: 12px;
font-size: 14px;
color: $primary-fg-color;
max-width: calc(100% - 48px); // Force word wrap instead of colliding with the switch
box-sizing: border-box;
padding-right: 10px;
}
.mx_SettingsTab_section .mx_SettingsFlag .mx_ToggleSwitch {

View file

@ -2,5 +2,5 @@
<defs>
<path id="a" d="M5 10A5 5 0 1 0 5 0a5 5 0 0 0 0 10zM2.5 3.5h5a1.5 1.5 0 0 1 0 3h-5a1.5 1.5 0 0 1 0-3z"/>
</defs>
<use fill="#F56679" fill-rule="evenodd" stroke="#F56679" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" transform="translate(1 1)" xlink:href="#a"/>
<use fill="#F56679" fill-rule="evenodd" stroke="#F56679" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" transform="translate(1 1)" xlink:href="#a"/>
</svg>

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 440 B

View file

@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="11" height="12" viewBox="0 0 11 12">
<path fill="#7AC9A1" fill-rule="evenodd" stroke="#7AC9A1" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M5.5 11S10 9 10 6V2.5L5.5 1 1 2.5V6c0 3 4.5 5 4.5 5z"/>
<path fill="#7AC9A1" fill-rule="evenodd" stroke="#7AC9A1" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M5.5 11S10 9 10 6V2.5L5.5 1 1 2.5V6c0 3 4.5 5 4.5 5z"/>
</svg>

Before

Width:  |  Height:  |  Size: 278 B

After

Width:  |  Height:  |  Size: 276 B

View file

@ -2,5 +2,5 @@
<defs>
<path id="a" d="M5 10A5 5 0 1 0 5 0a5 5 0 0 0 0 10zM5 .5A1.5 1.5 0 0 1 6.5 2v3a1.5 1.5 0 0 1-3 0V2A1.5 1.5 0 0 1 5 .5zm0 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z"/>
</defs>
<use fill="#F56679" fill-rule="evenodd" stroke="#F56679" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" transform="translate(1 1)" xlink:href="#a"/>
<use fill="#F56679" fill-rule="evenodd" stroke="#F56679" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" transform="translate(1 1)" xlink:href="#a"/>
</svg>

Before

Width:  |  Height:  |  Size: 500 B

After

Width:  |  Height:  |  Size: 498 B

View file

@ -3,9 +3,9 @@
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="LifeBuoy" transform="translate(-1073.000000, -872.000000)">
<g id="face-copy" transform="translate(1074.000000, 873.000000)">
<circle id="Oval" stroke="#B8BEC9" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" cx="8" cy="8" r="8"></circle>
<path d="M7.6,7.6 C8.13333333,8.71262059 8.4,9.77928725 8.4,10.8 C8.4,11.8207127 8.13333333,12.8873794 7.6,14" id="Path" stroke="#B8BEC9" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" transform="translate(8.000000, 10.800000) rotate(90.000000) translate(-8.000000, -10.800000) "></path>
<path d="" id="Path-Copy" stroke="#B8BEC9" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" transform="translate(4.800000, 4.800000) rotate(90.000000) translate(-4.800000, -4.800000) "></path>
<circle id="Oval" stroke="#B8BEC9" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" cx="8" cy="8" r="8"></circle>
<path d="M7.6,7.6 C8.13333333,8.71262059 8.4,9.77928725 8.4,10.8 C8.4,11.8207127 8.13333333,12.8873794 7.6,14" id="Path" stroke="#B8BEC9" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" transform="translate(8.000000, 10.800000) rotate(90.000000) translate(-8.000000, -10.800000) "></path>
<path d="" id="Path-Copy" stroke="#B8BEC9" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" transform="translate(4.800000, 4.800000) rotate(90.000000) translate(-4.800000, -4.800000) "></path>
<circle id="Oval" fill="#B8BEC9" fill-rule="nonzero" cx="4.8" cy="5.6" r="1"></circle>
<circle id="Oval-Copy" fill="#B8BEC9" fill-rule="nonzero" cx="11.2" cy="5.6" r="1"></circle>
</g>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -1,7 +1,7 @@
<svg width="15px" height="18px" viewBox="0 0 15 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="LifeBuoy" transform="translate(-1433.000000, -90.000000)" stroke="#B8BEC9" stroke-width="1.6">
<g id="LifeBuoy" transform="translate(-1433.000000, -90.000000)" stroke="#B8BEC9" stroke-width="1">
<g id="file-copy" transform="translate(1434.000000, 91.000000)">
<path d="M7.3125,0 L1.625,0 C0.727537282,0 0,0.7163444 0,1.6 L0,14.4 C0,15.2836556 0.727537282,16 1.625,16 L11.375,16 C12.2724627,16 13,15.2836556 13,14.4 L13,5.6 L7.3125,0 Z" id="Path"></path>
<polyline id="Path" points="7.3125 0 7.3125 5.6 13 5.6"></polyline>

Before

Width:  |  Height:  |  Size: 803 B

After

Width:  |  Height:  |  Size: 801 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="feather feather-gift"><polyline points="20 12 20 22 4 22 4 12"></polyline><rect x="2" y="7" width="20" height="5"></rect><line x1="12" y1="22" x2="12" y2="7"></line><path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"></path><path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"></path></svg>

After

Width:  |  Height:  |  Size: 482 B

View file

@ -1,7 +1,7 @@
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="LifeBuoy" transform="translate(-1350.000000, -91.000000)" stroke="#B8BEC9" stroke-width="1.6">
<g id="LifeBuoy" transform="translate(-1350.000000, -91.000000)" stroke="#B8BEC9" stroke-width="1">
<g id="grid-copy" transform="translate(1351.000000, 92.000000)">
<rect id="Rectangle" x="0" y="0" width="5.44444444" height="5.44444444"></rect>
<rect id="Rectangle" x="8.55555556" y="0" width="5.44444444" height="5.44444444"></rect>

Before

Width:  |  Height:  |  Size: 929 B

After

Width:  |  Height:  |  Size: 927 B

View file

@ -1,7 +1,7 @@
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="LifeBuoy" transform="translate(-77.000000, -862.000000)" stroke="#2E3648" stroke-width="1.6">
<g id="LifeBuoy" transform="translate(-77.000000, -862.000000)" stroke="#2E3648" stroke-width="1">
<g id="Buoy" transform="translate(68.000000, 853.000000)">
<g id="life-buoy" transform="translate(10.000000, 10.000000)">
<circle id="Oval" cx="10" cy="10" r="10"></circle>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1,7 +1,7 @@
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="LifeBuoy" transform="translate(-1460.000000, -90.000000)" stroke="#B8BEC9" stroke-width="1.6">
<g id="LifeBuoy" transform="translate(-1460.000000, -90.000000)" stroke="#B8BEC9" stroke-width="1">
<g id="bell-copy" transform="translate(1461.000000, 91.000000)">
<path d="M16,12 L0,12 C1.3254834,12 2.4,10.9254834 2.4,9.6 L2.4,5.6 C2.40000005,2.50720543 4.90720543,8.34465016e-08 8,8.3446502e-08 C11.0927946,8.34465023e-08 13.6,2.50720543 13.6,5.6 L13.6,9.6 C13.6,10.9254834 14.6745166,12 16,12 Z M9.384,15.2 C9.09776179,15.6934435 8.57045489,15.997165 8,15.997165 C7.42954511,15.997165 6.90223821,15.6934435 6.616,15.2 L9.384,15.2 Z" id="Shape"></path>
</g>

Before

Width:  |  Height:  |  Size: 916 B

After

Width:  |  Height:  |  Size: 914 B

View file

@ -1,7 +1,7 @@
<svg width="18px" height="19px" viewBox="0 0 18 19" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="LifeBuoy" transform="translate(-1103.000000, -871.000000)" stroke="#B8BEC9" stroke-width="1.6">
<g id="LifeBuoy" transform="translate(-1103.000000, -871.000000)" stroke="#B8BEC9" stroke-width="1">
<g id="paperclip-copy" transform="translate(1104.000000, 872.000000)">
<path d="M15.552,8.13571429 L8.2,15.5752381 C6.32444099,17.4731252 3.28355901,17.4731252 1.408,15.5752381 C-0.46755901,13.677351 -0.46755901,10.600268 1.408,8.70238095 L8.76,1.26285714 C10.0103727,-0.00240088755 12.0376273,-0.00240087226 13.288,1.26285718 C14.5383726,2.52811523 14.5383726,4.57950384 13.288,5.8447619 L5.928,13.2842857 C5.30281366,13.9169147 4.28918634,13.9169147 3.664,13.2842857 C3.03881366,12.6516567 3.03881366,11.6259624 3.664,10.9933333 L10.456,4.12857143" id="Path"></path>
</g>

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -1,7 +1,7 @@
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="LifeBuoy" transform="translate(-1133.000000, -872.000000)" stroke="#B8BEC9" stroke-width="1.6">
<g id="LifeBuoy" transform="translate(-1133.000000, -872.000000)" stroke="#B8BEC9" stroke-width="1">
<g id="phone-copy" transform="translate(1134.000000, 873.000000)">
<path d="M16.1904762,11.936 L16.1904762,14.336 C16.1923266,14.7865161 16.001896,15.2169267 15.6659678,15.5214919 C15.3300396,15.8260571 14.879747,15.9765505 14.4257143,15.936 C11.934675,15.6685126 9.54185647,14.8273156 7.43952381,13.48 C5.48357515,12.2517311 3.82527212,10.6129375 2.58238095,8.68 C1.21426795,6.59296319 0.362863382,4.21679565 0.0971428571,1.744 C0.0562402179,1.29669787 0.207458292,0.853001939 0.513858012,0.521296845 C0.820257732,0.18959175 1.25362391,0.000422952191 1.70809524,0 L4.13666667,0 C4.94931852,-0.00790412572 5.64197687,0.580773986 5.75571429,1.376 C5.85821863,2.14405316 6.04831623,2.89818151 6.32238095,3.624 C6.54478548,4.20870002 6.40253601,4.86784501 5.95809524,5.312 L4.93,6.328 C6.08240205,8.33083666 7.7604629,9.98915562 9.78714286,11.128 L10.8152381,10.112 C11.2646806,9.67278794 11.9316726,9.532212 12.5233333,9.752 C13.2577925,10.0228404 14.0208986,10.2107016 14.7980952,10.312 C15.6121222,10.4254883 16.2108681,11.1238341 16.1904762,11.936 Z" id="Path"></path>
</g>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -1,7 +1,7 @@
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="LifeBuoy" transform="translate(-1378.000000, -91.000000)" stroke="#61708b" stroke-width="1.6">
<g id="LifeBuoy" transform="translate(-1378.000000, -91.000000)" stroke="#61708b" stroke-width="1">
<g id="search-copy" transform="translate(1379.000000, 92.000000)">
<circle id="Oval" cx="6.22222222" cy="6.22222222" r="6.22222222"></circle>
<path d="M14,14 L10.6166667,10.6166667" id="Path"></path>

Before

Width:  |  Height:  |  Size: 676 B

After

Width:  |  Height:  |  Size: 674 B

View file

@ -1,7 +1,7 @@
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="LifeBuoy" transform="translate(-1378.000000, -91.000000)" stroke="#B8BEC9" stroke-width="1.6">
<g id="LifeBuoy" transform="translate(-1378.000000, -91.000000)" stroke="#B8BEC9" stroke-width="1">
<g id="search-copy" transform="translate(1379.000000, 92.000000)">
<circle id="Oval" cx="6.22222222" cy="6.22222222" r="6.22222222"></circle>
<path d="M14,14 L10.6166667,10.6166667" id="Path"></path>

Before

Width:  |  Height:  |  Size: 676 B

After

Width:  |  Height:  |  Size: 674 B

View file

@ -1,7 +1,7 @@
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="LifeBuoy" transform="translate(-1290.000000, -89.000000)" stroke="#B8BEC9" stroke-width="1.6">
<g id="LifeBuoy" transform="translate(-1290.000000, -89.000000)" stroke="#B8BEC9" stroke-width="1">
<g id="settings-copy" transform="translate(1291.000000, 90.000000)">
<circle id="Oval" cx="9" cy="9" r="2.45454545"></circle>
<path d="M15.0545455,11.4545455 C14.8317165,11.9594373 14.938637,12.5491199 15.3245455,12.9436364 L15.3736364,12.9927273 C15.6809079,13.2996571 15.85356,13.7161485 15.85356,14.1504545 C15.85356,14.5847606 15.6809079,15.0012519 15.3736364,15.3081818 C15.0667065,15.6154533 14.6502151,15.7881054 14.2159091,15.7881054 C13.781603,15.7881054 13.3651117,15.6154533 13.0581818,15.3081818 L13.0090909,15.2590909 C12.6145745,14.8731825 12.0248919,14.766262 11.52,14.9890909 C11.0254331,15.2010559 10.7039642,15.686474 10.7018182,16.2245455 L10.7018182,16.3636364 C10.7018182,17.267375 9.96919323,18 9.06545455,18 C8.16171586,18 7.42909091,17.267375 7.42909091,16.3636364 L7.42909091,16.29 C7.41612813,15.7358216 7.06571327,15.2458897 6.54545455,15.0545455 C6.04056267,14.8317165 5.45088006,14.938637 5.05636364,15.3245455 L5.00727273,15.3736364 C4.70034285,15.6809079 4.2838515,15.85356 3.84954545,15.85356 C3.41523941,15.85356 2.99874806,15.6809079 2.69181818,15.3736364 C2.38454666,15.0667065 2.21189456,14.6502151 2.21189456,14.2159091 C2.21189456,13.781603 2.38454666,13.3651117 2.69181818,13.0581818 L2.74090909,13.0090909 C3.12681754,12.6145745 3.23373801,12.0248919 3.01090909,11.52 C2.79894413,11.0254331 2.31352603,10.7039642 1.77545455,10.7018182 L1.63636364,10.7018182 C0.732624955,10.7018182 1.81672859e-16,9.96919323 0,9.06545455 C-9.08364293e-17,8.16171586 0.732624955,7.42909091 1.63636364,7.42909091 L1.71,7.42909091 C2.26417842,7.41612813 2.75411031,7.06571327 2.94545455,6.54545455 C3.16828346,6.04056267 3.06136299,5.45088006 2.67545455,5.05636364 L2.62636364,5.00727273 C2.31909211,4.70034285 2.14644002,4.2838515 2.14644002,3.84954545 C2.14644002,3.41523941 2.31909211,2.99874806 2.62636364,2.69181818 C2.93329351,2.38454666 3.34978487,2.21189456 3.78409091,2.21189456 C4.21839695,2.21189456 4.63488831,2.38454666 4.94181818,2.69181818 L4.99090909,2.74090909 C5.38542551,3.12681754 5.97510812,3.23373801 6.48,3.01090909 L6.54545455,3.01090909 C7.04002141,2.79894413 7.36149035,2.31352603 7.36363636,1.77545455 L7.36363636,1.63636364 C7.36363636,0.732624955 8.09626132,1.81672859e-16 9,0 C9.90373868,0 10.6363636,0.732624955 10.6363636,1.63636364 L10.6363636,1.71 C10.6385096,2.24807148 10.9599786,2.73348959 11.4545455,2.94545455 C11.9594373,3.16828346 12.5491199,3.06136299 12.9436364,2.67545455 L12.9927273,2.62636364 C13.2996571,2.31909211 13.7161485,2.14644002 14.1504545,2.14644002 C14.5847606,2.14644002 15.0012519,2.31909211 15.3081818,2.62636364 C15.6154533,2.93329351 15.7881054,3.34978487 15.7881054,3.78409091 C15.7881054,4.21839695 15.6154533,4.63488831 15.3081818,4.94181818 L15.2590909,4.99090909 C14.8731825,5.38542551 14.766262,5.97510812 14.9890909,6.48 L14.9890909,6.54545455 C15.2010559,7.04002141 15.686474,7.36149035 16.2245455,7.36363636 L16.3636364,7.36363636 C17.267375,7.36363636 18,8.09626132 18,9 C18,9.90373868 17.267375,10.6363636 16.3636364,10.6363636 L16.29,10.6363636 C15.7519285,10.6385096 15.2665104,10.9599786 15.0545455,11.4545455 Z" id="Path"></path>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

@ -1,7 +1,7 @@
<svg width="16px" height="18px" viewBox="0 0 16 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="LifeBuoy" transform="translate(-1322.000000, -90.000000)" stroke="#B8BEC9" stroke-width="1.6">
<g id="LifeBuoy" transform="translate(-1322.000000, -90.000000)" stroke="#B8BEC9" stroke-width="1">
<g id="share-2-copy" transform="translate(1323.000000, 91.000000)">
<ellipse id="Oval" cx="11.6666667" cy="2.4" rx="2.33333333" ry="2.4"></ellipse>
<ellipse id="Oval" cx="2.33333333" cy="8" rx="2.33333333" ry="2.4"></ellipse>

Before

Width:  |  Height:  |  Size: 954 B

After

Width:  |  Height:  |  Size: 952 B

View file

@ -0,0 +1,7 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg">
<g style="stroke:#454545;stroke-width:1;fill:none;fill-rule:evenodd;stroke-linecap:round;stroke-linejoin:round" transform="translate(1 1)">
<path d="m9.33333333 0h3.11111107c.8591097 0 1.5555556.69644595 1.5555556 1.55555556v10.88888884c0 .8591097-.6964459 1.5555556-1.5555556 1.5555556h-3.11111107"/>
<path d="m5.44444444 10.8888889 3.88888889-3.8888889-3.88888889-3.88888889"/>
<path d="m9.33333333 7h-9.33333333"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 546 B

View file

@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<g fill="none" fill-rule="evenodd" stroke="#454545" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2">
<g fill="none" fill-rule="evenodd" stroke="#454545" stroke-linecap="round" stroke-linejoin="round" stroke-width="1">
<path d="M5.667 15H2.556C1.696 15 1 14.304 1 13.444V2.556C1 1.696 1.696 1 2.556 1h3.11M11.111 11.889L15 8l-3.889-3.889M15 8H5.667"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 364 B

After

Width:  |  Height:  |  Size: 362 B

View file

@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
<g fill="none" fill-rule="evenodd" stroke="#454545" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2">
<g fill="none" fill-rule="evenodd" stroke="#454545" stroke-linecap="round" stroke-linejoin="round" stroke-width="1">
<path d="M13 9v2.667c0 .736-.597 1.333-1.333 1.333H2.333A1.333 1.333 0 0 1 1 11.667V9M10.667 4.333L7.333 1 4 4.333M7.333 1v8"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 359 B

After

Width:  |  Height:  |  Size: 357 B

View file

@ -1,7 +1,7 @@
<svg width="20px" height="16px" viewBox="0 0 20 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="LifeBuoy" transform="translate(-1269.000000, -143.000000)" stroke="#FFFFFF" stroke-width="1.6">
<g id="LifeBuoy" transform="translate(-1269.000000, -143.000000)" stroke="#FFFFFF" stroke-width="1">
<g id="user-plus-copy" transform="translate(1270.000000, 144.000000)">
<path d="M12.2727273,14 L12.2727273,12.4444444 C12.2727273,10.7262252 10.8074774,9.33333333 9,9.33333333 L3.27272727,9.33333333 C1.46524991,9.33333333 1.81672859e-16,10.7262252 0,12.4444444 L0,14" id="Path"></path>
<ellipse id="Oval" cx="6.13636364" cy="3.11111111" rx="3.27272727" ry="3.11111111"></ellipse>

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -1,7 +1,7 @@
<svg width="15px" height="16px" viewBox="0 0 15 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="LifeBuoy" transform="translate(-1406.000000, -91.000000)" stroke="#B8BEC9" stroke-width="1.6">
<g id="LifeBuoy" transform="translate(-1406.000000, -91.000000)" stroke="#B8BEC9" stroke-width="1">
<g id="user-copy" transform="translate(1407.000000, 92.000000)">
<path d="M13,14 L13,12.4444444 C13,10.7262252 11.5449254,9.33333333 9.75,9.33333333 L3.25,9.33333333 C1.45507456,9.33333333 0,10.7262252 0,12.4444444 L0,14" id="Path"></path>
<ellipse id="Oval" cx="6.5" cy="3.11111111" rx="3.25" ry="3.11111111"></ellipse>

Before

Width:  |  Height:  |  Size: 797 B

After

Width:  |  Height:  |  Size: 795 B

View file

@ -1,7 +1,7 @@
<svg width="20px" height="16px" viewBox="0 0 20 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="LifeBuoy-Copy" transform="translate(-78.000000, -813.000000)" stroke="#293042" stroke-width="1.6">
<g id="LifeBuoy-Copy" transform="translate(-78.000000, -813.000000)" stroke="#293042" stroke-width="1">
<g id="Buoy-Copy" transform="translate(68.000000, 802.000000)">
<g id="users-copy" transform="translate(11.000000, 12.000000)">
<path d="M13.0909091,14 L13.0909091,12.4444444 C13.0909091,10.7262252 11.6256592,9.33333333 9.81818182,9.33333333 L3.27272727,9.33333333 C1.46524991,9.33333333 1.81672859e-16,10.7262252 0,12.4444444 L0,14" id="Path"></path>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -1,7 +1,7 @@
<svg width="20px" height="13px" viewBox="0 0 20 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="LifeBuoy" transform="translate(-1164.000000, -874.000000)" stroke="#B8BEC9" stroke-width="1.6">
<g id="LifeBuoy" transform="translate(-1164.000000, -874.000000)" stroke="#B8BEC9" stroke-width="1">
<g id="video-copy" transform="translate(1165.000000, 875.000000)">
<polygon id="Path" points="18 1.57142857 12.2727273 5.5 18 9.42857143"></polygon>
<rect id="Rectangle" x="0" y="0" width="12.2727273" height="11" rx="1.6"></rect>

Before

Width:  |  Height:  |  Size: 707 B

After

Width:  |  Height:  |  Size: 705 B

View file

@ -1,5 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="12" viewBox="0 0 14 12">
<g fill="none" fill-rule="evenodd" stroke="#454545" stroke-linecap="round" stroke-linejoin="round">
<path d="M6.018 1.532l-4.864 7.81c-.204.34-.205.76-.003 1.1.202.341.577.554.985.558h9.728c.408-.004.783-.217.985-.558.202-.34.201-.76-.003-1.1l-4.864-7.81A1.159 1.159 0 0 0 7 1c-.401 0-.774.202-.982.532zM7 3v4"/>
</g>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 14 12" style="enable-background:new 0 0 14 12;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#454545;stroke-linecap:round;stroke-linejoin:round;}
</style>
<g>
<path class="st0" d="M6,1.5L1.2,9.3c-0.2,0.3-0.2,0.8,0,1.1c0.2,0.3,0.6,0.6,1,0.6h9.7c0.4,0,0.8-0.2,1-0.6c0.2-0.3,0.2-0.8,0-1.1
L8,1.5C7.8,1.2,7.4,1,7,1C6.6,1,6.2,1.2,6,1.5z M7,4v3"/>
</g>
<line class="st0" x1="7" y1="9" x2="7" y2="9"/>
</svg>

Before

Width:  |  Height:  |  Size: 425 B

After

Width:  |  Height:  |  Size: 704 B

View file

@ -1,186 +1,196 @@
// unified palette
// try to use these colors when possible
$bg-color: #181b21;
$base-color: #1b1f25;
$base-text-color: #edf3ff;
$header-panel-bg-color: #22262e;
$header-panel-border-color: #000000;
$header-panel-text-primary-color: #a1b2d1;
$header-panel-text-secondary-color: #c8c8cd;
$text-primary-color: #edf3ff;
$text-secondary-color: #a1b2d1;
$search-bg-color: #181b21;
$search-placeholder-color: #61708b;
$room-highlight-color: #343a46;
// typical text (dark-on-white in light skin)
$primary-fg-color: #212121;
$primary-bg-color: #2d2d2d;
// used for focusing form controls
$focus-bg-color: #101010;
$primary-fg-color: $text-primary-color;
$primary-bg-color: $bg-color;
// used for dialog box text
$light-fg-color: #747474;
$light-fg-color: $header-panel-text-secondary-color;
// 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;
// used for focusing form controls
$focus-bg-color: $room-highlight-color;
$selection-fg-color: $primary-fg-color;
$mention-user-pill-bg-color: $warning-color;
$other-user-pill-bg-color: $room-highlight-color;
$focus-brightness: 200%;
// informational plinth
$info-plinth-bg-color: $header-panel-bg-color;
$info-plinth-fg-color: #888;
// red warning colour
$warning-color: #ff0064;
$warning-bg-color: #DF2A8B;
$info-bg-color: #2A9EDF;
$preview-bar-bg-color: $header-panel-bg-color;
// groups
$info-plinth-bg-color: #454545;
$other-user-pill-bg-color: rgba(255, 255, 255, 0.1);
$preview-bar-bg-color: #333;
// left-panel style muted accent color
$secondary-accent-color: $primary-bg-color;
$tertiary-accent-color: #454545;
// stop the tinter trying to change the secondary accent color
// by overriding the key to something untintable
// XXX: this is a bit of a hack.
#mx_theme_secondaryAccentColor {
color: #c0ffee ! important;
}
#mx_theme_tertiaryAccentColor {
color: #c0ffee ! important;
}
// used by RoomDirectory permissions
$plinth-bg-color: #474747;
// used by RoomDropTarget
$droptarget-bg-color: rgba(45,45,45,0.5);
$tagpanel-bg-color: $base-color;
// used by AddressSelector
$selected-color: #000000;
$selected-color: $room-highlight-color;
// selected for hoverover & selected event tiles
$event-selected-color: #353535;
$event-selected-color: $search-bg-color;
// used for the hairline dividers in RoomView
$primary-hairline-color: #474747;
$primary-hairline-color: $header-panel-border-color;
// used for the border of input text fields
$input-border-color: #3a3a3a;
$input-border-color: #e7e7e7;
$input-darker-bg-color: $search-bg-color;
$input-darker-fg-color: $search-placeholder-color;
$input-lighter-bg-color: #f2f5f8;
$input-lighter-fg-color: $input-darker-fg-color;
$input-focused-border-color: #238cf5;
$input-valid-border-color: $accent-color;
$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;
$field-focused-label-bg-color: $bg-color;
// used for UserSettings EditableText
$input-underline-color: $primary-fg-color;
$input-fg-color: $primary-fg-color;
// scrollbars
$scrollbar-thumb-color: rgba(255, 255, 255, 0.2);
$scrollbar-track-color: transparent;
// context menus
$menu-border-color: rgba(187, 187, 187, 0.5);
$menu-bg-color: #373737;
$menu-selected-color: #f5f8fa;
$menu-border-color: $header-panel-border-color;
$menu-bg-color: $header-panel-bg-color;
$menu-box-shadow-color: $bg-color;
$menu-selected-color: $room-highlight-color;
$avatar-initial-color: #2d2d2d;
$avatar-bg-color: #ffffff;
$menu-selected-color: #f5f8fa;
$avatar-initial-color: #ffffff;
$avatar-bg-color: $bg-color;
$h3-color: $primary-fg-color;
$dialog-background-bg-color: #000;
$dialog-background-bg-color: $header-panel-bg-color;
$lightbox-background-bg-color: #000;
$greyed-fg-color: #888;
$neutral-badge-color: #888;
$preview-widget-bar-color: $menu-bg-color;
$preview-widget-fg-color: $greyed-fg-color;
$blockquote-bar-color: #ddd;
$blockquote-fg-color: #777;
$settings-grey-fg-color: #a2a2a2;
$settings-profile-placeholder-bg-color: #e7e7e7;
$settings-profile-overlay-bg-color: #000;
$settings-profile-overlay-placeholder-bg-color: transparent;
$settings-profile-overlay-fg-color: #fff;
$settings-profile-overlay-placeholder-fg-color: #454545;
$settings-subsection-fg-color: $text-secondary-color;
$voip-decline-color: #f48080;
$voip-accept-color: #80f480;
$topleftmenu-color: $text-primary-color;
$roomheader-color: $text-primary-color;
$roomheader-addroom-color: $header-panel-text-primary-color;
$tagpanel-button-color: $header-panel-text-primary-color;
$roomheader-button-color: $header-panel-text-primary-color;
$groupheader-button-color: $header-panel-text-primary-color;
$rightpanel-button-color: $header-panel-text-primary-color;
$composer-button-color: $header-panel-text-primary-color;
$roomtopic-color: $text-secondary-color;
$eventtile-meta-color: $roomtopic-color;
$rte-bg-color: #353535;
$rte-code-bg-color: #000;
$header-divider-color: $header-panel-text-primary-color;
$room-warning-bg-color: #2d2d2d;
$roomtile-name-color: $header-panel-text-primary-color;
$roomtile-selected-color: $text-primary-color;
$roomtile-notified-color: $text-primary-color;
$roomtile-selected-bg-color: $room-highlight-color;
$roomtile-focused-bg-color: $room-highlight-color;
// ********************
$roomtile-transparent-focused-color: rgba(0, 0, 0, 0.1);
$roomtile-name-color: rgba(186, 186, 186, 0.8);
$roomtile-selected-bg-color: #333;
$roomtile-focused-bg-color: rgba(255, 255, 255, 0.2);
$panel-divider-color: $header-panel-border-color;
$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;
$roomsublist-chevron-color: $accent-color;
$panel-divider-color: rgba(118, 207, 166, 0.2);
// ********************
$widget-menu-bar-bg-color: $tertiary-accent-color;
// ********************
// event tile lifecycle
$event-encrypting-color: rgba(171, 221, 188, 0.4);
$event-sending-color: #888;
$event-notsent-color: #f44;
// event redaction
$event-redacted-fg-color: #606060;
$event-redacted-border-color: #000000;
$widget-menu-bar-bg-color: $search-bg-color;
// event timestamp
$event-timestamp-color: #acacac;
$event-timestamp-color: $text-secondary-color;
$edit-button-url: "$(res)/img/icon_context_message_dark.svg";
$copy-button-url: "$(res)/img/icon_copy_message_dark.svg";
// Tabbed views
$tab-label-fg-color: $text-primary-color;
$tab-label-active-fg-color: $text-primary-color;
$tab-label-bg-color: transparent;
$tab-label-active-bg-color: $accent-color;
$tab-label-icon-bg-color: $text-primary-color;
$tab-label-active-icon-bg-color: $text-primary-color;
// e2e
$e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color
$e2e-unverified-color: #e8bf37;
$e2e-warning-color: #ba6363;
// Buttons
$button-primary-fg-color: #ffffff;
$button-primary-bg-color: $accent-color;
$button-secondary-bg-color: transparent;
$button-danger-fg-color: #ffffff;
$button-danger-bg-color: $notice-primary-color;
$button-danger-disabled-fg-color: #ffffff;
$button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color
/*** ImageView ***/
$lightbox-bg-color: #454545;
$lightbox-fg-color: #ffffff;
$lightbox-border-color: #ffffff;
$room-warning-bg-color: $header-panel-bg-color;
$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);
$panel-gradient: rgba(34, 38, 46, 0), rgba(34, 38, 46, 1);
// unused?
$progressbar-color: #000;
/*** 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_Dialog, .mx_MatrixChat {
:not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=text],
:not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=search],
.mx_textinput {
background-color: transparent;
color: $input-darker-fg-color;
border: 1px solid #c1c1c1;
}
.mx_textinput {
> input[type=text],
> input[type=search] {
color: $primary-fg-color;
}
input::placeholder {
color: $roomsublist-label-fg-color;
}
}
}
/*** panels ***/
.dark-panel {
background-color: $header-panel-bg-color;
}
.dark-panel {
:not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=text],
:not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=search],
.mx_textinput {
color: $input-darker-fg-color;
background-color: $input-darker-bg-color;
border: none;
}
}
.light-panel {
:not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=text],
:not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=search],
.mx_textinput {
color: $input-lighter-fg-color;
background-color: $input-lighter-bg-color;
border: none;
}
}
// ***** Mixins! *****
// XXX: copypasted from _base in order to pick up the right FG color...
@define-mixin mx_DialogButton {
/* align images in buttons (eg spinners) */
vertical-align: middle;
border: 0px;
border-radius: 36px;
border-radius: 4px;
font-family: $font-family;
font-size: 14px;
color: $accent-fg-color;
background-color: $accent-color;
color: $button-fg-color;
background-color: $button-bg-color;
width: auto;
padding: 7px;
padding-left: 1.5em;
@ -190,12 +200,16 @@ $progressbar-color: #000;
outline: none;
}
@define-mixin mx_DialogButton_danger {
background-color: $accent-color;
}
@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;
background-color: $button-secondary-bg-color;
}
// Nasty hacks to apply a filter to arbitrary monochrome artwork to make it
@ -232,8 +246,3 @@ $progressbar-color: #000;
}
}
}
// Add a line to the right side of the left panel to distinguish it from the middle panel
.mx_LeftPanel {
border-right: 1px solid $tertiary-accent-color;
}

View file

@ -1,5 +1,5 @@
@import "../../light/css/_paths.scss";
@import "../../light/css/_fonts.scss";
@import "../../light/css/_base.scss";
@import "../../light/css/_light.scss";
@import "_dark.scss";
@import "../../../../res/css/_components.scss";

View file

@ -1,53 +0,0 @@
/*
* Nunito.
* Includes extended Latin and Vietnamese character sets
* Current URLs are taken from
* https://github.com/alexeiva/NunitoFont/releases/tag/v3.500
* ...in order to include cyrillic.
*
* Previously, they were
* https://fonts.googleapis.com/css?family=Nunito:400,400i,600,600i,700,700i&subset=latin-ext,vietnamese
*
* We explicitly do not include Nunito's italic variants, as they are not italic enough
* and it's better to rely on the browser's built-in obliquing behaviour.
*/
/* the 'src' links are relative to the bundle.css, which is in a subdirectory.
*/
@font-face {
font-family: 'Nunito';
font-style: normal;
font-weight: 400;
src: url('$(res)/fonts/Nunito/Nunito-Regular.ttf') format('truetype');
}
@font-face {
font-family: 'Nunito';
font-style: normal;
font-weight: 600;
src: url('$(res)/fonts/Nunito/Nunito-SemiBold.ttf') format('truetype');
}
@font-face {
font-family: 'Nunito';
font-style: normal;
font-weight: 700;
src: url('$(res)/fonts/Nunito/Nunito-Bold.ttf') format('truetype');
}
/*
* Fira Mono
* Used for monospace copy, i.e. code
*/
@font-face {
font-family: 'Fira Mono';
src: url('$(res)/fonts/Fira_Mono/FiraMono-Regular.ttf') format('truetype');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Fira Mono';
src: url('$(res)/fonts/Fira_Mono/FiraMono-Bold.ttf') format('truetype');
font-weight: 700;
font-style: normal;
}

View file

@ -1,4 +0,0 @@
@import "../../light/css/_paths.scss";
@import "_fonts.scss";
@import "_dharma.scss";
@import "../../../../res/css/_components.scss";

View file

@ -1,276 +0,0 @@
/* 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
horizontal mess. Arial empirically gets it right, hence prioritising
Arial here. */
$font-family: 'Open Sans', 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: #76CFA6;
$accent-color-alt: $accent-color;
$accent-color-50pct: #76CFA67F;
$selection-fg-color: $primary-bg-color;
$focus-brightness: 125%;
// red warning colour
$warning-color: #ff0064;
// background colour for warnings
$warning-bg-color: #DF2A8B;
$info-bg-color: #2A9EDF;
$mention-user-pill-bg-color: #ff0064;
$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: #eaf5f0;
$tertiary-accent-color: #d3efe1;
$tagpanel-bg-color: $tertiary-accent-color;
// 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: #f0f0f0;
$input-border-dark-color: #b8b8b8;
$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;
$input-focused-border-color: #238cf5;
$input-valid-border-color: #7ac9a1;
$field-focused-label-bg-color: #ffffff;
// used for UserSettings EditableText
$input-underline-color: rgba(151, 151, 151, 0.5);
$input-fg-color: rgba(74, 74, 74, 0.9);
// scrollbars
$scrollbar-thumb-color: rgba(0, 0, 0, 0.2);
$scrollbar-track-color: transparent;
// 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;
$h3-color: #3d3b39;
$dialog-title-fg-color: #454545;
$dialog-backdrop-color: rgba(46, 48, 51, 0.38);
$dialog-shadow-color: rgba(0, 0, 0, 0.48);
$dialog-close-fg-color: #9fa9ba;
$dialog-background-bg-color: #e9e9e9;
$lightbox-background-bg-color: #000;
$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;
$settings-profile-placeholder-bg-color: #e7e7e7;
$settings-profile-overlay-bg-color: #000;
$settings-profile-overlay-placeholder-bg-color: transparent;
$settings-profile-overlay-fg-color: #fff;
$settings-profile-overlay-placeholder-fg-color: #454545;
$settings-subsection-fg-color: #61708b;
$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: $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;
$composer-e2e-icon-color: #c9ced6;
// ********************
$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);
$roomsublist-label-fg-color: $h3-color;
$roomsublist-label-bg-color: $tertiary-accent-color;
$roomsublist-chevron-color: $accent-color;
$panel-divider-color: rgba(118, 207, 166, 0.2);
// ********************
$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: "$(res)/img/icon_context_message.svg";
$copy-button-url: "$(res)/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;
$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);
// Tabbed views
$tab-label-fg-color: #45474a;
$tab-label-active-fg-color: #ffffff;
$tab-label-bg-color: transparent;
$tab-label-active-bg-color: #7ac9a1;
$tab-label-icon-bg-color: #454545;
$tab-label-active-icon-bg-color: #ffffff;
// Buttons
$button-primary-fg-color: #ffffff;
$button-primary-bg-color: #7ac9a1;
$button-primary-disabled-fg-color: #ffffff;
$button-primary-disabled-bg-color: #bce4d0;
$button-danger-fg-color: #ffffff;
$button-danger-bg-color: #f56679;
$button-danger-disabled-fg-color: #ffffff;
$button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color
// Toggle switch
$togglesw-off-color: #c1c9d6;
$togglesw-on-color: #7ac9a1;
$togglesw-ball-color: #fff;
// unused?
$progressbar-color: #000;
$room-warning-bg-color: #fff8e3;
$memberstatus-placeholder-color: $roomtile-name-color;
$authpage-bg-color: #2e3649;
$authpage-modal-bg-color: rgba(255, 255, 255, 0.59);
$authpage-body-bg-color: #ffffff;
$authpage-lang-color: #4e5054;
$authpage-body-color: #61708b;
// ***** Mixins! *****
@define-mixin mx_DialogButton {
/* align images in buttons (eg spinners) */
vertical-align: middle;
border: 0px;
border-radius: 36px;
font-family: $font-family;
font-size: 14px;
color: $accent-fg-color;
background-color: $accent-color;
width: auto;
padding: 7px;
padding-left: 1.5em;
padding-right: 1.5em;
cursor: pointer;
display: inline-block;
outline: none;
}
@define-mixin mx_DialogButton_danger {
background-color: $warning-color;
}
@define-mixin mx_DialogButton_hover {
}
@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;
}

View file

@ -1,50 +1,36 @@
/*
* Open Sans
* Includes extended Latin, Greek, Cyrillic and Vietnamese character sets
* Nunito.
* Includes extended Latin and Vietnamese character sets
* Current URLs are taken from
* https://github.com/alexeiva/NunitoFont/releases/tag/v3.500
* ...in order to include cyrillic.
*
* Previously, they were
* https://fonts.googleapis.com/css?family=Nunito:400,400i,600,600i,700,700i&subset=latin-ext,vietnamese
*
* We explicitly do not include Nunito's italic variants, as they are not italic enough
* and it's better to rely on the browser's built-in obliquing behaviour.
*/
/* the 'src' links are relative to the bundle.css, which is in a subdirectory.
*/
@font-face {
font-family: 'Open Sans';
src: url('$(res)/fonts/Open_Sans/OpenSans-Regular.ttf') format('truetype');
font-weight: 400;
font-style: normal;
font-family: 'Nunito';
font-style: normal;
font-weight: 400;
src: url('$(res)/fonts/Nunito/Nunito-Regular.ttf') format('truetype');
}
@font-face {
font-family: 'Open Sans';
src: url('$(res)/fonts/Open_Sans/OpenSans-Italic.ttf') format('truetype');
font-weight: 400;
font-style: italic;
font-family: 'Nunito';
font-style: normal;
font-weight: 600;
src: url('$(res)/fonts/Nunito/Nunito-SemiBold.ttf') format('truetype');
}
@font-face {
font-family: 'Open Sans';
src: url('$(res)/fonts/Open_Sans/OpenSans-Semibold.ttf') format('truetype');
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: 'Open Sans';
src: url('$(res)/fonts/Open_Sans/OpenSans-SemiboldItalic.ttf') format('truetype');
font-weight: 600;
font-style: italic;
}
@font-face {
font-family: 'Open Sans';
src: url('$(res)/fonts/Open_Sans/OpenSans-Bold.ttf') format('truetype');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'Open Sans';
src: url('$(res)/fonts/Open_Sans/OpenSans-BoldItalic.ttf') format('truetype');
font-weight: 700;
font-style: italic;
font-family: 'Nunito';
font-style: normal;
font-weight: 700;
src: url('$(res)/fonts/Nunito/Nunito-Bold.ttf') format('truetype');
}
/*

View file

@ -6,6 +6,13 @@
Arial here. */
$font-family: 'Nunito', Arial, Helvetica, Sans-Serif;
// unified palette
// try to use these colors when possible
$accent-color: #03b381;
$notice-primary-color: #ff4b55;
$notice-secondary-color: #61708b;
$header-panel-bg-color: #f2f5f8;
// typical text (dark-on-white in light skin)
$primary-fg-color: #454545;
$primary-bg-color: #ffffff;
@ -18,7 +25,6 @@ $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;
@ -27,7 +33,7 @@ $selection-fg-color: $primary-bg-color;
$focus-brightness: 105%;
// red warning colour
$warning-color: #f56679;
$warning-color: $notice-primary-color;
// background colour for warnings
$warning-bg-color: #DF2A8B;
$info-bg-color: #2A9EDF;
@ -35,8 +41,8 @@ $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;
$pinned-unread-color: $notice-primary-color;
$pinned-color: $notice-secondary-color;
// informational plinth
$info-plinth-bg-color: #f7f7f7;
@ -72,11 +78,11 @@ $input-darker-fg-color: #9fa9ba;
$input-lighter-bg-color: #f2f5f8;
$input-lighter-fg-color: $input-darker-fg-color;
$input-focused-border-color: #238cf5;
$input-valid-border-color: #7ac9a1;
$input-valid-border-color: $accent-color;
$field-focused-label-bg-color: #ffffff;
$button-bg-color: #7ac9a1;
$button-bg-color: $accent-color;
$button-fg-color: white;
// apart from login forms, which have stronger border
@ -91,6 +97,7 @@ $scrollbar-track-color: transparent;
// context menus
$menu-border-color: #ebedf8;
$menu-bg-color: #fff;
$menu-box-shadow-color: rgba(118, 131, 156, 0.6);
$menu-selected-color: #f5f8fa;
$avatar-initial-color: #ffffff;
@ -139,10 +146,16 @@ $rte-group-pill-color: #aaa;
$topleftmenu-color: #212121;
$roomheader-color: #45474a;
$roomheader-addroom-color: #91A1C0;
$tagpanel-button-color: #91A1C0;
$roomheader-button-color: #91A1C0;
$groupheader-button-color: #91A1C0;
$rightpanel-button-color: #91A1C0;
$composer-button-color: #91A1C0;
$roomtopic-color: #9fa9ba;
$eventtile-meta-color: $roomtopic-color;
$composer-e2e-icon-color: #c9ced6;
$header-divider-color: #91A1C0;
// ********************
@ -152,14 +165,14 @@ $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;
$username-variant1-color: #368bd6;
$username-variant2-color: #ac3ba8;
$username-variant3-color: #03b381;
$username-variant4-color: #e64f7a;
$username-variant5-color: #ff812d;
$username-variant6-color: #2dc2c5;
$username-variant7-color: #5c56f5;
$username-variant8-color: #74d12c;
$roomtile-transparent-focused-color: rgba(0, 0, 0, 0.1);
@ -205,26 +218,24 @@ $lightbox-border-color: #ffffff;
$tab-label-fg-color: #45474a;
$tab-label-active-fg-color: #ffffff;
$tab-label-bg-color: transparent;
$tab-label-active-bg-color: #7ac9a1;
$tab-label-active-bg-color: $accent-color;
$tab-label-icon-bg-color: #454545;
$tab-label-active-icon-bg-color: #ffffff;
// Buttons
$button-primary-fg-color: #ffffff;
$button-primary-bg-color: #7ac9a1;
$button-primary-disabled-fg-color: #ffffff;
$button-primary-disabled-bg-color: #bce4d0;
$button-primary-bg-color: $accent-color;
$button-secondary-bg-color: $accent-fg-color;
$button-danger-fg-color: #ffffff;
$button-danger-bg-color: #f56679;
$button-danger-bg-color: $notice-primary-color;
$button-danger-disabled-fg-color: #ffffff;
$button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color
// Toggle switch
$togglesw-off-color: #c1c9d6;
$togglesw-on-color: #7ac9a1;
$togglesw-on-color: $accent-color;
$togglesw-ball-color: #fff;
// unused?
$progressbar-color: #000;
$room-warning-bg-color: #fff8e3;
@ -235,7 +246,10 @@ $authpage-bg-color: #2e3649;
$authpage-modal-bg-color: rgba(255, 255, 255, 0.59);
$authpage-body-bg-color: #ffffff;
$authpage-lang-color: #4e5054;
$authpage-body-color: #61708b;
$authpage-primary-color: #454545;
$authpage-secondary-color: #61708b;
$panel-gradient: rgba(242, 245, 248, 0), rgba(242, 245, 248, 1);
/*** form elements ***/
@ -270,23 +284,13 @@ $authpage-body-color: #61708b;
border: none;
flex: 1;
color: $primary-fg-color;
},
}
input::placeholder {
color: $roomsublist-label-fg-color;
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;
@ -319,7 +323,6 @@ input[type=search].mx_textinput_icon {
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 {
@ -332,7 +335,7 @@ 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;
display: none;
}
.input[type=text]::-webkit-input-placeholder,
@ -386,5 +389,5 @@ textarea::placeholder {
font-weight: 600;
border: 1px solid $accent-color ! important;
color: $accent-color;
background-color: $accent-fg-color;
background-color: $button-secondary-bg-color;
}

View file

@ -1,4 +1,4 @@
@import "_paths.scss";
@import "_fonts.scss";
@import "_base.scss";
@import "_light.scss";
@import "../../../../res/css/_components.scss";

View file

@ -177,7 +177,7 @@ class MatrixClientPeg {
userId: creds.userId,
deviceId: creds.deviceId,
timelineSupport: true,
forceTURN: !SettingsStore.getValue('webRtcForcePeerToPeer', false),
forceTURN: !SettingsStore.getValue('webRtcAllowPeerToPeer', false),
verificationMethods: [verificationMethods.SAS]
};

View file

@ -32,7 +32,7 @@ module.exports = {
return false;
} else if (ev.getType() == 'm.call.answer' || ev.getType() == 'm.call.hangup') {
return false;
} else if (ev.getType == 'm.room.message' && ev.getContent().msgtype == 'm.notify') {
} else if (ev.getType() == 'm.room.message' && ev.getContent().msgtype == 'm.notify') {
return false;
}
const EventTile = sdk.getComponent('rooms.EventTile');

View file

@ -240,7 +240,6 @@ export default React.createClass({
_renderPhasePassPhrase: function() {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
let strengthMeter;
let helpText;
@ -265,8 +264,15 @@ export default React.createClass({
}
return <div>
<p>{_t("Secure your encrypted message history with a Recovery Passphrase.")}</p>
<p>{_t("You'll need it if you log out or lose access to this device.")}</p>
<p>{_t(
"<b>Warning</b>: you should only set up key backup from a trusted computer.", {},
{ b: sub => <b>{sub}</b> },
)}</p>
<p>{_t(
"We'll store an encrypted copy of your keys on our server. " +
"Protect your backup with a passphrase to keep it secure.",
)}</p>
<p>{_t("For maximum security, this should be different from your account password.")}</p>
<div className="mx_CreateKeyBackupDialog_primaryContainer">
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">
@ -291,34 +297,12 @@ export default React.createClass({
disabled={!this._passPhraseIsValid()}
/>
<p>{_t(
"If you don't want encrypted message history to be available on other devices, "+
"<button>opt out</button>.",
{},
{
button: sub => <AccessibleButton
element="span"
className="mx_linkButton"
onClick={this._onOptOutClick}
>
{sub}
</AccessibleButton>,
},
)}</p>
<p>{_t(
"Or, if you don't want to create a Recovery Passphrase, skip this step and "+
"<button>download a recovery key</button>.",
{},
{
button: sub => <AccessibleButton
element="span"
className="mx_linkButton"
onClick={this._onSkipPassPhraseClick}
>
{sub}
</AccessibleButton>,
},
)}</p>
<details>
<summary>{_t("Advanced")}</summary>
<p><button onClick={this._onSkipPassPhraseClick} >
{_t("Set up with a Recovery Key")}
</button></p>
</details>
</div>;
},
@ -353,9 +337,7 @@ export default React.createClass({
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return <div>
<p>{_t(
"Type in your Recovery Passphrase to confirm you remember it. " +
"If it helps, add it to your password manager or store it " +
"somewhere safe.",
"Please enter your passphrase a second time to confirm.",
)}</p>
<div className="mx_CreateKeyBackupDialog_primaryContainer">
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">
@ -392,7 +374,13 @@ export default React.createClass({
}
return <div>
<p>{_t("Make a copy of this Recovery Key and keep it safe.")}</p>
<p>{_t(
"Your recovery key is a safety net - you can use it to restore " +
"access to your encrypted messages if you forget your passphrase.",
)}</p>
<p>{_t(
"Keep your recovery key somewhere very secure, like a password manager (or a safe)",
)}</p>
<p>{bodyText}</p>
<div className="mx_CreateKeyBackupDialog_primaryContainer">
<div className="mx_CreateKeyBackupDialog_recoveryKeyHeader">
@ -455,10 +443,9 @@ export default React.createClass({
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return <div>
<p>{_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.")}</p>
<DialogButtons primaryButton={_t('Close')}
"Your keys are being backed up (the first backup could take a few minutes).",
)}</p>
<DialogButtons primaryButton={_t('Okay')}
onPrimaryButtonClick={this._onDone}
hasCancel={false}
/>
@ -484,19 +471,19 @@ export default React.createClass({
_titleForPhase: function(phase) {
switch (phase) {
case PHASE_PASSPHRASE:
return _t('Create a Recovery Passphrase');
return _t('Secure your backup with a passphrase');
case PHASE_PASSPHRASE_CONFIRM:
return _t('Confirm Recovery Passphrase');
return _t('Confirm your passphrase');
case PHASE_OPTOUT_CONFIRM:
return _t('Warning!');
case PHASE_SHOWKEY:
return _t('Recovery Key');
return _t('Recovery key');
case PHASE_KEEPITSAFE:
return _t('Keep it safe');
case PHASE_BACKINGUP:
return _t('Starting backup...');
case PHASE_DONE:
return _t('Backup Started');
return _t('Success!');
default:
return _t("Create Key Backup");
}

View file

@ -39,36 +39,8 @@ export default class NewRecoveryMethodDialog extends React.PureComponent {
}
onSetupClick = async () => {
// TODO: Should change to a restore key backup flow that checks the
// recovery passphrase while at the same time also cross-signing the
// device as well in a single flow. Since we don't have that yet, we'll
// look for an unverified device and verify it. Note that this means
// we won't restore keys yet; instead we'll only trust the backup for
// sending our own new keys to it.
let backupSigStatus;
try {
backupSigStatus = await MatrixClientPeg.get().isKeyBackupTrusted(this.props.newVersionInfo);
} catch (e) {
console.log("Unable to fetch key backup status", e);
return;
}
let unverifiedDevice;
for (const sig of backupSigStatus.sigs) {
if (!sig.device.isVerified()) {
unverifiedDevice = sig.device;
break;
}
}
if (!unverifiedDevice) {
console.log("Unable to find a device to verify.");
return;
}
const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog');
Modal.createTrackedDialog('Device Verify Dialog', '', DeviceVerifyDialog, {
userId: MatrixClientPeg.get().credentials.userId,
device: unverifiedDevice,
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, {
onFinished: this.props.onFinished,
});
}
@ -111,11 +83,6 @@ export default class NewRecoveryMethodDialog extends React.PureComponent {
} else {
content = <div>
{newMethodDetected}
<p>{_t(
"Setting up Secure Messages on this device " +
"will re-encrypt this device's message history with " +
"the new recovery method.",
)}</p>
{hackWarning}
<DialogButtons
primaryButton={_t("Set up Secure Messages")}

View file

@ -114,10 +114,15 @@ export default class AutoHideScrollbar extends React.Component {
}
}
getScrollTop() {
return this.containerRef.scrollTop;
}
render() {
return (<div
ref={this._collectContainerRef}
className={["mx_AutoHideScrollbar", this.props.className].join(" ")}
onScroll={this.props.onScroll}
>
<div className="mx_AutoHideScrollbar_offset">
{ this.props.children }

View file

@ -1157,7 +1157,6 @@ export default React.createClass({
render: function() {
const GroupAvatar = sdk.getComponent("avatars.GroupAvatar");
const Spinner = sdk.getComponent("elements.Spinner");
const TintableSvg = sdk.getComponent("elements.TintableSvg");
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
if (this.state.summaryLoading && this.state.error === null || this.state.saving) {
@ -1248,13 +1247,17 @@ export default React.createClass({
if (this.state.editing) {
rightButtons.push(
<AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
onClick={this._onSaveClick} key="_saveButton"
key="_saveButton"
onClick={this._onSaveClick}
>
{ _t('Save') }
</AccessibleButton>,
);
rightButtons.push(
<AccessibleButton className="mx_RoomHeader_cancelButton" onClick={this._onCancelClick} key="_cancelButton">
<AccessibleButton className="mx_RoomHeader_cancelButton"
key="_cancelButton"
onClick={this._onCancelClick}
>
<img src={require("../../../res/img/cancel.svg")} className="mx_filterFlipColor"
width="18" height="18" alt={_t("Cancel")} />
</AccessibleButton>,
@ -1262,16 +1265,20 @@ export default React.createClass({
} else {
if (summary.user && summary.user.membership === 'join') {
rightButtons.push(
<AccessibleButton className="mx_GroupHeader_button"
onClick={this._onEditClick} title={_t("Community Settings")} key="_editButton"
<AccessibleButton className="mx_GroupHeader_button mx_GroupHeader_editButton"
key="_editButton"
onClick={this._onEditClick}
title={_t("Community Settings")}
>
<TintableSvg src={require("../../../res/img/icons-settings-room.svg")} width="16" height="16" />
</AccessibleButton>,
);
}
rightButtons.push(
<AccessibleButton className="mx_GroupHeader_button" onClick={this._onShareClick} title={_t('Share Community')} key="_shareButton">
<TintableSvg src={require("../../../res/img/icons-share.svg")} width="16" height="16" />
<AccessibleButton className="mx_GroupHeader_button mx_GroupHeader_shareButton"
key="_shareButton"
onClick={this._onShareClick}
title={_t('Share Community')}
>
</AccessibleButton>,
);
}

View file

@ -59,6 +59,10 @@ export default class IndicatorScrollbar extends React.Component {
}
}
getScrollTop() {
return this._autoHideScrollbar.getScrollTop();
}
componentWillUnmount() {
if (this._scrollElement) {
this._scrollElement.removeEventListener("scroll", this.checkOverflow);

View file

@ -182,6 +182,7 @@ const LeftPanel = React.createClass({
render: function() {
const RoomList = sdk.getComponent('rooms.RoomList');
const RoomBreadcrumbs = sdk.getComponent('rooms.RoomBreadcrumbs');
const TagPanel = sdk.getComponent('structures.TagPanel');
const CustomRoomTagPanel = sdk.getComponent('structures.CustomRoomTagPanel');
const TopLeftMenuButton = sdk.getComponent('structures.TopLeftMenuButton');
@ -215,12 +216,17 @@ const LeftPanel = React.createClass({
onCleared={ this.onSearchCleared }
collapsed={this.props.collapsed} />);
let breadcrumbs;
if (SettingsStore.isFeatureEnabled("feature_room_breadcrumbs")) {
breadcrumbs = (<RoomBreadcrumbs collapsed={this.props.collapsed} />);
}
return (
<div className={containerClasses}>
{ tagPanelContainer }
<aside className={"mx_LeftPanel dark-panel"} onKeyDown={ this._onKeyDown } onFocus={ this._onFocus } onBlur={ this._onBlur }>
<TopLeftMenuButton collapsed={ this.props.collapsed } />
{ breadcrumbs }
{ searchBox }
<CallPreview ConferenceHandler={VectorConferenceHandler} />
<RoomList

View file

@ -574,11 +574,8 @@ export default React.createClass({
const UserSettingsDialog = sdk.getComponent("dialogs.UserSettingsDialog");
Modal.createTrackedDialog('User settings', '', UserSettingsDialog, {}, 'mx_SettingsDialog');
// View the home page if we need something to look at
if (!this.state.currentGroupId && !this.state.currentRoomId) {
this._setPage(PageTypes.HomePage);
this.notifyNewScreen('home');
}
// View the welcome or home page if we need something to look at
this._viewSomethingBehindModal();
break;
}
case 'view_create_room':
@ -595,11 +592,8 @@ export default React.createClass({
config: this.props.config,
}, 'mx_RoomDirectory_dialogWrapper');
// View the home page if we need something to look at
if (!this.state.currentGroupId && !this.state.currentRoomId) {
this._setPage(PageTypes.HomePage);
this.notifyNewScreen('home');
}
// View the welcome or home page if we need something to look at
this._viewSomethingBehindModal();
}
break;
case 'view_my_groups':
@ -660,11 +654,9 @@ export default React.createClass({
});
break;
}
// case 'set_theme':
// disable changing the theme for now
// as other themes are not compatible with dharma
// this._onSetTheme(payload.value);
// break;
case 'set_theme':
this._onSetTheme(payload.value);
break;
case 'on_logging_in':
// We are now logging in, so set the state to reflect that
// NB. This does not touch 'ready' since if our dispatches
@ -825,6 +817,7 @@ export default React.createClass({
this.focusComposer = true;
const newState = {
view: VIEWS.LOGGED_IN,
currentRoomId: roomInfo.room_id || null,
page_type: PageTypes.RoomView,
thirdPartyInvite: roomInfo.third_party_invite,
@ -887,6 +880,16 @@ export default React.createClass({
this.notifyNewScreen('group/' + groupId);
},
_viewSomethingBehindModal() {
if (this.state.view !== VIEWS.LOGGED_IN) {
this._viewWelcome();
return;
}
if (!this.state.currentGroupId && !this.state.currentRoomId) {
this._viewHome();
}
},
_viewWelcome() {
this.setStateForNewView({
view: VIEWS.WELCOME,
@ -1552,11 +1555,7 @@ export default React.createClass({
payload.room_id = roomString;
}
// we can't view a room unless we're logged in
// (a guest account is fine)
if (this.state.view === VIEWS.LOGGED_IN) {
dis.dispatch(payload);
}
dis.dispatch(payload);
} else if (screen.indexOf('user/') == 0) {
const userId = screen.substring(5);

View file

@ -78,6 +78,11 @@ module.exports = React.createClass({
this.protocols = null;
this.setState({protocolsLoading: true});
if (!MatrixClientPeg.get()) {
// We may not have a client yet when invoked from welcome page
this.setState({protocolsLoading: false});
return;
}
MatrixClientPeg.get().getThirdpartyProtocols().done((response) => {
this.protocols = response;
this.setState({protocolsLoading: false});

View file

@ -27,7 +27,8 @@ import IndicatorScrollbar from './IndicatorScrollbar';
import { KeyCode } from '../../Keyboard';
import { Group } from 'matrix-js-sdk';
import PropTypes from 'prop-types';
import RoomTile from "../views/rooms/RoomTile";
import LazyRenderList from "../views/elements/LazyRenderList";
// turn this on for drop & drag console debugging galore
const debug = false;
@ -60,6 +61,9 @@ const RoomSubList = React.createClass({
getInitialState: function() {
return {
hidden: this.props.startAsHidden || false,
// some values to get LazyRenderList starting
scrollerHeight: 800,
scrollTop: 0,
};
},
@ -134,23 +138,21 @@ const RoomSubList = React.createClass({
this.setState(this.state);
},
makeRoomTiles: function() {
const RoomTile = sdk.getComponent("rooms.RoomTile");
return this.props.list.map((room, index) => {
return <RoomTile
room={room}
roomSubList={this}
tagName={this.props.tagName}
key={room.roomId}
collapsed={this.props.collapsed || false}
unread={Unread.doesRoomHaveUnreadMessages(room)}
highlight={room.getUnreadNotificationCount('highlight') > 0 || this.props.isInvite}
isInvite={this.props.isInvite}
refreshSubList={this._updateSubListCount}
incomingCall={null}
onClick={this.onRoomTileClick}
/>;
});
makeRoomTile: function(room) {
return <RoomTile
room={room}
roomSubList={this}
tagName={this.props.tagName}
key={room.roomId}
collapsed={this.props.collapsed || false}
unread={Unread.doesRoomHaveUnreadMessages(room)}
highlight={room.getUnreadNotificationCount('highlight') > 0 || this.props.isInvite}
notificationCount={room.getUnreadNotificationCount()}
isInvite={this.props.isInvite}
refreshSubList={this._updateSubListCount}
incomingCall={null}
onClick={this.onRoomTileClick}
/>;
},
_onNotifBadgeClick: function(e) {
@ -269,6 +271,29 @@ const RoomSubList = React.createClass({
if (this.refs.subList) {
this.refs.subList.style.height = `${height}px`;
}
this._updateLazyRenderHeight(height);
},
_updateLazyRenderHeight: function(height) {
this.setState({scrollerHeight: height});
},
_onScroll: function() {
this.setState({scrollTop: this.refs.scroller.getScrollTop()});
},
_getRenderItems: function() {
// try our best to not create a new array
// because LazyRenderList rerender when the items prop
// is not the same object as the previous value
const {list, extraTiles} = this.props;
if (!extraTiles || !extraTiles.length) {
return list;
}
if (!list || list.length) {
return extraTiles;
}
return list.concat(extraTiles);
},
render: function() {
@ -286,12 +311,15 @@ const RoomSubList = React.createClass({
{this._getHeaderJsx(isCollapsed)}
</div>;
} else {
const tiles = this.makeRoomTiles();
tiles.push(...this.props.extraTiles);
return <div ref="subList" className={subListClasses}>
{this._getHeaderJsx(isCollapsed)}
<IndicatorScrollbar ref="scroller" className="mx_RoomSubList_scroll">
{ tiles }
<IndicatorScrollbar ref="scroller" className="mx_RoomSubList_scroll" onScroll={ this._onScroll }>
<LazyRenderList
scrollTop={this.state.scrollTop }
height={ this.state.scrollerHeight }
renderItem={ this.makeRoomTile }
itemHeight={34}
items={this._getRenderItems()} />
</IndicatorScrollbar>
</div>;
}

View file

@ -283,6 +283,15 @@ module.exports = React.createClass({
}
},
_getRoomId() {
// According to `_onRoomViewStoreUpdate`, `state.roomId` can be null
// if we have a room alias we haven't resolved yet. To work around this,
// first we'll try the room object if it's there, and then fallback to
// the bare room ID. (We may want to update `state.roomId` after
// resolving aliases, so we could always trust it.)
return this.state.room ? this.state.room.roomId : this.state.roomId;
},
_onWidgetEchoStoreUpdate: function() {
this.setState({
showApps: this._shouldShowApps(this.state.room),
@ -784,6 +793,7 @@ module.exports = React.createClass({
this._updateConfCallNotification();
this._updateDMState();
this._checkIfAlone(this.state.room);
this._updateE2EStatus(this.state.room);
}, 500),
_checkIfAlone: function(room) {
@ -877,13 +887,12 @@ module.exports = React.createClass({
// If the user is a ROU, allow them to transition to a PWLU
if (cli && cli.isGuest()) {
// Join this room once the user has registered and logged in
const signUrl = this.props.thirdPartyInvite ?
this.props.thirdPartyInvite.inviteSignUrl : undefined;
// (If we failed to peek, we may not have a valid room object.)
dis.dispatch({
action: 'do_after_sync_prepared',
deferred_action: {
action: 'view_room',
room_id: this.state.room.roomId,
room_id: this._getRoomId(),
},
});

View file

@ -21,7 +21,7 @@ import { _t } from '../../languageHandler';
import { KeyCode } from '../../Keyboard';
import sdk from '../../index';
import dis from '../../dispatcher';
import rate_limited_func from '../../ratelimitedfunc';
import { throttle } from 'lodash';
import AccessibleButton from '../../components/views/elements/AccessibleButton';
module.exports = React.createClass({
@ -67,12 +67,9 @@ module.exports = React.createClass({
this.onSearch();
},
onSearch: new rate_limited_func(
function() {
this.props.onSearch(this.refs.search.value);
},
500,
),
onSearch: throttle(function() {
this.props.onSearch(this.refs.search.value);
}, 200, {trailing: true, leading: true}),
_onKeyDown: function(ev) {
switch (ev.keyCode) {

View file

@ -64,6 +64,17 @@ module.exports = React.createClass({
getInitialState: function() {
const customURLsAllowed = !SdkConfig.get()['disable_custom_urls'];
let initialPhase = PHASE_SERVER_DETAILS;
if (
// if we have these two, skip to the good bit
// (they could come in from the URL params in a
// registration email link)
(this.props.clientSecret && this.props.sessionId) ||
// or if custom URLs aren't allowed, skip them
!customURLsAllowed
) {
initialPhase = PHASE_REGISTRATION;
}
return {
busy: false,
@ -87,7 +98,7 @@ module.exports = React.createClass({
hsUrl: this.props.customHsUrl,
isUrl: this.props.customIsUrl,
// Phase of the overall registration dialog.
phase: customURLsAllowed ? PHASE_SERVER_DETAILS : PHASE_REGISTRATION,
phase: initialPhase,
flows: null,
};
},
@ -111,7 +122,7 @@ module.exports = React.createClass({
});
},
onServerTypeChange(type) {
onServerTypeChange(type, initial) {
this.setState({
serverType: type,
});
@ -137,9 +148,15 @@ module.exports = React.createClass({
hsUrl: this.props.defaultHsUrl,
isUrl: this.props.defaultIsUrl,
});
this.setState({
phase: PHASE_SERVER_DETAILS,
});
// if this is the initial value from the control and we're
// already in the registration phase, don't go back to the
// server details phase (but do if it's actually a change resulting
// from user interaction).
if (!initial || !this.state.phase === PHASE_REGISTRATION) {
this.setState({
phase: PHASE_SERVER_DETAILS,
});
}
break;
}
},
@ -372,9 +389,12 @@ module.exports = React.createClass({
// If we're on a different phase, we only show the server type selector,
// which is always shown if we allow custom URLs at all.
if (PHASES_ENABLED && this.state.phase !== PHASE_SERVER_DETAILS) {
// if we've been given a custom HS URL we should actually pass that, so
// that the appropriate section is selected at the start to match the
// homeserver URL we're using
return <div>
<ServerTypeSelector
defaultHsUrl={this.props.defaultHsUrl}
defaultHsUrl={this.props.customHsUrl || this.props.defaultHsUrl}
onChange={this.onServerTypeChange}
/>
</div>;

View file

@ -90,7 +90,11 @@ export default class ServerTypeSelector extends React.PureComponent {
selected: type,
};
if (onChange) {
onChange(type);
// FIXME: Supply a second 'initial' param here to flag that this is
// initialising the value rather than from user interaction
// (which sometimes we'll want to ignore). Must be a better way
// to do this.
onChange(type, true);
}
}

View file

@ -90,6 +90,11 @@ module.exports = React.createClass({
this.closeMenu();
},
e2eInfoClicked: function() {
this.props.e2eInfoCallback();
this.closeMenu();
},
onViewSourceClick: function() {
const ViewSource = sdk.getComponent('structures.ViewSource');
Modal.createTrackedDialog('View Event Source', '', ViewSource, {
@ -332,6 +337,13 @@ module.exports = React.createClass({
);
}
let e2eInfo;
if (this.props.e2eInfoCallback) {
e2eInfo = <div className="mx_MessageContextMenu_field" onClick={this.e2eInfoClicked}>
{ _t('End-to-end encryption information') }
</div>;
}
return (
<div className="mx_MessageContextMenu">
{ resendButton }
@ -347,6 +359,7 @@ module.exports = React.createClass({
{ replyButton }
{ externalURLButton }
{ collapseReplyThread }
{ e2eInfo }
</div>
);
},

View file

@ -68,7 +68,7 @@ export default class TagTileContextMenu extends React.Component {
<hr className="mx_TagTileContextMenu_separator" />
<div className="mx_TagTileContextMenu_item" onClick={this._onRemoveClick} >
<img className="mx_TagTileContextMenu_item_icon" src={require("../../../../res/img/icon_context_delete.svg")} width="15" height="15" />
{ _t('Remove') }
{ _t('Hide') }
</div>
</div>;
}

View file

@ -20,11 +20,15 @@ import { _t } from '../../../languageHandler';
import LogoutDialog from "../dialogs/LogoutDialog";
import Modal from "../../../Modal";
import SdkConfig from '../../../SdkConfig';
import MatrixClientPeg from '../../../MatrixClientPeg';
export class TopLeftMenu extends React.Component {
constructor() {
super();
this.viewHomePage = this.viewHomePage.bind(this);
this.viewWelcomePage = this.viewWelcomePage.bind(this);
this.openSettings = this.openSettings.bind(this);
this.signIn = this.signIn.bind(this);
this.signOut = this.signOut.bind(this);
}
@ -41,6 +45,8 @@ export class TopLeftMenu extends React.Component {
}
render() {
const isGuest = MatrixClientPeg.get().isGuest();
let homePageSection = null;
if (this.hasHomePage()) {
homePageSection = <ul className="mx_TopLeftMenu_section">
@ -48,14 +54,26 @@ export class TopLeftMenu extends React.Component {
</ul>;
}
let signInOutSection;
if (isGuest) {
signInOutSection = <ul className="mx_TopLeftMenu_section">
<li className="mx_TopLeftMenu_icon_signin" onClick={this.signIn}>{_t("Sign in")}</li>
</ul>;
} else {
signInOutSection = <ul className="mx_TopLeftMenu_section">
<li className="mx_TopLeftMenu_icon_signout" onClick={this.signOut}>{_t("Sign out")}</li>
</ul>;
}
return <div className="mx_TopLeftMenu">
{homePageSection}
<ul className="mx_TopLeftMenu_section">
<li className="mx_TopLeftMenu_icon_settings" onClick={this.openSettings}>{_t("Settings")}</li>
<li className="mx_TopLeftMenu_icon_welcome" onClick={this.viewWelcomePage}>{_t("Welcome")}</li>
</ul>
<ul className="mx_TopLeftMenu_section">
<li className="mx_TopLeftMenu_icon_signout" onClick={this.signOut}>{_t("Sign out")}</li>
<li className="mx_TopLeftMenu_icon_settings" onClick={this.openSettings}>{_t("Settings")}</li>
</ul>
{signInOutSection}
</div>;
}
@ -64,11 +82,21 @@ export class TopLeftMenu extends React.Component {
this.closeMenu();
}
viewWelcomePage() {
dis.dispatch({action: 'view_welcome_page'});
this.closeMenu();
}
openSettings() {
dis.dispatch({action: 'view_user_settings'});
this.closeMenu();
}
signIn() {
dis.dispatch({action: 'start_login'});
this.closeMenu();
}
signOut() {
Modal.createTrackedDialog('Logout E2E Export', '', LogoutDialog);
this.closeMenu();

View file

@ -60,6 +60,11 @@ export default class DeviceVerifyDialog extends React.Component {
}
_onSwitchToLegacyClick = () => {
if (this._verifier) {
this._verifier.removeListener('show_sas', this._onVerifierShowSas);
this._verifier.cancel('User cancel');
this._verifier = null;
}
this.setState({mode: MODE_LEGACY});
}
@ -184,11 +189,21 @@ export default class DeviceVerifyDialog extends React.Component {
_renderSasVerificationPhaseWaitAccept() {
const Spinner = sdk.getComponent("views.elements.Spinner");
const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton');
return (
<div>
<Spinner />
<p>{_t("Waiting for partner to accept...")}</p>
<p>{_t(
"Nothing appearing? Not all clients support interactive verification yet. " +
"<button>Use legacy verification</button>.",
{}, {button: sub => <AccessibleButton element='span' className="mx_linkButton"
onClick={this._onSwitchToLegacyClick}
>
{sub}
</AccessibleButton>},
)}</p>
</div>
);
}

View file

@ -16,6 +16,7 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import MatrixClientPeg from '../../../MatrixClientPeg';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
@ -37,9 +38,12 @@ export default class IncomingSasDialog extends React.Component {
this.state = {
phase: PHASE_START,
sasVerified: false,
opponentProfile: null,
opponentProfileError: null,
};
this.props.verifier.on('show_sas', this._onVerifierShowSas);
this.props.verifier.on('cancel', this._onVerifierCancel);
this._fetchOpponentProfile();
}
componentWillUnmount() {
@ -49,6 +53,21 @@ export default class IncomingSasDialog extends React.Component {
this.props.verifier.removeListener('show_sas', this._onVerifierShowSas);
}
async _fetchOpponentProfile() {
try {
const prof = await MatrixClientPeg.get().getProfileInfo(
this.props.verifier.userId,
);
this.setState({
opponentProfile: prof,
});
} catch (e) {
this.setState({
opponentProfileError: e,
});
}
}
_onFinished = () => {
this.props.onFinished(this.state.phase === PHASE_VERIFIED);
}
@ -93,10 +112,39 @@ export default class IncomingSasDialog extends React.Component {
_renderPhaseStart() {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const Spinner = sdk.getComponent("views.elements.Spinner");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
let profile;
if (this.state.opponentProfile) {
profile = <div className="mx_IncomingSasDialog_opponentProfile">
<BaseAvatar name={this.state.opponentProfile.displayname}
idName={this.props.verifier.userId}
url={MatrixClientPeg.get().mxcUrlToHttp(
this.state.opponentProfile.avatar_url,
Math.floor(48 * window.devicePixelRatio),
Math.floor(48 * window.devicePixelRatio),
'crop',
)}
width={48} height={48} resizeMethod='crop'
/>
<h2>{this.state.opponentProfile.displayname}</h2>
</div>;
} else if (this.state.opponentProfileError) {
profile = <div>
<BaseAvatar name={this.props.verifier.userId.slice(1)}
idName={this.props.verifier.userId}
width={48} height={48}
/>
<h2>{this.props.verifier.userId}</h2>
</div>;
} else {
profile = <Spinner />;
}
return (
<div>
<h2>{this.props.verifier.userId}</h2>
{profile}
<p>{_t(
"Verify this user to mark them as trusted. " +
"Trusting users gives you extra peace of mind when using " +

View file

@ -22,6 +22,10 @@ import { _t } from '../../../languageHandler';
import MatrixClientPeg from '../../../MatrixClientPeg';
export default class LogoutDialog extends React.Component {
defaultProps = {
onFinished: function() {},
}
constructor() {
super();
this._onSettingsLinkClick = this._onSettingsLinkClick.bind(this);
@ -29,13 +33,37 @@ export default class LogoutDialog extends React.Component {
this._onFinished = this._onFinished.bind(this);
this._onSetRecoveryMethodClick = this._onSetRecoveryMethodClick.bind(this);
this._onLogoutConfirm = this._onLogoutConfirm.bind(this);
this.state = {
loading: false,
backupInfo: null,
error: null,
};
if (!MatrixClientPeg.get().getKeyBackupEnabled()) {
this._loadBackupStatus();
}
}
async _loadBackupStatus() {
try {
const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
this.setState({
loading: false,
backupInfo,
});
} catch (e) {
console.log("Unable to fetch key backup status", e);
this.setState({
loading: false,
error: e,
});
}
}
_onSettingsLinkClick() {
// close dialog
if (this.props.onFinished) {
this.props.onFinished();
}
this.props.onFinished();
}
_onExportE2eKeysClicked() {
@ -52,9 +80,7 @@ export default class LogoutDialog extends React.Component {
dis.dispatch({action: 'logout'});
}
// close dialog
if (this.props.onFinished) {
this.props.onFinished();
}
this.props.onFinished();
}
_onSetRecoveryMethodClick() {
@ -63,72 +89,83 @@ export default class LogoutDialog extends React.Component {
);
// close dialog
if (this.props.onFinished) {
this.props.onFinished();
}
this.props.onFinished();
}
_onLogoutConfirm() {
dis.dispatch({action: 'logout'});
// close dialog
if (this.props.onFinished) {
this.props.onFinished();
}
this.props.onFinished();
}
render() {
const description = <div>
<p>{_t(
"When you log out, you'll lose your secure message history. To prevent " +
"this, set up a recovery method.",
)}</p>
<p>{_t(
"Alternatively, advanced users can also manually export encryption keys in " +
"<a>Settings</a> before logging out.", {},
{
a: sub => <a href='#/settings' onClick={this._onSettingsLinkClick}>{sub}</a>,
},
"Encrypted messages are secured with end-to-end encryption. " +
"Only you and the recipient(s) have the keys to read these messages.",
)}</p>
<p>{_t("Back up your keys before signing out to avoid losing them.")}</p>
</div>;
if (!MatrixClientPeg.get().getKeyBackupEnabled()) {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
let dialogContent;
if (this.state.loading) {
const Spinner = sdk.getComponent('views.elements.Spinner');
dialogContent = <Spinner />;
} else {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
let setupButtonCaption;
if (this.state.backupInfo) {
setupButtonCaption = _t("Use Key Backup");
} else {
// if there's an error fetching the backup info, we'll just assume there's
// no backup for the purpose of the button caption
setupButtonCaption = _t("Start using Key Backup");
}
dialogContent = <div>
<div className="mx_Dialog_content" id='mx_Dialog_content'>
{ description }
</div>
<DialogButtons primaryButton={setupButtonCaption}
hasCancel={false}
onPrimaryButtonClick={this._onSetRecoveryMethodClick}
focus={true}
>
<button onClick={this._onLogoutConfirm}>
{_t("I don't want my encrypted messages")}
</button>
</DialogButtons>
<details>
<summary>{_t("Advanced")}</summary>
<p><button onClick={this._onExportE2eKeysClicked}>
{_t("Manually export keys")}
</button></p>
</details>
</div>;
}
// Not quite a standard question dialog as the primary button cancels
// the action and does something else instead, whilst non-default button
// confirms the action.
return (<BaseDialog
title={_t("Warning!")}
title={_t("You'll lose access to your encrypted messages")}
contentId='mx_Dialog_content'
hasCancel={false}
onFinsihed={this._onFinished}
hasCancel={true}
onFinished={this._onFinished}
>
<div className="mx_Dialog_content" id='mx_Dialog_content'>
{ description }
</div>
<DialogButtons primaryButton={_t('Set a Recovery Method')}
hasCancel={false}
onPrimaryButtonClick={this._onSetRecoveryMethodClick}
focus={true}
>
<button onClick={this._onLogoutConfirm}>
{_t("I understand, log out without")}
</button>
</DialogButtons>
{dialogContent}
</BaseDialog>);
} else {
const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog');
return (<QuestionDialog
hasCancelButton={true}
title={_t("Sign out")}
// TODO: This is made up by me and would need to also mention verifying
// once you can restore a backup by verifying a device
description={_t(
"When signing in again, you can access encrypted chat history by " +
"restoring your key backup. You'll need your recovery passphrase " +
"or, if you didn't set a recovery passphrase, your recovery key " +
"(that you downloaded).",
"Are you sure you want to sign out?",
)}
button={_t("Sign out")}
onFinished={this._onFinished}

View file

@ -25,35 +25,12 @@ import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
import { markAllDevicesKnown } from '../../../cryptodevices';
function DeviceListEntry(props) {
const {userId, device} = props;
const DeviceVerifyButtons = sdk.getComponent('elements.DeviceVerifyButtons');
return (
<li>
{ device.deviceId }
<DeviceVerifyButtons device={device} userId={userId} />
<br />
{ device.getDisplayName() }
</li>
);
}
DeviceListEntry.propTypes = {
userId: PropTypes.string.isRequired,
// deviceinfo
device: PropTypes.object.isRequired,
};
function UserUnknownDeviceList(props) {
const MemberDeviceInfo = sdk.getComponent('rooms.MemberDeviceInfo');
const {userId, userDevices} = props;
const deviceListEntries = Object.keys(userDevices).map((deviceId) =>
<DeviceListEntry key={deviceId} userId={userId}
device={userDevices[deviceId]} />,
<li key={deviceId}><MemberDeviceInfo device={userDevices[deviceId]} userId={userId} showDeviceId={true} /></li>,
);
return (

View file

@ -230,10 +230,15 @@ export default React.createClass({
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
title = _t("Enter Recovery Passphrase");
content = <div>
{_t(
<p>{_t(
"<b>Warning</b>: you should only set up key backup " +
"from a trusted computer.", {},
{ b: sub => <b>{sub}</b> },
)}</p>
<p>{_t(
"Access your secure message history and set up secure " +
"messaging by entering your recovery passphrase.",
)}<br />
)}</p>
<div className="mx_RestoreKeyBackupDialog_primaryContainer">
<input type="password"
@ -288,10 +293,15 @@ export default React.createClass({
}
content = <div>
{_t(
<p>{_t(
"<b>Warning</b>: you should only set up key backup " +
"from a trusted computer.", {},
{ b: sub => <b>{sub}</b> },
)}</p>
<p>{_t(
"Access your secure message history and set up secure " +
"messaging by entering your recovery key.",
)}<br />
)}</p>
<div className="mx_RestoreKeyBackupDialog_primaryContainer">
<input className="mx_RestoreKeyBackupDialog_recoveryKeyInput"

View file

@ -579,8 +579,8 @@ export default class AppTile extends React.Component {
// editing is done in scalar
const canUserModify = this._canUserModify();
const showEditButton = Boolean(this._scalarClient && canUserModify);
const showDeleteButton = canUserModify;
const showCancelButton = !showDeleteButton;
const showDeleteButton = (this.props.showDelete === undefined || this.props.showDelete) && canUserModify;
const showCancelButton = (this.props.showCancel === undefined || this.props.showCancel) && !showDeleteButton;
// Picture snapshot - only show button when apps are maximised.
const showPictureSnapshotButton = this._hasCapability('m.capability.screenshot') && this.props.show;
const showMinimiseButton = this.props.showMinimise && this.props.show;

View file

@ -0,0 +1,97 @@
/*
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";
const OVERFLOW_ITEMS = 20;
const OVERFLOW_MARGIN = 5;
class ItemRange {
constructor(topCount, renderCount, bottomCount) {
this.topCount = topCount;
this.renderCount = renderCount;
this.bottomCount = bottomCount;
}
contains(range) {
return range.topCount >= this.topCount &&
(range.topCount + range.renderCount) <= (this.topCount + this.renderCount);
}
expand(amount) {
const topGrow = Math.min(amount, this.topCount);
const bottomGrow = Math.min(amount, this.bottomCount);
return new ItemRange(
this.topCount - topGrow,
this.renderCount + topGrow + bottomGrow,
this.bottomCount - bottomGrow,
);
}
}
export default class LazyRenderList extends React.Component {
constructor(props) {
super(props);
const renderRange = LazyRenderList.getVisibleRangeFromProps(props).expand(OVERFLOW_ITEMS);
this.state = {renderRange};
}
static getVisibleRangeFromProps(props) {
const {items, itemHeight, scrollTop, height} = props;
const length = items ? items.length : 0;
const topCount = Math.max(0, Math.floor(scrollTop / itemHeight));
const itemsAfterTop = length - topCount;
const renderCount = Math.min(Math.ceil(height / itemHeight), itemsAfterTop);
const bottomCount = itemsAfterTop - renderCount;
return new ItemRange(topCount, renderCount, bottomCount);
}
componentWillReceiveProps(props) {
const state = this.state;
const range = LazyRenderList.getVisibleRangeFromProps(props);
const intersectRange = range.expand(OVERFLOW_MARGIN);
const prevSize = this.props.items ? this.props.items.length : 0;
const listHasChangedSize = props.items.length !== prevSize;
// only update renderRange if the list has shrunk/grown and we need to adjust padding or
// if the new range isn't contained by the old anymore
if (listHasChangedSize || !state.renderRange || !state.renderRange.contains(intersectRange)) {
this.setState({renderRange: range.expand(OVERFLOW_ITEMS)});
}
}
shouldComponentUpdate(nextProps, nextState) {
const itemsChanged = nextProps.items !== this.props.items;
const rangeChanged = nextState.renderRange !== this.state.renderRange;
return itemsChanged || rangeChanged;
}
render() {
const {itemHeight, items, renderItem} = this.props;
const {renderRange} = this.state;
const paddingTop = renderRange.topCount * itemHeight;
const paddingBottom = renderRange.bottomCount * itemHeight;
const renderedItems = (items || []).slice(
renderRange.topCount,
renderRange.topCount + renderRange.renderCount,
);
return (<div style={{paddingTop: `${paddingTop}px`, paddingBottom: `${paddingBottom}px`}}>
{ renderedItems.map(renderItem) }
</div>);
}
}

View file

@ -24,7 +24,6 @@ import ScalarMessaging from '../../../ScalarMessaging';
import Modal from "../../../Modal";
import { _t } from '../../../languageHandler';
import AccessibleButton from './AccessibleButton';
import TintableSvg from './TintableSvg';
export default class ManageIntegsButton extends React.Component {
constructor(props) {
@ -76,6 +75,7 @@ export default class ManageIntegsButton extends React.Component {
if (this.scalarClient !== null) {
const integrationsButtonClasses = classNames({
mx_RoomHeader_button: true,
mx_RoomHeader_manageIntegsButton: true,
mx_ManageIntegsButton_error: !!this.state.scalarError,
});
@ -94,8 +94,10 @@ export default class ManageIntegsButton extends React.Component {
}
integrationsButton = (
<AccessibleButton className={integrationsButtonClasses} onClick={this.onManageIntegrations} title={_t('Manage Integrations')}>
<TintableSvg src={require("../../../../res/img/feather-icons/grid.svg")} width="20" height="20" />
<AccessibleButton className={integrationsButtonClasses}
onClick={this.onManageIntegrations}
title={_t('Manage Integrations')}
>
{ integrationsWarningTriangle }
{ integrationsErrorPopup }
</AccessibleButton>

View file

@ -65,12 +65,14 @@ export default class GroupHeaderButtons extends HeaderButtons {
];
return [
<HeaderButton key="_groupMembersButton" title={_t('Members')} iconSrc={require("../../../../res/img/icons-people.svg")}
<HeaderButton key="groupMembersButton" name="groupMembersButton"
title={_t('Members')}
isHighlighted={this.isPhase(groupPhases)}
clickPhase={RightPanel.Phase.GroupMemberList}
analytics={['Right Panel', 'Group Member List Button', 'click']}
/>,
<HeaderButton key="_roomsButton" title={_t('Rooms')} iconSrc={require("../../../../res/img/icons-room-nobg.svg")}
<HeaderButton key="roomsButton" name="roomsButton"
title={_t('Rooms')}
isHighlighted={this.isPhase(roomPhases)}
clickPhase={RightPanel.Phase.GroupRoomList}
analytics={['Right Panel', 'Group Room List Button', 'click']}

View file

@ -23,7 +23,6 @@ import classNames from 'classnames';
import dis from '../../../dispatcher';
import Analytics from '../../../Analytics';
import AccessibleButton from '../elements/AccessibleButton';
import TintableSvg from '../elements/TintableSvg';
export default class HeaderButton extends React.Component {
constructor() {
@ -44,6 +43,7 @@ export default class HeaderButton extends React.Component {
const classes = classNames({
mx_RightPanel_headerButton: true,
mx_RightPanel_headerButton_highlight: this.props.isHighlighted,
[`mx_RightPanel_${this.props.name}`]: true,
});
return <AccessibleButton
@ -51,9 +51,8 @@ export default class HeaderButton extends React.Component {
aria-expanded={this.props.isHighlighted}
title={this.props.title}
className={classes}
onClick={this.onClick} >
<TintableSvg src={this.props.iconSrc} width="20" height="20" />
</AccessibleButton>;
onClick={this.onClick}>
</AccessibleButton>;
}
}
@ -62,14 +61,14 @@ HeaderButton.propTypes = {
isHighlighted: PropTypes.bool.isRequired,
// The phase to swap to when the button is clicked
clickPhase: PropTypes.string.isRequired,
// The source file of the icon to display
iconSrc: PropTypes.string.isRequired,
// The badge to display above the icon
badge: PropTypes.node,
// The parameters to track the click event
analytics: PropTypes.arrayOf(PropTypes.string).isRequired,
// Button name
name: PropTypes.string.isRequired,
// Button title
title: PropTypes.string.isRequired,
};

View file

@ -88,7 +88,7 @@ export default class HeaderButtons extends React.Component {
render() {
// inline style as this will be swapped around in future commits
return <div style={{display: 'flex'}}>
return <div className="mx_HeaderButtons">
{ this.renderButtons() }
</div>;
}

View file

@ -52,17 +52,20 @@ export default class RoomHeaderButtons extends HeaderButtons {
];
return [
<HeaderButton key="_membersButton" title={_t('Members')} iconSrc={require("../../../../res/img/feather-icons/user.svg")}
<HeaderButton key="membersButton" name="membersButton"
title={_t('Members')}
isHighlighted={this.isPhase(membersPhases)}
clickPhase={RightPanel.Phase.RoomMemberList}
analytics={['Right Panel', 'Member List Button', 'click']}
/>,
<HeaderButton key="_filesButton" title={_t('Files')} iconSrc={require("../../../../res/img/feather-icons/files.svg")}
<HeaderButton key="filesButton" name="filesButton"
title={_t('Files')}
isHighlighted={this.isPhase(RightPanel.Phase.FilePanel)}
clickPhase={RightPanel.Phase.FilePanel}
analytics={['Right Panel', 'File List Button', 'click']}
/>,
<HeaderButton key="_notifsButton" title={_t('Notifications')} iconSrc={require("../../../../res/img/feather-icons/notifications.svg")}
<HeaderButton key="notifsButton" name="notifsButton"
title={_t('Notifications')}
isHighlighted={this.isPhase(RightPanel.Phase.NotificationPanel)}
clickPhase={RightPanel.Phase.NotificationPanel}
analytics={['Right Panel', 'Notification List Button', 'click']}

View file

@ -16,6 +16,7 @@ limitations under the License.
import classNames from 'classnames';
import { _t } from '../../../languageHandler';
import AccessibleButton from '../elements/AccessibleButton';
export default function(props) {
const isWarning = props.status === "warning";
@ -35,5 +36,10 @@ export default function(props) {
_t("All devices for this user are trusted") :
_t("All devices in this encrypted room are trusted");
}
return (<div className={e2eIconClasses} title={e2eTitle} />);
const icon = (<div className={e2eIconClasses} title={e2eTitle} />);
if (props.onClick) {
return (<AccessibleButton onClick={props.onClick}>{ icon }</AccessibleButton>);
} else {
return icon;
}
}

View file

@ -327,6 +327,7 @@ module.exports = withMatrixClient(React.createClass({
top: y,
eventTileOps: tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined,
collapseReplyThread: replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined,
e2eInfoCallback: () => this.onCryptoClicked(),
onFinished: function() {
self.setState({menu: false});
},
@ -773,29 +774,31 @@ module.exports.haveTileForEvent = function(e) {
function E2ePadlockUndecryptable(props) {
return (
<E2ePadlock title={_t("Undecryptable")} icon="undecryptable" />
<E2ePadlock title={_t("Undecryptable")} icon="undecryptable" {...props} />
);
}
function E2ePadlockUnverified(props) {
return (
<E2ePadlock title={_t("Encrypted by an unverified device")} icon="unverified" />
<E2ePadlock title={_t("Encrypted by an unverified device")} icon="unverified" {...props} />
);
}
function E2ePadlockUnencrypted(props) {
return (
<E2ePadlock title={_t("Unencrypted message")} icon="unencrypted" />
<E2ePadlock title={_t("Unencrypted message")} icon="unencrypted" {...props} />
);
}
function E2ePadlock(props) {
if (SettingsStore.getValue("alwaysShowEncryptionIcons")) {
return <div
className={`mx_EventTile_e2eIcon mx_EventTile_e2eIcon_${props.icon}`}
title={props.title} onClick={props.onClick} />;
return (<div
className={`mx_EventTile_e2eIcon mx_EventTile_e2eIcon_${props.icon}`}
title={props.title} onClick={props.onClick} />);
} else {
return <div className="mx_EventTile_e2eIcon mx_EventTile_e2eIcon_hidden" onClick={props.onClick} />;
return (<div
className={`mx_EventTile_e2eIcon mx_EventTile_e2eIcon_hidden mx_EventTile_e2eIcon_${props.icon}`}
onClick={props.onClick} />);
}
}

View file

@ -30,7 +30,7 @@ export default class MemberDeviceInfo extends React.Component {
mx_MemberDeviceInfo_icon_unverified: this.props.device.isUnverified(),
});
const indicator = (<div className={iconClasses} />);
const deviceName = this.props.device.ambiguous ?
const deviceName = (this.props.device.ambiguous || this.props.showDeviceId) ?
(this.props.device.getDisplayName() ? this.props.device.getDisplayName() : "") + " (" + this.props.device.deviceId + ")" :
this.props.device.getDisplayName();

View file

@ -941,6 +941,8 @@ module.exports = withMatrixClient(React.createClass({
}
let roomMemberDetails = null;
let e2eIconElement;
if (this.props.member.roomId) { // is in room
const PowerSelector = sdk.getComponent('elements.PowerSelector');
roomMemberDetails = <div>
@ -959,6 +961,11 @@ module.exports = withMatrixClient(React.createClass({
{statusLabel}
</div>
</div>;
const isEncrypted = this.props.matrixClient.isRoomEncrypted(this.props.member.roomId);
if (this.state.e2eStatus && isEncrypted) {
e2eIconElement = (<E2EIcon status={this.state.e2eStatus} isUser={true} />);
}
}
const avatarUrl = this.props.member.getMxcAvatarUrl();
@ -967,7 +974,7 @@ module.exports = withMatrixClient(React.createClass({
const httpUrl = this.props.matrixClient.mxcUrlToHttp(avatarUrl, 800, 800);
avatarElement = <div className="mx_MemberInfo_avatar">
<img src={httpUrl} />
</div>
</div>;
}
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
@ -979,7 +986,7 @@ module.exports = withMatrixClient(React.createClass({
<AccessibleButton className="mx_MemberInfo_cancel" onClick={this.onCancel}>
<img src={require("../../../../res/img/minimise.svg")} width="10" height="16" className="mx_filterFlipColor" alt={_t('Close')} />
</AccessibleButton>
{ this.state.e2eStatus ? <E2EIcon status={this.state.e2eStatus} isUser={true} /> : undefined }
{ e2eIconElement }
<EmojiText element="h2">{ memberName }</EmojiText>
</div>
{ avatarElement }

View file

@ -306,7 +306,6 @@ export default class MessageComposer extends React.Component {
render() {
const uploadInputStyle = {display: 'none'};
const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar');
const TintableSvg = sdk.getComponent("elements.TintableSvg");
const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput");
const controls = [];
@ -335,17 +334,26 @@ export default class MessageComposer extends React.Component {
// Call buttons
if (this.props.callState && this.props.callState !== 'ended') {
hangupButton =
<AccessibleButton key="controls_hangup" className="mx_MessageComposer_hangup" onClick={this.onHangupClick}>
<img src={require("../../../../res/img/hangup.svg")} alt={_t('Hangup')} title={_t('Hangup')} width="25" height="25" />
<AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_hangup"
key="controls_hangup"
onClick={this.onHangupClick}
title={_t('Hangup')}
>
</AccessibleButton>;
} else {
callButton =
<AccessibleButton key="controls_call" className="mx_MessageComposer_voicecall" onClick={this.onVoiceCallClick} title={_t('Voice call')}>
<TintableSvg src={require("../../../../res/img/feather-icons/phone.svg")} width="20" height="20" />
<AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_voicecall"
key="controls_call"
onClick={this.onVoiceCallClick}
title={_t('Voice call')}
>
</AccessibleButton>;
videoCallButton =
<AccessibleButton key="controls_videocall" className="mx_MessageComposer_videocall" onClick={this.onCallClick} title={_t('Video call')}>
<TintableSvg src={require("../../../../res/img/feather-icons/video.svg")} width="20" height="20" />
<AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_videocall"
key="controls_videocall"
onClick={this.onCallClick}
title={_t('Video call')}
>
</AccessibleButton>;
}
@ -385,9 +393,11 @@ export default class MessageComposer extends React.Component {
// check separately for whether we can call, but this is slightly
// complex because of conference calls.
const uploadButton = (
<AccessibleButton key="controls_upload" className="mx_MessageComposer_upload"
onClick={this.onUploadClick} title={_t('Upload file')}>
<TintableSvg src={require("../../../../res/img/feather-icons/paperclip.svg")} width="20" height="20" />
<AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_upload"
key="controls_upload"
onClick={this.onUploadClick}
title={_t('Upload file')}
>
<input ref="uploadInput" type="file"
style={uploadInputStyle}
multiple

View file

@ -0,0 +1,109 @@
/*
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.
*/
'use strict';
import React from "react";
import dis from "../../../dispatcher";
import MatrixClientPeg from "../../../MatrixClientPeg";
import AccessibleButton from '../elements/AccessibleButton';
import RoomAvatar from '../avatars/RoomAvatar';
import classNames from 'classnames';
const MAX_ROOMS = 20;
export default class RoomBreadcrumbs extends React.Component {
constructor(props) {
super(props);
this.state = {rooms: []};
this.onAction = this.onAction.bind(this);
this._previousRoomId = null;
this._dispatcherRef = null;
}
componentWillMount() {
this._dispatcherRef = dis.register(this.onAction);
}
componentWillUnmount() {
dis.unregister(this._dispatcherRef);
}
componentDidUpdate() {
const rooms = this.state.rooms.slice();
if (rooms.length) {
const {room, animated} = rooms[0];
if (!animated) {
rooms[0] = {room, animated: true};
setTimeout(() => this.setState({rooms}), 0);
}
}
}
onAction(payload) {
switch (payload.action) {
case 'view_room':
if (this._previousRoomId) {
this._appendRoomId(this._previousRoomId);
}
this._previousRoomId = payload.room_id;
}
}
_appendRoomId(roomId) {
const room = MatrixClientPeg.get().getRoom(roomId);
if (!room) {
return;
}
const rooms = this.state.rooms.slice();
const existingIdx = rooms.findIndex((r) => r.room.roomId === room.roomId);
if (existingIdx !== -1) {
rooms.splice(existingIdx, 1);
}
rooms.splice(0, 0, {room, animated: false});
if (rooms.length > MAX_ROOMS) {
rooms.splice(MAX_ROOMS, rooms.length - MAX_ROOMS);
}
this.setState({rooms});
}
_viewRoom(room) {
dis.dispatch({action: "view_room", room_id: room.roomId});
}
render() {
// check for collapsed here and
// not at parent so we keep
// rooms in our state
// when collapsing and expanding
if (this.props.collapsed) {
return null;
}
const rooms = this.state.rooms;
const avatars = rooms.map(({room, animated}, i) => {
const isFirst = i === 0;
const classes = classNames({
"mx_RoomBreadcrumbs_preAnimate": isFirst && !animated,
"mx_RoomBreadcrumbs_animate": isFirst,
});
return (
<AccessibleButton className={classes} key={room.roomId} title={room.name} onClick={() => this._viewRoom(room)}>
<RoomAvatar room={room} width={32} height={32} />
</AccessibleButton>
);
});
return (<div className="mx_RoomBreadcrumbs">{ avatars }</div>);
}
}

View file

@ -32,6 +32,7 @@ import {CancelButton} from './SimpleRoomHeader';
import SettingsStore from "../../../settings/SettingsStore";
import RoomHeaderButtons from '../right_panel/RoomHeaderButtons';
import E2EIcon from './E2EIcon';
import * as cryptodevices from '../../../cryptodevices';
module.exports = React.createClass({
displayName: 'RoomHeader',
@ -145,9 +146,14 @@ module.exports = React.createClass({
return !(currentPinEvent.getContent().pinned && currentPinEvent.getContent().pinned.length <= 0);
},
_onShowDevicesClick: function() {
if (this.props.e2eStatus === "warning") {
cryptodevices.showUnknownDeviceDialogForMessages(MatrixClientPeg.get(), this.props.room);
}
},
render: function() {
const RoomAvatar = sdk.getComponent("avatars.RoomAvatar");
const TintableSvg = sdk.getComponent("elements.TintableSvg");
const EmojiText = sdk.getComponent('elements.EmojiText');
let searchStatus = null;
@ -156,7 +162,7 @@ module.exports = React.createClass({
let pinnedEventsButton = null;
const e2eIcon = this.props.e2eStatus ?
<E2EIcon status={this.props.e2eStatus} /> :
<E2EIcon status={this.props.e2eStatus} onClick={this._onShowDevicesClick} /> :
undefined;
if (this.props.onCancelClick) {
@ -221,8 +227,10 @@ module.exports = React.createClass({
if (this.props.onSettingsClick) {
settingsButton =
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onSettingsClick} title={_t("Settings")}>
<TintableSvg src={require("../../../../res/img/feather-icons/settings.svg")} width="20" height="20" />
<AccessibleButton className="mx_RoomHeader_button mx_RoomHeader_settingsButton"
onClick={this.props.onSettingsClick}
title={_t("Settings")}
>
</AccessibleButton>;
}
@ -238,7 +246,6 @@ module.exports = React.createClass({
<AccessibleButton className="mx_RoomHeader_button mx_RoomHeader_pinnedButton"
onClick={this.props.onPinnedClick} title={_t("Pinned Messages")}>
{ pinsIndicator }
<TintableSvg src={require("../../../../res/img/icons-pin.svg")} width="16" height="16" />
</AccessibleButton>;
}
@ -253,24 +260,30 @@ module.exports = React.createClass({
let forgetButton;
if (this.props.onForgetClick) {
forgetButton =
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onForgetClick} title={_t("Forget room")}>
<TintableSvg src={require("../../../../res/img/leave.svg")} width="26" height="20" />
<AccessibleButton className="mx_RoomHeader_button mx_RoomHeader_forgetButton"
onClick={this.props.onForgetClick}
title={_t("Forget room")}
>
</AccessibleButton>;
}
let searchButton;
if (this.props.onSearchClick && this.props.inRoom) {
searchButton =
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onSearchClick} title={_t("Search")}>
<TintableSvg src={require("../../../../res/img/feather-icons/search.svg")} width="20" height="20" />
<AccessibleButton className="mx_RoomHeader_button mx_RoomHeader_searchButton"
onClick={this.props.onSearchClick}
title={_t("Search")}
>
</AccessibleButton>;
}
let shareRoomButton;
if (this.props.inRoom) {
shareRoomButton =
<AccessibleButton className="mx_RoomHeader_button" onClick={this.onShareRoomClick} title={_t('Share room')}>
<TintableSvg src={require("../../../../res/img/feather-icons/share.svg")} width="20" height="20" />
<AccessibleButton className="mx_RoomHeader_button mx_RoomHeader_shareButton"
onClick={this.onShareRoomClick}
title={_t('Share room')}
>
</AccessibleButton>;
}

View file

@ -17,6 +17,7 @@ limitations under the License.
'use strict';
import SettingsStore from "../../../settings/SettingsStore";
import Timer from "../../../utils/Timer";
const React = require("react");
const ReactDOM = require("react-dom");
@ -41,6 +42,7 @@ import {Resizer} from '../../../resizer';
import {Layout, Distributor} from '../../../resizer/distributors/roomsublist2';
const HIDE_CONFERENCE_CHANS = true;
const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/;
const HOVER_MOVE_TIMEOUT = 1000;
function labelForTagName(tagName) {
if (tagName.startsWith('u.')) return tagName.slice(2);
@ -73,6 +75,7 @@ module.exports = React.createClass({
getInitialState: function() {
this._hoverClearTimer = null;
this._subListRefs = {
// key => RoomSubList ref
};
@ -95,7 +98,7 @@ module.exports = React.createClass({
// update overflow indicators
this._checkSubListsOverflow();
// don't store height for collapsed sublists
if(!this.collapsedState[key]) {
if (!this.collapsedState[key]) {
this.subListSizes[key] = size;
window.localStorage.setItem("mx_roomlist_sizes",
JSON.stringify(this.subListSizes));
@ -357,11 +360,32 @@ module.exports = React.createClass({
this.forceUpdate();
},
onMouseEnter: function(ev) {
this.setState({hover: true});
onMouseMove: async function(ev) {
if (!this._hoverClearTimer) {
this.setState({hover: true});
this._hoverClearTimer = new Timer(HOVER_MOVE_TIMEOUT);
this._hoverClearTimer.start();
let finished = true;
try {
await this._hoverClearTimer.finished();
} catch (err) {
finished = false;
}
this._hoverClearTimer = null;
if (finished) {
this.setState({hover: false});
this._delayedRefreshRoomList();
}
} else {
this._hoverClearTimer.restart();
}
},
onMouseLeave: function(ev) {
if (this._hoverClearTimer) {
this._hoverClearTimer.abort();
this._hoverClearTimer = null;
}
this.setState({hover: false});
// Refresh the room list just in case the user missed something.
@ -774,7 +798,7 @@ module.exports = React.createClass({
return (
<div ref={this._collectResizeContainer} className="mx_RoomList"
onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
onMouseMove={this.onMouseMove} onMouseLeave={this.onMouseLeave}>
{ subListComponents }
</div>
);

View file

@ -39,6 +39,7 @@ export default class RoomRecoveryReminder extends React.PureComponent {
loading: true,
error: null,
backupInfo: null,
notNowClicked: false,
};
}
@ -77,6 +78,10 @@ export default class RoomRecoveryReminder extends React.PureComponent {
}
}
onOnNotNowClick = () => {
this.setState({notNowClicked: true});
}
onDontAskAgainClick = () => {
// When you choose "Don't ask again" from the room reminder, we show a
// dialog to confirm the choice.
@ -104,46 +109,54 @@ export default class RoomRecoveryReminder extends React.PureComponent {
}
render() {
if (this.state.loading) {
// If there was an error loading just don't display the banner: we'll try again
// next time the user switchs to the room.
if (this.state.error || this.state.loading || this.state.notNowClicked) {
return null;
}
const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton");
let body;
if (this.state.error) {
body = <div className="error">
{_t("Unable to load key backup status")}
</div>;
} else if (this.state.backupInfo) {
// A key backup exists for this account, but we're not using it.
body = <div>
<p>{_t(
"Secure Key Backup should be active on all of your devices to avoid " +
"losing access to your encrypted messages.",
)}</p>
</div>;
let setupCaption;
if (this.state.backupInfo) {
setupCaption = _t("Use Key Backup");
} else {
body = _t(
"Securely back up your decryption keys to the server to make sure " +
"you'll always be able to read your encrypted messages.",
);
setupCaption = _t("Start using Key Backup");
}
return (
<div className="mx_RoomRecoveryReminder">
<div className="mx_RoomRecoveryReminder_header">{_t(
"Don't risk losing your encrypted messages!",
"Never lose encrypted messages",
)}</div>
<div className="mx_RoomRecoveryReminder_body">{body}</div>
<div className="mx_RoomRecoveryReminder_body">
<p>{_t(
"Messages in this room are secured with end-to-end " +
"encryption. Only you and the recipient(s) have the " +
"keys to read these messages.",
)}</p>
<p>{_t(
"Securely back up your keys to avoid losing them. " +
"<a>Learn more.</a>", {},
{
// TODO: We don't have this link yet: this will prevent the translators
// having to re-translate the string when we do.
a: sub => '',
},
)}</p>
</div>
<div className="mx_RoomRecoveryReminder_buttons">
<AccessibleButton className="mx_RoomRecoveryReminder_button"
onClick={this.onSetupClick}>
{_t("Activate Secure Key Backup")}
{setupCaption}
</AccessibleButton>
<p><AccessibleButton className="mx_RoomRecoveryReminder_secondary mx_linkButton"
onClick={this.onOnNotNowClick}>
{ _t("Not now") }
</AccessibleButton></p>
<p><AccessibleButton className="mx_RoomRecoveryReminder_secondary mx_linkButton"
onClick={this.onDontAskAgainClick}>
{ _t("No thanks, I'll download a copy of my decryption keys before I log out") }
{ _t("Don't ask me again") }
</AccessibleButton></p>
</div>
</div>

View file

@ -108,13 +108,6 @@ module.exports = React.createClass({
return statusUser._unstable_statusMessage;
},
onRoomTimeline: function(ev, room) {
if (room !== this.props.room) return;
this.setState({
notificationCount: this.props.room.getUnreadNotificationCount(),
});
},
onRoomName: function(room) {
if (room !== this.props.room) return;
this.setState({
@ -159,7 +152,6 @@ module.exports = React.createClass({
componentWillMount: function() {
MatrixClientPeg.get().on("accountData", this.onAccountData);
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
MatrixClientPeg.get().on("Room.name", this.onRoomName);
ActiveRoomObserver.addListener(this.props.room.roomId, this._onActiveRoomChange);
this.dispatcherRef = dis.register(this.onAction);
@ -179,7 +171,6 @@ module.exports = React.createClass({
const cli = MatrixClientPeg.get();
if (cli) {
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
}
ActiveRoomObserver.removeListener(this.props.room.roomId, this._onActiveRoomChange);
@ -306,7 +297,7 @@ module.exports = React.createClass({
render: function() {
const isInvite = this.props.room.getMyMembership() === "invite";
const notificationCount = this.state.notificationCount;
const notificationCount = this.props.notificationCount;
// var highlightCount = this.props.room.getUnreadNotificationCount("highlight");
const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge();

View file

@ -226,6 +226,7 @@ export default class Stickerpicker extends React.Component {
showTitle={false}
showMinimise={true}
showDelete={false}
showCancel={false}
showPopout={false}
onMinimiseClick={this._onHideStickersClick}
handleMinimisePointerEvents={true}
@ -321,7 +322,6 @@ export default class Stickerpicker extends React.Component {
}
render() {
const TintableSvg = sdk.getComponent("elements.TintableSvg");
const ContextualMenu = sdk.getComponent('structures.ContextualMenu');
const GenericElementContextMenu = sdk.getComponent('context_menus.GenericElementContextMenu');
let stickersButton;
@ -347,11 +347,11 @@ export default class Stickerpicker extends React.Component {
<AccessibleButton
id='stickersButton'
key="controls_hide_stickers"
className="mx_MessageComposer_stickers mx_Stickers_hideStickers"
className="mx_MessageComposer_button mx_MessageComposer_stickers mx_Stickers_hideStickers"
onClick={this._onHideStickersClick}
ref='target'
title={_t("Hide Stickers")}>
<TintableSvg src={require("../../../../res/img/feather-icons/face.svg")} width="20" height="20" />
title={_t("Hide Stickers")}
>
</AccessibleButton>;
} else {
// Show show-stickers button
@ -359,10 +359,10 @@ export default class Stickerpicker extends React.Component {
<AccessibleButton
id='stickersButton'
key="controls_show_stickers"
className="mx_MessageComposer_stickers"
className="mx_MessageComposer_button mx_MessageComposer_stickers"
onClick={this._onShowStickersClick}
title={_t("Show Stickers")}>
<TintableSvg src={require("../../../../res/img/feather-icons/face.svg")} width="20" height="20" />
title={_t("Show Stickers")}
>
</AccessibleButton>;
}
return <div>

Some files were not shown because too many files have changed in this diff Show more