Merge remote-tracking branch 'origin/develop' into develop
|
@ -44,13 +44,11 @@ src/components/views/rooms/MessageComposer.js
|
|||
src/components/views/rooms/PinnedEventTile.js
|
||||
src/components/views/rooms/RoomList.js
|
||||
src/components/views/rooms/RoomPreviewBar.js
|
||||
src/components/views/rooms/RoomSettings.js
|
||||
src/components/views/rooms/SearchableEntityList.js
|
||||
src/components/views/rooms/SearchBar.js
|
||||
src/components/views/rooms/SearchResultTile.js
|
||||
src/components/views/rooms/TopUnreadMessagesBar.js
|
||||
src/components/views/rooms/UserTile.js
|
||||
src/components/views/settings/AddPhoneNumber.js
|
||||
src/components/views/settings/ChangeAvatar.js
|
||||
src/components/views/settings/ChangePassword.js
|
||||
src/components/views/settings/DevicesPanel.js
|
||||
|
|
|
@ -91,13 +91,13 @@ module.exports = {
|
|||
// to JSX.
|
||||
ignorePattern: '^\\s*<',
|
||||
ignoreComments: true,
|
||||
ignoreRegExpLiterals: true,
|
||||
code: 120,
|
||||
}],
|
||||
"valid-jsdoc": ["warn"],
|
||||
"new-cap": ["warn"],
|
||||
"key-spacing": ["warn"],
|
||||
"prefer-const": ["warn"],
|
||||
"arrow-parens": "off",
|
||||
|
||||
// crashes currently: https://github.com/eslint/eslint/issues/6274
|
||||
"generator-star-spacing": "off",
|
||||
|
|
|
@ -4081,7 +4081,7 @@ Changes in [0.6.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v
|
|||
[\#297](https://github.com/matrix-org/matrix-react-sdk/pull/297)
|
||||
* multiple URL preview support
|
||||
[\#290](https://github.com/matrix-org/matrix-react-sdk/pull/290)
|
||||
* Add a fallback home server to log into
|
||||
* Add a fallback homeserver to log into
|
||||
[\#293](https://github.com/matrix-org/matrix-react-sdk/pull/293)
|
||||
* Hopefully fix memory leak with velocity
|
||||
[\#291](https://github.com/matrix-org/matrix-react-sdk/pull/291)
|
||||
|
|
|
@ -270,6 +270,20 @@ textarea {
|
|||
opacity: 0.7;
|
||||
}
|
||||
|
||||
// TODO: Review mx_GeneralButton usage to see if it can use a different class
|
||||
// These classes were brought in from the old UserSettings and are included here to avoid
|
||||
// breaking the app.
|
||||
// Ref: https://github.com/vector-im/riot-web/issues/8420
|
||||
.mx_GeneralButton {
|
||||
@mixin mx_DialogButton;
|
||||
display: inline;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.mx_GeneralButton:hover {
|
||||
@mixin mx_DialogButton_hover;
|
||||
}
|
||||
|
||||
.mx_linkButton {
|
||||
cursor: pointer;
|
||||
color: $accent-color;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
@import "./structures/_CompatibilityPage.scss";
|
||||
@import "./structures/_ContextualMenu.scss";
|
||||
@import "./structures/_CreateRoom.scss";
|
||||
@import "./structures/_CustomRoomTagPanel.scss";
|
||||
@import "./structures/_FilePanel.scss";
|
||||
@import "./structures/_GroupView.scss";
|
||||
@import "./structures/_HomePage.scss";
|
||||
|
@ -19,9 +20,9 @@
|
|||
@import "./structures/_SearchBox.scss";
|
||||
@import "./structures/_TabbedView.scss";
|
||||
@import "./structures/_TagPanel.scss";
|
||||
@import "./structures/_TagPanelButtons.scss";
|
||||
@import "./structures/_TopLeftMenuButton.scss";
|
||||
@import "./structures/_UploadBar.scss";
|
||||
@import "./structures/_UserSettings.scss";
|
||||
@import "./structures/_ViewSource.scss";
|
||||
@import "./structures/auth/_Login.scss";
|
||||
@import "./views/auth/_AuthBody.scss";
|
||||
|
@ -35,6 +36,7 @@
|
|||
@import "./views/auth/_LanguageSelector.scss";
|
||||
@import "./views/auth/_ServerConfig.scss";
|
||||
@import "./views/auth/_ServerTypeSelector.scss";
|
||||
@import "./views/auth/_Welcome.scss";
|
||||
@import "./views/avatars/_BaseAvatar.scss";
|
||||
@import "./views/avatars/_MemberStatusMessageAvatar.scss";
|
||||
@import "./views/context_menus/_MessageContextMenu.scss";
|
||||
|
@ -42,6 +44,7 @@
|
|||
@import "./views/context_menus/_StatusMessageContextMenu.scss";
|
||||
@import "./views/context_menus/_TagTileContextMenu.scss";
|
||||
@import "./views/context_menus/_TopLeftMenu.scss";
|
||||
@import "./views/dialogs/_Analytics.scss";
|
||||
@import "./views/dialogs/_BugReportDialog.scss";
|
||||
@import "./views/dialogs/_ChangelogDialog.scss";
|
||||
@import "./views/dialogs/_ChatCreateOrReuseChatDialog.scss";
|
||||
|
@ -50,6 +53,7 @@
|
|||
@import "./views/dialogs/_CreateGroupDialog.scss";
|
||||
@import "./views/dialogs/_CreateRoomDialog.scss";
|
||||
@import "./views/dialogs/_DeactivateAccountDialog.scss";
|
||||
@import "./views/dialogs/_DeviceVerifyDialog.scss";
|
||||
@import "./views/dialogs/_DevtoolsDialog.scss";
|
||||
@import "./views/dialogs/_EncryptedEventDialog.scss";
|
||||
@import "./views/dialogs/_GroupAddressPicker.scss";
|
||||
|
@ -74,9 +78,9 @@
|
|||
@import "./views/elements/_Dropdown.scss";
|
||||
@import "./views/elements/_EditableItemList.scss";
|
||||
@import "./views/elements/_Field.scss";
|
||||
@import "./views/elements/_HexVerify.scss";
|
||||
@import "./views/elements/_ImageView.scss";
|
||||
@import "./views/elements/_InlineSpinner.scss";
|
||||
@import "./views/elements/_ManageIntegsButton.scss";
|
||||
@import "./views/elements/_MemberEventListSummary.scss";
|
||||
@import "./views/elements/_ProgressBar.scss";
|
||||
@import "./views/elements/_ReplyThread.scss";
|
||||
|
@ -104,9 +108,12 @@
|
|||
@import "./views/messages/_SenderProfile.scss";
|
||||
@import "./views/messages/_TextualEvent.scss";
|
||||
@import "./views/messages/_UnknownBody.scss";
|
||||
@import "./views/room_settings/_AliasSettings.scss";
|
||||
@import "./views/room_settings/_ColorSettings.scss";
|
||||
@import "./views/rooms/_AppsDrawer.scss";
|
||||
@import "./views/rooms/_Autocomplete.scss";
|
||||
@import "./views/rooms/_AuxPanel.scss";
|
||||
@import "./views/rooms/_E2EIcon.scss";
|
||||
@import "./views/rooms/_EntityTile.scss";
|
||||
@import "./views/rooms/_EventTile.scss";
|
||||
@import "./views/rooms/_JumpToBottomButton.scss";
|
||||
|
@ -124,7 +131,6 @@
|
|||
@import "./views/rooms/_RoomList.scss";
|
||||
@import "./views/rooms/_RoomPreviewBar.scss";
|
||||
@import "./views/rooms/_RoomRecoveryReminder.scss";
|
||||
@import "./views/rooms/_RoomSettings.scss";
|
||||
@import "./views/rooms/_RoomTile.scss";
|
||||
@import "./views/rooms/_RoomTooltip.scss";
|
||||
@import "./views/rooms/_RoomUpgradeWarningBar.scss";
|
||||
|
@ -150,6 +156,7 @@
|
|||
@import "./views/settings/tabs/_SecuritySettingsTab.scss";
|
||||
@import "./views/settings/tabs/_SettingsTab.scss";
|
||||
@import "./views/settings/tabs/_VoiceSettingsTab.scss";
|
||||
@import "./views/verification/_VerificationShowSas.scss";
|
||||
@import "./views/voip/_CallView.scss";
|
||||
@import "./views/voip/_IncomingCallbox.scss";
|
||||
@import "./views/voip/_VideoView.scss";
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
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_LeftPanel_tagPanelContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mx_CustomRoomTagPanel {
|
||||
background-color: $tagpanel-bg-color;
|
||||
max-height: 40vh;
|
||||
}
|
||||
|
||||
.mx_CustomRoomTagPanel_scroller {
|
||||
max-height: inherit;
|
||||
}
|
||||
|
||||
.mx_CustomRoomTagPanel .mx_AccessibleButton {
|
||||
margin: 9px auto;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.mx_CustomRoomTagPanel .mx_BaseAvatar_image {
|
||||
box-sizing: border-box;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.mx_CustomRoomTagPanel .mx_AccessibleButton.CustomRoomTagPanel_tileSelected .mx_BaseAvatar_image {
|
||||
border: 3px solid $warning-color;
|
||||
border-radius: 40px;
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
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.
|
||||
|
@ -22,27 +23,3 @@ limitations under the License.
|
|||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.mx_HomePage iframe {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
.mx_HomePage_body {
|
||||
// margin-left: 63px;
|
||||
}
|
||||
|
||||
.mx_HomePage_guest_warning {
|
||||
display: flex;
|
||||
background-color: $secondary-accent-color;
|
||||
border: 1px solid $accent-color;
|
||||
margin: 20px;
|
||||
padding: 20px 40px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.mx_HomePage_guest_warning img {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
|
|
@ -33,6 +33,11 @@ limitations under the License.
|
|||
flex: 0 0 140px;
|
||||
}
|
||||
|
||||
.mx_LeftPanel_tagPanelContainer {
|
||||
flex: 0 0 70px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mx_LeftPanel_hideButton {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
|
|
|
@ -121,7 +121,7 @@ limitations under the License.
|
|||
|
||||
.mx_RoomStatusBar_connectionLostBar img {
|
||||
padding-left: 10px;
|
||||
padding-right: 22px;
|
||||
padding-right: 10px;
|
||||
vertical-align: middle;
|
||||
float: left;
|
||||
}
|
||||
|
|
|
@ -75,6 +75,7 @@ limitations under the License.
|
|||
font-size: 12px;
|
||||
padding: 0 5px;
|
||||
background-color: $roomtile-name-color;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_RoomSubList_addRoom, .mx_RoomSubList_badge {
|
||||
|
|
|
@ -17,17 +17,21 @@ limitations under the License.
|
|||
|
||||
.mx_TabbedView {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding: 0 0 0 58px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.mx_TabbedView_tabLabels {
|
||||
width: 150px;
|
||||
max-width: 150px;
|
||||
height: 100%;
|
||||
color: $tab-label-fg-color;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.mx_TabbedView_tabLabel {
|
||||
|
@ -47,11 +51,6 @@ limitations under the License.
|
|||
color: $tab-label-active-fg-color;
|
||||
}
|
||||
|
||||
// TODO: Remove temporary hack alongside "visit old settings" tab
|
||||
.mx_TabbedView_tabLabel_TEMP_HACK {
|
||||
background-color: orange;
|
||||
}
|
||||
|
||||
.mx_TabbedView_maskedIcon {;
|
||||
margin-left: 6px;
|
||||
margin-right: 9px;
|
||||
|
@ -82,13 +81,15 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_TabbedView_tabPanel {
|
||||
width: calc(100% - 320px);
|
||||
display: inline-block;
|
||||
margin-left: 70px;
|
||||
margin-left: 220px; // 150px sidebar + 70px padding
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0; // firefox
|
||||
}
|
||||
|
||||
.mx_TabbedView_tabPanelContent {
|
||||
flex-grow: 1;
|
||||
min-width: 560px;
|
||||
overflow: auto;
|
||||
min-height: 0; // firefox
|
||||
}
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
.mx_TagPanel {
|
||||
flex: 0 0 70px;
|
||||
flex: 1;
|
||||
background-color: $tagpanel-bg-color;
|
||||
cursor: pointer;
|
||||
|
||||
|
@ -68,10 +68,13 @@ limitations under the License.
|
|||
|
||||
height: 100%;
|
||||
}
|
||||
.mx_TagPanel .mx_TagPanel_tagTileContainer > div {
|
||||
height: 40px;
|
||||
padding: 5px 0 4px 0;
|
||||
}
|
||||
|
||||
.mx_TagPanel .mx_TagTile {
|
||||
padding-top: 9px;
|
||||
padding-bottom: 9px;
|
||||
margin: 9px 0;
|
||||
// opacity: 0.5;
|
||||
position: relative;
|
||||
}
|
||||
|
@ -81,13 +84,7 @@ limitations under the License.
|
|||
// opacity: 1;
|
||||
}
|
||||
|
||||
.mx_TagPanel .mx_TagTile.mx_TagTile_selected {
|
||||
/* To offset border of mx_TagTile_avatar */
|
||||
padding: 3px 0px;
|
||||
}
|
||||
|
||||
.mx_TagPanel .mx_TagTile.mx_TagTile_selected .mx_TagTile_avatar .mx_BaseAvatar {
|
||||
border: 3px solid $accent-color;
|
||||
background-color: $accent-color;
|
||||
border-radius: 40px;
|
||||
|
||||
|
@ -97,6 +94,13 @@ limitations under the License.
|
|||
width: 40px;
|
||||
}
|
||||
|
||||
.mx_TagPanel .mx_TagTile_selected .mx_BaseAvatar_image {
|
||||
border: 3px solid $accent-color;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.mx_TagPanel .mx_TagTile.mx_AccessibleButton:focus {
|
||||
filter: none;
|
||||
}
|
||||
|
@ -112,7 +116,7 @@ limitations under the License.
|
|||
height: 15px;
|
||||
position: absolute;
|
||||
right: -5px;
|
||||
top: 1px;
|
||||
top: -8px;
|
||||
border-radius: 8px;
|
||||
background-color: $neutral-badge-color;
|
||||
color: #ffffff;
|
||||
|
@ -124,39 +128,22 @@ limitations under the License.
|
|||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.mx_TagPanel_groupsButton {
|
||||
flex: 0;
|
||||
margin: 17px 0 3px 0;
|
||||
}
|
||||
|
||||
.mx_TagPanel_groupsButton > .mx_GroupsButton:before {
|
||||
mask: url('$(res)/img/feather-icons/users.svg');
|
||||
mask-position: center 11px;
|
||||
}
|
||||
|
||||
.mx_TagPanel_groupsButton > .mx_TagPanel_report:before {
|
||||
mask: url('$(res)/img/feather-icons/life-buoy.svg');
|
||||
mask-position: center 9px;
|
||||
}
|
||||
|
||||
.mx_TagPanel_groupsButton > .mx_AccessibleButton {
|
||||
margin-bottom: 12px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 20px;
|
||||
background-color: $roomheader-addroom-color;
|
||||
.mx_TagTile_avatar {
|
||||
position: relative;
|
||||
/* overwrite mx_RoleButton inline-block */
|
||||
display: block !important;
|
||||
|
||||
&:before {
|
||||
background-color: $tagpanel-bg-color;
|
||||
mask-repeat: no-repeat;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_TagTile_badge {
|
||||
position: absolute;
|
||||
right: -4px;
|
||||
top: -2px;
|
||||
border-radius: 8px;
|
||||
color: $accent-fg-color;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
padding: 0 5px;
|
||||
background-color: $roomtile-name-color;
|
||||
}
|
||||
|
||||
.mx_TagTile_badgeHighlight {
|
||||
background-color: $warning-color;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
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_TagPanelButtons {
|
||||
background-color: $tagpanel-bg-color;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 17px 0 3px 0;
|
||||
}
|
||||
|
||||
.mx_TagPanelButtons > .mx_GroupsButton:before {
|
||||
mask: url('$(res)/img/feather-icons/users.svg');
|
||||
mask-position: center 11px;
|
||||
}
|
||||
|
||||
.mx_TagPanelButtons > .mx_TagPanelButtons_report:before {
|
||||
mask: url('$(res)/img/feather-icons/life-buoy.svg');
|
||||
mask-position: center 9px;
|
||||
}
|
||||
|
||||
.mx_TagPanelButtons > .mx_AccessibleButton {
|
||||
margin-bottom: 12px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 20px;
|
||||
background-color: $roomheader-addroom-color;
|
||||
position: relative;
|
||||
/* overwrite mx_RoleButton inline-block */
|
||||
display: block !important;
|
||||
|
||||
&:before {
|
||||
background-color: $tagpanel-bg-color;
|
||||
mask-repeat: no-repeat;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
|
@ -1,269 +0,0 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations 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_UserSettings {
|
||||
max-width: 960px;
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mx_UserSettings .mx_RoomHeader {
|
||||
order: 1;
|
||||
|
||||
flex: 0 0 70px;
|
||||
}
|
||||
|
||||
.mx_UserSettings_body {
|
||||
order: 2;
|
||||
|
||||
flex: 1 1 0;
|
||||
|
||||
margin-top: -20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.mx_UserSettings h3 {
|
||||
clear: both;
|
||||
margin-left: 63px;
|
||||
text-transform: uppercase;
|
||||
color: $h3-color;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
margin-top: 26px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.mx_UserSettings_section h3 {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.mx_UserSettings_spinner {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 12px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.mx_UserSettings_button {
|
||||
@mixin mx_DialogButton;
|
||||
display: inline;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.mx_UserSettings_button:hover {
|
||||
@mixin mx_DialogButton_hover;
|
||||
}
|
||||
|
||||
.mx_UserSettings_button.danger {
|
||||
background-color: $warning-color;
|
||||
}
|
||||
|
||||
.mx_UserSettings_section {
|
||||
margin-left: 63px;
|
||||
margin-top: 28px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.mx_UserSettings_cryptoSection ul {
|
||||
display: table;
|
||||
}
|
||||
.mx_UserSettings_cryptoSection li {
|
||||
display: table-row;
|
||||
}
|
||||
.mx_UserSettings_cryptoSection label,
|
||||
.mx_UserSettings_cryptoSection span {
|
||||
display: table-cell;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.mx_UserSettings_passwordWarning {
|
||||
/* To move the "Sign out" button out of the way */
|
||||
clear: both;
|
||||
color: $warning-color;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.mx_UserSettings_importExportButtons {
|
||||
padding-top: 10px;
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
.mx_UserSettings_importExportButtons .mx_UserSettings_button {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.mx_UserSettings_toggle input {
|
||||
width: 16px;
|
||||
margin-right: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mx_UserSettings_toggle label {
|
||||
padding-bottom: 21px;
|
||||
}
|
||||
|
||||
.mx_UserSettings_accountTable
|
||||
.mx_UserSettings_notifTable
|
||||
{
|
||||
display: table;
|
||||
}
|
||||
|
||||
.mx_UserSettings_notifTable .mx_Spinner {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.mx_UserSettings_language {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.mx_UserSettings_webRtcDevices_dropdown {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.mx_UserSettings_profileTable
|
||||
{
|
||||
display: table;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.mx_UserSettings_profileTableRow
|
||||
{
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.mx_UserSettings_profileLabelCell
|
||||
{
|
||||
padding-bottom: 21px;
|
||||
display: table-cell;
|
||||
font-weight: bold;
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
.mx_UserSettings_profileInputCell {
|
||||
display: table-cell;
|
||||
padding-bottom: 21px;
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.mx_UserSettings_profileInputCell input,
|
||||
.mx_UserSettings_profileInputCell .mx_EditableText
|
||||
{
|
||||
display: inline-block;
|
||||
border: 0px;
|
||||
border-bottom: 1px solid $input-underline-color;
|
||||
padding: 0px;
|
||||
width: 240px;
|
||||
color: $input-fg-color;
|
||||
font-family: $font-family;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.mx_UserSettings_threepidButton {
|
||||
display: table-cell;
|
||||
padding-left: 0.5em;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_UserSettings_phoneSection {
|
||||
display:table;
|
||||
}
|
||||
|
||||
.mx_UserSettings_phoneCountry {
|
||||
width: 70px;
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
input.mx_UserSettings_phoneNumberField {
|
||||
margin-left: 3px;
|
||||
width: 172px;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.mx_UserSettings_changePasswordButton {
|
||||
float: right;
|
||||
margin-right: 32px;
|
||||
margin-left: 32px;
|
||||
}
|
||||
|
||||
.mx_UserSettings_logout {
|
||||
float: right;
|
||||
margin-right: 32px;
|
||||
margin-left: 32px;
|
||||
}
|
||||
|
||||
.mx_UserSettings_avatarPicker {
|
||||
margin-left: 32px;
|
||||
margin-right: 32px;
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_UserSettings_avatarPicker_img .mx_BaseAvatar_image {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.mx_UserSettings_avatarPicker_edit {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.mx_UserSettings_avatarPicker_edit img {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_UserSettings_avatarPicker_edit > input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mx_UserSettings_avatarPicker_imgContainer {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.mx_UserSettings_avatarPicker_remove {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
margin-right: -15px;
|
||||
}
|
||||
|
||||
.mx_UserSettings_advanced_spoiler,
|
||||
.mx_UserSettings_link {
|
||||
cursor: pointer;
|
||||
color: $accent-color;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.mx_UserSettings_analyticsModal table {
|
||||
margin: 10px 0px;
|
||||
}
|
||||
|
||||
|
||||
// Temp styles to keep the layout moderately usable. Not perfect, but better
|
||||
// than 30 options being impossible to understand.
|
||||
.mx_UserSettings .mx_SettingsFlag {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.mx_UserSettings .mx_SettingsFlag .mx_ToggleSwitch {
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
}
|
|
@ -19,7 +19,8 @@ limitations under the License.
|
|||
width: 100%;
|
||||
font-size: 14px;
|
||||
opacity: 0.72;
|
||||
margin: 20px 0;
|
||||
padding: 20px 0;
|
||||
background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.8));
|
||||
}
|
||||
|
||||
.mx_AuthFooter a:link,
|
||||
|
|
|
@ -18,6 +18,6 @@ limitations under the License.
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 206px;
|
||||
padding: 25px 50px;
|
||||
padding: 25px 40px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
.mx_AuthHeaderLogo {
|
||||
margin-top: 15px;
|
||||
flex: 1;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.mx_AuthHeaderLogo img {
|
||||
|
|
|
@ -26,8 +26,6 @@ limitations under the License.
|
|||
display: flex;
|
||||
margin: 100px auto auto;
|
||||
border-radius: 4px;
|
||||
// Not currently supported in Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1178765
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.33);
|
||||
background-color: $authpage-modal-bg-color;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
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_Welcome {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mx_Welcome .mx_AuthBody_language {
|
||||
width: 120px;
|
||||
margin-bottom: 10px;
|
||||
}
|
|
@ -27,6 +27,10 @@ limitations under the License.
|
|||
margin: 5px 0;
|
||||
padding: 0;
|
||||
|
||||
li.mx_TopLeftMenu_icon_home::after {
|
||||
mask-image: url('$(res)/img/feather-icons/home.svg');
|
||||
}
|
||||
|
||||
li.mx_TopLeftMenu_icon_settings::after {
|
||||
mask-image: url('$(res)/img/feather-icons/settings.svg');
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
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_AnalyticsModal table {
|
||||
margin: 10px 0px;
|
||||
}
|
|
@ -14,21 +14,16 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_HexVerify {
|
||||
text-align: center;
|
||||
.mx_DeviceVerifyDialog_cryptoSection ul {
|
||||
display: table;
|
||||
}
|
||||
|
||||
.mx_HexVerify_pair {
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
.mx_DeviceVerifyDialog_cryptoSection li {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.mx_HexVerify_pair_verified {
|
||||
color: $accent-color;
|
||||
}
|
||||
|
||||
.mx_HexVerify_pair:hover{
|
||||
color: $accent-color;
|
||||
.mx_DeviceVerifyDialog_cryptoSection label,
|
||||
.mx_DeviceVerifyDialog_cryptoSection span {
|
||||
display: table-cell;
|
||||
padding-right: 1em;
|
||||
}
|
|
@ -15,56 +15,33 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
.mx_SettingsDialog {
|
||||
.mx_Dialog {
|
||||
max-width: 784px; // 900px - 58px (left padding) - 58px (right padding)
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
border-radius: 4px;
|
||||
.mx_Dialog {
|
||||
max-width: 900px;
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
border-radius: 4px;
|
||||
padding-top: 0;
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
|
||||
.mx_TabbedView_tabLabels {
|
||||
// Force the sidebar to be always visible, letting the rest of the content scroll
|
||||
position: fixed;
|
||||
}
|
||||
.mx_TabbedView {
|
||||
top: 65px;
|
||||
}
|
||||
|
||||
.mx_TabbedView_tabPanel {
|
||||
max-width: 485px;
|
||||
margin-left: 206px; // 70px margin + 136px for the sidebar
|
||||
}
|
||||
.mx_TabbedView .mx_SettingsTab {
|
||||
box-sizing: border-box;
|
||||
min-width: 550px;
|
||||
padding-right: 130px;
|
||||
|
||||
.mx_SettingsDialog_header {
|
||||
font-size: 24px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
color: $dialog-title-fg-color;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 24px;
|
||||
padding: 0;
|
||||
}
|
||||
// Put some padding on the bottom to avoid the settings tab from
|
||||
// colliding harshly with the dialog when scrolled down.
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
|
||||
.mx_SettingsDialog_close {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 25px;
|
||||
.mx_Dialog_title {
|
||||
text-align: center;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_SettingsDialog_closeIcon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.mx_SettingsDialog_closeIcon:before {
|
||||
mask: url('$(res)/img/feather-icons/cancel.svg');
|
||||
background-color: $dialog-close-fg-color;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: 16px;
|
||||
mask-position: center;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,10 @@ limitations under the License.
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mx_UnknownDeviceDialog .mx_DeviceVerifyButtons {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.mx_UnknownDeviceDialog .mx_Dialog_content {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ limitations under the License.
|
|||
.mx_Dropdown_arrow {
|
||||
width: 10px;
|
||||
height: 6px;
|
||||
padding-right: 8px;
|
||||
padding-right: 9px;
|
||||
mask: url('$(res)/img/feather-icons/dropdown-arrow.svg');
|
||||
mask-repeat: no-repeat;
|
||||
background: $primary-fg-color;
|
||||
|
@ -54,6 +54,7 @@ limitations under the License.
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mx_Dropdown_option {
|
||||
|
|
|
@ -100,7 +100,8 @@ limitations under the License.
|
|||
top 0.25s ease-out 0s,
|
||||
background-color 0.25s ease-out 0s;
|
||||
font-size: 10px;
|
||||
top: -14px;
|
||||
top: -13px;
|
||||
padding: 0 2px;
|
||||
background-color: $field-focused-label-bg-color;
|
||||
}
|
||||
|
||||
|
@ -111,7 +112,11 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_Field select:disabled,
|
||||
.mx_Field input:disabled {
|
||||
.mx_Field select:disabled + label,
|
||||
.mx_Field input:disabled,
|
||||
.mx_Field input:disabled + label,
|
||||
.mx_Field textarea:disabled,
|
||||
.mx_Field textarea:disabled + label {
|
||||
background-color: $field-focused-label-bg-color;
|
||||
color: $greyed-fg-color;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
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_ManageIntegsButton_error {
|
||||
position: relative;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.mx_ManageIntegsButton_error img {
|
||||
position: absolute;
|
||||
right: -5px;
|
||||
top: -5px;
|
||||
}
|
||||
|
||||
.mx_ManageIntegsButton_error {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.mx_ManageIntegsButton_error .mx_ManageIntegsButton_errorPopup {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mx_ManageIntegsButton_error:hover .mx_ManageIntegsButton_errorPopup {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.mx_ManageIntegsButton_errorPopup {
|
||||
position: absolute;
|
||||
top: 110%;
|
||||
left: -275%;
|
||||
width: 550%;
|
||||
padding: 30%;
|
||||
font-size: 10pt;
|
||||
line-height: 1.5em;
|
||||
border-radius: 5px;
|
||||
background-color: $accent-color;
|
||||
color: $accent-fg-color;
|
||||
text-align: center;
|
||||
z-index: 1000;
|
||||
}
|
|
@ -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_AliasSettings_editable {
|
||||
border: 0px;
|
||||
border-bottom: 1px solid $strong-input-border-color;
|
||||
padding: 0px;
|
||||
min-width: 240px;
|
||||
}
|
||||
|
||||
.mx_AliasSettings_editable:focus {
|
||||
border-bottom: 1px solid $accent-color;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2018 New Vector
|
||||
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.
|
||||
|
@ -14,16 +14,26 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import SettingController from "./SettingController";
|
||||
import MatrixClientPeg from "../../MatrixClientPeg";
|
||||
import PlatformPeg from "../../PlatformPeg";
|
||||
|
||||
export default class LazyLoadingController extends SettingController {
|
||||
async onChange(level, roomId, newValue) {
|
||||
if (!PlatformPeg.get()) return;
|
||||
|
||||
MatrixClientPeg.get().stopClient();
|
||||
await MatrixClientPeg.get().store.deleteAllData();
|
||||
PlatformPeg.get().reload();
|
||||
}
|
||||
.mx_ColorSettings_roomColor {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 37px;
|
||||
height: 37px;
|
||||
border: 1px solid #979797;
|
||||
margin-right: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_ColorSettings_roomColor_selected {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 4px;
|
||||
cursor: default ! important;
|
||||
}
|
||||
|
||||
.mx_ColorSettings_roomColorPrimary {
|
||||
height: 10px;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
}
|
|
@ -56,9 +56,8 @@ limitations under the License.
|
|||
max-width: 960px;
|
||||
width: 50%;
|
||||
margin-right: 5px;
|
||||
border: 1px solid $primary-hairline-color;
|
||||
border-radius: 2px;
|
||||
background-color: $dialog-background-bg-color;
|
||||
border: 5px solid $widget-menu-bar-bg-color;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mx_AppTile:last-child {
|
||||
|
@ -71,8 +70,8 @@ limitations under the License.
|
|||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 1px solid $primary-hairline-color;
|
||||
border-radius: 2px;
|
||||
border: 5px solid $widget-menu-bar-bg-color;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mx_AppTile_mini {
|
||||
|
@ -93,9 +92,7 @@ limitations under the License.
|
|||
|
||||
.mx_AppTileMenuBar {
|
||||
margin: 0;
|
||||
padding: 2px 10px;
|
||||
border-bottom: 1px solid $primary-hairline-color;
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
background-color: $widget-menu-bar-bg-color;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -104,6 +101,10 @@ limitations under the License.
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_AppTileMenuBar_expanded {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.mx_AppTileMenuBarTitle {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -111,6 +112,10 @@ limitations under the License.
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
.mx_AppTileMenuBarTitle > :last-child {
|
||||
margin-left: 9px;
|
||||
}
|
||||
|
||||
.mx_AppTileMenuBarWidgets {
|
||||
float: right;
|
||||
display: flex;
|
||||
|
@ -118,6 +123,53 @@ limitations under the License.
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.mx_AppTileMenuBar_iconButton {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: 0 center;
|
||||
mask-size: auto 12px;
|
||||
background-color: $topleftmenu-color;
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_minimise {
|
||||
mask-image: url('$(res)/img/feather-icons/widget/minimise.svg');
|
||||
background-color: $accent-color;
|
||||
}
|
||||
|
||||
.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_maximise {
|
||||
mask-image: url('$(res)/img/feather-icons/widget/maximise.svg');
|
||||
background-color: $accent-color;
|
||||
}
|
||||
|
||||
.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_reload {
|
||||
mask-image: url('$(res)/img/feather-icons/widget/refresh.svg');
|
||||
mask-size: 100%;
|
||||
}
|
||||
|
||||
.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_popout {
|
||||
mask-image: url('$(res)/img/feather-icons/widget/external-link.svg');
|
||||
}
|
||||
|
||||
.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_snapshot {
|
||||
mask-image: url('$(res)/img/feather-icons/widget/camera.svg');
|
||||
}
|
||||
|
||||
.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_edit {
|
||||
mask-image: url('$(res)/img/feather-icons/widget/edit.svg');
|
||||
}
|
||||
|
||||
.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_delete {
|
||||
mask-image: url('$(res)/img/feather-icons/widget/bin.svg');
|
||||
background-color: $warning-color;
|
||||
}
|
||||
|
||||
.mx_AppTileMenuBar_iconButton.mx_AppTileMenuBar_iconButton_cancel {
|
||||
mask-image: url('$(res)/img/feather-icons/widget/x-circle.svg');
|
||||
}
|
||||
|
||||
/* delete ? */
|
||||
.mx_AppTileMenuBarWidget {
|
||||
cursor: pointer;
|
||||
width: 10px;
|
||||
|
@ -265,14 +317,11 @@ form.mx_Custom_Widget_Form div {
|
|||
}
|
||||
|
||||
.mx_AppPermissionButton {
|
||||
padding: 5px;
|
||||
border: none;
|
||||
padding: 5px 20px;
|
||||
border-radius: 5px;
|
||||
color: $warning-color;
|
||||
background-color: $primary-bg-color;
|
||||
}
|
||||
|
||||
.mx_AppPermissionButton:hover {
|
||||
background-color: $primary-fg-color;
|
||||
background-color: $button-bg-color;
|
||||
color: $button-fg-color;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
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_E2EIcon {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center 0;
|
||||
margin: 0 9px;
|
||||
}
|
||||
|
||||
.mx_E2EIcon_verified {
|
||||
mask-image: url('$(res)/img/feather-icons/e2e/lock-verified.svg');
|
||||
background-color: $accent-color;
|
||||
}
|
||||
|
||||
.mx_E2EIcon_warning {
|
||||
mask-image: url('$(res)/img/feather-icons/e2e/lock-warning.svg');
|
||||
background-color: $warning-color;
|
||||
}
|
|
@ -281,9 +281,24 @@ limitations under the License.
|
|||
.mx_EventTile_e2eIcon {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
top: 8px;
|
||||
left: 46px;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
cursor: pointer;
|
||||
mask-size: 14px;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: 0;
|
||||
}
|
||||
|
||||
.mx_EventTile_e2eIcon_undecryptable, .mx_EventTile_e2eIcon_unverified {
|
||||
mask-image: url('$(res)/img/feather-icons/e2e/warning.svg');
|
||||
background-color: $warning-color;
|
||||
}
|
||||
|
||||
.mx_EventTile_e2eIcon_unencrypted {
|
||||
mask-image: url('$(res)/img/feather-icons/e2e/warning.svg');
|
||||
background-color: $composer-e2e-icon-color;
|
||||
}
|
||||
|
||||
.mx_EventTile_e2eIcon_hidden {
|
||||
|
|
|
@ -20,6 +20,25 @@ limitations under the License.
|
|||
align-items: start;
|
||||
}
|
||||
|
||||
.mx_MemberDeviceInfo_icon {
|
||||
margin-top: 4px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
mask-repeat: no-repeat;
|
||||
}
|
||||
.mx_MemberDeviceInfo_icon_blacklisted {
|
||||
mask-image: url('$(res)/img/feather-icons/e2e/blacklisted.svg');
|
||||
background-color: $warning-color;
|
||||
}
|
||||
.mx_MemberDeviceInfo_icon_verified {
|
||||
mask-image: url('$(res)/img/feather-icons/e2e/verified.svg');
|
||||
background-color: $accent-color;
|
||||
}
|
||||
.mx_MemberDeviceInfo_icon_unverified {
|
||||
mask-image: url('$(res)/img/feather-icons/e2e/warning.svg');
|
||||
background-color: $warning-color;
|
||||
}
|
||||
|
||||
.mx_MemberDeviceInfo > .mx_DeviceVerifyButtons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -26,6 +26,10 @@ limitations under the License.
|
|||
display: flex;
|
||||
}
|
||||
|
||||
.mx_MemberInfo_name > .mx_E2EIcon {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.mx_MemberInfo_cancel {
|
||||
height: 16px;
|
||||
padding: 10px 15px;
|
||||
|
|
|
@ -23,6 +23,10 @@ limitations under the License.
|
|||
padding-left: 84px;
|
||||
}
|
||||
|
||||
.mx_MessageComposer_wrapper.mx_MessageComposer_hasE2EIcon {
|
||||
padding-left: 109px;
|
||||
}
|
||||
|
||||
.mx_MessageComposer_replaced_wrapper {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
@ -71,9 +75,10 @@ limitations under the License.
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.mx_MessageComposer_e2eIcon {
|
||||
.mx_MessageComposer_e2eIcon.mx_E2EIcon {
|
||||
position: absolute;
|
||||
left: 60px;
|
||||
background-color: $composer-e2e-icon-color;
|
||||
}
|
||||
|
||||
.mx_MessageComposer_noperm_error {
|
||||
|
|
|
@ -38,7 +38,6 @@ limitations under the License.
|
|||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.mx_RoomRecoveryReminder_button.mx_RoomRecoveryReminder_secondary {
|
||||
@mixin mx_DialogButton_secondary;
|
||||
background-color: transparent;
|
||||
.mx_RoomRecoveryReminder_secondary {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
|
|
@ -1,260 +0,0 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations 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_RoomSettings {
|
||||
margin: 40px;
|
||||
}
|
||||
|
||||
.mx_RoomSettings_upgradeButton,
|
||||
.mx_RoomSettings_leaveButton,
|
||||
.mx_RoomSettings_unbanButton {
|
||||
@mixin mx_DialogButton;
|
||||
position: relative;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.mx_RoomSettings_devtoolsButton {
|
||||
@mixin mx_DialogButton;
|
||||
position: relative;
|
||||
padding: 4px 1.5em;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.mx_RoomSettings_upgradeButton,
|
||||
.mx_RoomSettings_leaveButton:hover,
|
||||
.mx_RoomSettings_unbanButton:hover {
|
||||
@mixin mx_DialogButton_hover;
|
||||
}
|
||||
|
||||
.mx_RoomSettings_upgradeButton.danger {
|
||||
@mixin mx_DialogButton_danger;
|
||||
}
|
||||
|
||||
.mx_RoomSettings_integrationsButton_error {
|
||||
position: relative;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.mx_RoomSettings_integrationsButton_error img {
|
||||
position: absolute;
|
||||
right: -5px;
|
||||
top: -5px;
|
||||
}
|
||||
.mx_RoomSettings_leaveButton,
|
||||
.mx_RoomSettings_integrationsButton_error {
|
||||
float: right;
|
||||
}
|
||||
.mx_RoomSettings_integrationsButton_error .mx_RoomSettings_integrationsButton_errorPopup {
|
||||
display: none;
|
||||
}
|
||||
.mx_RoomSettings_integrationsButton_error:hover .mx_RoomSettings_integrationsButton_errorPopup {
|
||||
display: inline;
|
||||
}
|
||||
.mx_RoomSettings_integrationsButton_errorPopup {
|
||||
position: absolute;
|
||||
top: 110%;
|
||||
left: -275%;
|
||||
width: 550%;
|
||||
padding: 30%;
|
||||
font-size: 10pt;
|
||||
line-height: 1.5em;
|
||||
border-radius: 5px;
|
||||
background-color: $accent-color;
|
||||
color: $accent-fg-color;
|
||||
text-align: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
.mx_RoomSettings_unbanButton {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.mx_RoomSettings_e2eIcon {
|
||||
padding-left: 4px;
|
||||
padding-right: 7px;
|
||||
}
|
||||
|
||||
.mx_RoomSettings_leaveButton {
|
||||
margin-right: 32px;
|
||||
}
|
||||
|
||||
.mx_RoomSettings_powerLevels {
|
||||
display: table;
|
||||
}
|
||||
|
||||
.mx_RoomSettings_powerLevel {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.mx_RoomSettings_powerLevelKey,
|
||||
.mx_RoomSettings_powerLevel .mx_PowerSelector {
|
||||
display: table-cell;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.mx_RoomSettings_powerLevelKey {
|
||||
text-align: right;
|
||||
padding-right: 0.3em;
|
||||
}
|
||||
|
||||
.mx_RoomSettings h3 {
|
||||
text-transform: uppercase;
|
||||
color: $h3-color;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
margin-top: 36px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.mx_RoomSettings .mx_RoomSettings_toggles label {
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mx_RoomSettings .mx_RoomSettings_toggles input[type="checkbox"],
|
||||
.mx_RoomSettings .mx_RoomSettings_toggles input[type="radio"] {
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
.mx_RoomSettings .mx_RoomSettings_tags input[type="checkbox"] {
|
||||
margin-left: 1em;
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
.mx_RoomSettings .mx_RoomSettings_tags {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mx_RoomSettings .mx_RoomSettings_roomColor {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 37px;
|
||||
height: 37px;
|
||||
border: 1px solid #979797;
|
||||
margin-right: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_RoomSettings .mx_RoomSettings_roomColor_selected {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 4px;
|
||||
cursor: default ! important;
|
||||
}
|
||||
|
||||
.mx_RoomSettings .mx_RoomSettings_roomColorPrimary {
|
||||
height: 10px;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mx_RoomSettings .mx_RoomSettings_aliasLabel {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mx_RoomSettings .mx_RoomSettings_aliasesTable {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 0px;
|
||||
margin-left: 56px;
|
||||
display: table;
|
||||
}
|
||||
|
||||
.mx_RoomSettings .mx_RoomSettings_aliasesTableRow {
|
||||
display: table-row;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.mx_RoomSettings .mx_RoomSettings_alias {
|
||||
max-width: 400px;
|
||||
margin-bottom: 16px;
|
||||
/*
|
||||
commented out so margin applies
|
||||
display: table-cell; */
|
||||
}
|
||||
|
||||
.mx_RoomSettings .mx_RoomSettings_addAlias,
|
||||
.mx_RoomSettings .mx_RoomSettings_deleteAlias {
|
||||
display: table-cell;
|
||||
padding-left: 0.5em;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_RoomSettings .mx_RoomSettings_addAlias img,
|
||||
.mx_RoomSettings .mx_RoomSettings_deleteAlias img {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.mx_RoomSettings .mx_RoomSettings_aliasesTableRow:hover .mx_RoomSettings_addAlias img,
|
||||
.mx_RoomSettings .mx_RoomSettings_aliasesTableRow:hover .mx_RoomSettings_deleteAlias img {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.mx_RoomSettings_warning {
|
||||
color: $warning-color;
|
||||
font-weight: bold;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mx_RoomSettings_editable {
|
||||
border: 0px;
|
||||
border-bottom: 1px solid $strong-input-border-color;
|
||||
padding: 0px;
|
||||
min-width: 240px;
|
||||
}
|
||||
|
||||
.mx_RoomSettings_editable:focus {
|
||||
border-bottom: 1px solid $accent-color;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.mx_RoomSettings_deleteAlias,
|
||||
.mx_RoomSettings_addAlias {
|
||||
display: table-cell;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.mx_RoomSettings_deleteAlias:hover,
|
||||
.mx_RoomSettings_addAlias:hover {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.mx_RoomSettings_aliasPlaceholder {
|
||||
color: $settings-grey-fg-color;
|
||||
}
|
||||
|
||||
.mx_RoomSettings_buttons {
|
||||
text-align: right;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.mx_RoomSettings_button {
|
||||
display: inline;
|
||||
border: 0px;
|
||||
height: 36px;
|
||||
border-radius: 36px;
|
||||
font-weight: 400;
|
||||
font-size: 15px;
|
||||
color: $accent-fg-color;
|
||||
background-color: $accent-color;
|
||||
width: auto;
|
||||
margin: auto;
|
||||
padding: 6px;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
|
@ -59,10 +59,15 @@ limitations under the License.
|
|||
color: $accent-color;
|
||||
}
|
||||
|
||||
.mx_UserSettings_devicesTable td {
|
||||
.mx_UserNotifSettings_devicesTable td {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
.mx_UserSettings_devicesTable_nodevices {
|
||||
font-style: italic;
|
||||
|
||||
.mx_UserNotifSettings_notifTable {
|
||||
display: table;
|
||||
}
|
||||
|
||||
.mx_UserNotifSettings_notifTable .mx_Spinner {
|
||||
position: absolute;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
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_VerificationShowSas_decimalSas {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
}
|
||||
|
||||
.mx_VerificationShowSas_decimalSas span {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.mx_VerificationShowSas_emojiSas {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mx_VerificationShowSas_emojiSas_block {
|
||||
display: inline-block;
|
||||
margin-left: 15px;
|
||||
margin-right: 15px;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mx_VerificationShowSas_emojiSas_emoji {
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.mx_VerificationShowSas_emojiSas_label {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
After Width: | Height: | Size: 169 B |
After Width: | Height: | Size: 171 B |
Before Width: | Height: | Size: 146 B |
Before Width: | Height: | Size: 966 B |
Before Width: | Height: | Size: 146 B |
After Width: | Height: | Size: 170 B |
Before Width: | Height: | Size: 146 B |
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12" height="12" viewBox="0 0 12 12">
|
||||
<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"/>
|
||||
</svg>
|
After Width: | Height: | Size: 442 B |
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="25" viewBox="0 0 22 25">
|
||||
<g fill="none" fill-rule="evenodd" stroke="#7AC9A1" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke-width="2" d="M8.23 21.01l-5.233-.007a1.995 1.995 0 0 1-1.997-2V11a2 2 0 0 1 2-2h14c1.259 0 2 .939 2 1M5 9V6a5 5 0 1 1 10 0v3"/>
|
||||
<path fill="#7AC9A1" d="M15.5 24s5.5-2.4 5.5-6v-4.2L15.5 12 10 13.8V18c0 3.6 5.5 6 5.5 6z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 454 B |
|
@ -0,0 +1,9 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="23" height="25" viewBox="0 0 23 25">
|
||||
<defs>
|
||||
<path id="a" d="M15 23a6 6 0 1 0 0-12 6 6 0 0 0 0 12zm0-10.5a1.5 1.5 0 0 1 1.5 1.5v3a1.5 1.5 0 0 1-3 0v-3a1.5 1.5 0 0 1 1.5-1.5zm0 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z"/>
|
||||
</defs>
|
||||
<g fill="none" fill-rule="evenodd" stroke="#F56679" stroke-linecap="round" stroke-linejoin="round" transform="translate(1 1)">
|
||||
<path stroke-width="2" d="M7.23 20.01l-5.233-.007a2 2 0 0 1-1.997-2V10a2 2 0 0 1 2-2h14c1.259 0 2 .939 2 1M4 8V5a5 5 0 1 1 10 0v3"/>
|
||||
<use fill="#F56679" xlink:href="#a"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 665 B |
|
@ -0,0 +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"/>
|
||||
</svg>
|
After Width: | Height: | Size: 278 B |
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12" height="12" viewBox="0 0 12 12">
|
||||
<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"/>
|
||||
</svg>
|
After Width: | Height: | Size: 500 B |
|
@ -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="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
|
After Width: | Height: | Size: 332 B |
|
@ -0,0 +1,65 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="11.014242"
|
||||
height="12"
|
||||
viewBox="0 0 11.014242 12"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="bin.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="750"
|
||||
inkscape:window-height="480"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
fit-margin-top="0.5"
|
||||
fit-margin-left="0.5"
|
||||
fit-margin-bottom="0.5"
|
||||
fit-margin-right="0.5"
|
||||
inkscape:zoom="19.666667"
|
||||
inkscape:cx="5.5071212"
|
||||
inkscape:cy="6"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg6" />
|
||||
<g
|
||||
id="g4"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
|
||||
transform="translate(1.0071212)">
|
||||
<path
|
||||
d="M 0,3 H 9 M 8,3 v 7 A 1,1 0 0 1 7,11 H 2 A 1,1 0 0 1 1,10 V 3 M 2.5,3 V 2 a 1,1 0 0 1 1,-1 h 2 a 1,1 0 0 1 1,1 v 1 m -3,2.5 v 3 m 2,-3 v 3"
|
||||
id="path2"
|
||||
style="stroke:#000000;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="11" viewBox="0 0 13 11">
|
||||
<g fill="none" fill-rule="evenodd" stroke="#212121" stroke-linecap="round" stroke-linejoin="round" transform="translate(1 1)">
|
||||
<path d="M11 8a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V2.5a1 1 0 0 1 1-1h2L4 0h3l1 1.5h2a1 1 0 0 1 1 1V8z"/>
|
||||
<circle cx="5.5" cy="5" r="2"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 378 B |
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="11" viewBox="0 0 12 11">
|
||||
<g fill="none" fill-rule="evenodd" stroke="#212121" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M10 6.33V9a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h2.67"/>
|
||||
<path d="M9 0l2 2-5 5H4V5z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 324 B |
|
@ -0,0 +1,5 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 11 11">
|
||||
<g fill="none" fill-rule="evenodd" stroke="#212121" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M8.5 6v3a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h3M7 1h3v3M4.5 6.5L10 1"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 304 B |
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="11"
|
||||
height="11"
|
||||
viewBox="0 0 11 11"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="maximise.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="750"
|
||||
inkscape:window-height="480"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
inkscape:zoom="21.454545"
|
||||
inkscape:cx="5.5"
|
||||
inkscape:cy="5.5"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg6" />
|
||||
<g
|
||||
fill="none"
|
||||
fill-rule="evenodd"
|
||||
stroke="#7AC9A1"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
id="g4"
|
||||
style="stroke:#000000;stroke-opacity:1">
|
||||
<path
|
||||
d="M7 1h3v3M4 10H1V7M10 1L6.5 4.5M1 10l3.5-3.5"
|
||||
id="path2"
|
||||
style="stroke:#000000;stroke-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -0,0 +1,65 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="10.2101"
|
||||
height="10.2101"
|
||||
viewBox="0 0 10.2101 10.210099"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="minimise.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
fit-margin-top="0.1"
|
||||
fit-margin-left="0.1"
|
||||
fit-margin-right="0.1"
|
||||
fit-margin-bottom="0.1"
|
||||
inkscape:zoom="26.222222"
|
||||
inkscape:cx="-1.5686788"
|
||||
inkscape:cy="5.0287789"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg6" />
|
||||
<g
|
||||
id="g4"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
|
||||
transform="translate(-0.39494988,0.60505012)">
|
||||
<path
|
||||
d="m 1.5,5.5 h 3 v 3 m 5,-5 h -3 v -3 m 0,3 L 10,0 M 1,9 4.5,5.5"
|
||||
id="path2"
|
||||
inkscape:connector-curvature="0"
|
||||
style="stroke:#000000;stroke-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="11" viewBox="0 0 13 11">
|
||||
<g fill="none" fill-rule="evenodd" stroke="#212121" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 1.5v3H9M1 9.5v-3h3"/>
|
||||
<path d="M2.255 4A4.5 4.5 0 0 1 9.68 2.32L12 4.5m-11 2l2.32 2.18A4.5 4.5 0 0 0 10.745 7"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 346 B |
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
|
||||
<g fill="none" fill-rule="evenodd" stroke="#212121" stroke-linecap="round" stroke-linejoin="round" transform="translate(1 1)">
|
||||
<circle cx="5" cy="5" r="5"/>
|
||||
<path d="M6.5 3.5l-3 3M3.5 3.5l3 3"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 315 B |
|
@ -142,6 +142,8 @@ $roomheader-addroom-color: #91A1C0;
|
|||
$roomtopic-color: #9fa9ba;
|
||||
$eventtile-meta-color: $roomtopic-color;
|
||||
|
||||
$composer-e2e-icon-color: #c9ced6;
|
||||
|
||||
// ********************
|
||||
|
||||
$roomtile-name-color: #61708b;
|
||||
|
@ -170,7 +172,7 @@ $panel-divider-color: #dee1f3;
|
|||
|
||||
// ********************
|
||||
|
||||
$widget-menu-bar-bg-color: $tertiary-accent-color;
|
||||
$widget-menu-bar-bg-color: $secondary-accent-color;
|
||||
|
||||
// ********************
|
||||
|
||||
|
|
|
@ -134,6 +134,9 @@ $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);
|
||||
|
|
|
@ -19,7 +19,7 @@ import MatrixClientPeg from './MatrixClientPeg';
|
|||
import { _t } from './languageHandler';
|
||||
|
||||
/**
|
||||
* Allows a user to add a third party identifier to their Home Server and,
|
||||
* Allows a user to add a third party identifier to their homeserver and,
|
||||
* optionally, the identity servers.
|
||||
*
|
||||
* This involves getting an email token from the identity server to "prove" that
|
||||
|
|
|
@ -267,7 +267,7 @@ class Analytics {
|
|||
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
||||
Modal.createTrackedDialog('Analytics Details', '', ErrorDialog, {
|
||||
title: _t('Analytics'),
|
||||
description: <div className="mx_UserSettings_analyticsModal">
|
||||
description: <div className="mx_AnalyticsModal">
|
||||
<div>
|
||||
{ _t('The information being sent to us to help make Riot.im better includes:') }
|
||||
</div>
|
||||
|
|
|
@ -51,7 +51,7 @@ module.exports = {
|
|||
},
|
||||
|
||||
defaultAvatarUrlForString: function(s) {
|
||||
const images = ['76cfa6', '50e2c2', 'f4c371'];
|
||||
const images = ['03b381', '368bd6', 'ac3ba8'];
|
||||
let total = 0;
|
||||
for (let i = 0; i < s.length; ++i) {
|
||||
total += s.charCodeAt(i);
|
||||
|
|
|
@ -359,7 +359,7 @@ class ContentMessages {
|
|||
if (!upload.canceled) {
|
||||
let desc = _t('The file \'%(fileName)s\' failed to upload', {fileName: upload.fileName}) + '.';
|
||||
if (err.http_status == 413) {
|
||||
desc = _t('The file \'%(fileName)s\' exceeds this home server\'s size limit for uploads', {fileName: upload.fileName});
|
||||
desc = _t('The file \'%(fileName)s\' exceeds this homeserver\'s size limit for uploads', {fileName: upload.fileName});
|
||||
}
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Upload failed', '', ErrorDialog, {
|
||||
|
|
|
@ -19,16 +19,21 @@ limitations under the License.
|
|||
|
||||
import ReplyThread from "./components/views/elements/ReplyThread";
|
||||
|
||||
const React = require('react');
|
||||
const sanitizeHtml = require('sanitize-html');
|
||||
const highlight = require('highlight.js');
|
||||
const linkifyMatrix = require('./linkify-matrix');
|
||||
import React from 'react';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import highlight from 'highlight.js';
|
||||
import * as linkify from 'linkifyjs';
|
||||
import linkifyMatrix from './linkify-matrix';
|
||||
import _linkifyElement from 'linkifyjs/element';
|
||||
import _linkifyString from 'linkifyjs/string';
|
||||
import escape from 'lodash/escape';
|
||||
import emojione from 'emojione';
|
||||
import classNames from 'classnames';
|
||||
import MatrixClientPeg from './MatrixClientPeg';
|
||||
import url from 'url';
|
||||
|
||||
linkifyMatrix(linkify);
|
||||
|
||||
emojione.imagePathSVG = 'emojione/svg/';
|
||||
// Store PNG path for displaying many flags at once (for increased performance over SVG)
|
||||
emojione.imagePathPNG = 'emojione/png/';
|
||||
|
@ -63,8 +68,10 @@ export function containsEmoji(str) {
|
|||
/* modified from https://github.com/Ranks/emojione/blob/master/lib/js/emojione.js
|
||||
* because we want to include emoji shortnames in title text
|
||||
*/
|
||||
function unicodeToImage(str) {
|
||||
let replaceWith; let unicode; let alt; let short; let fname;
|
||||
function unicodeToImage(str, addAlt) {
|
||||
if (addAlt === undefined) addAlt = true;
|
||||
|
||||
let replaceWith; let unicode; let short; let fname;
|
||||
const mappedUnicode = emojione.mapUnicodeToShort();
|
||||
|
||||
str = str.replace(emojione.regUnicode, function(unicodeChar) {
|
||||
|
@ -79,10 +86,14 @@ function unicodeToImage(str) {
|
|||
fname = emojione.emojioneList[short].fname;
|
||||
|
||||
// depending on the settings, we'll either add the native unicode as the alt tag, otherwise the shortname
|
||||
alt = (emojione.unicodeAlt) ? emojione.convert(unicode.toUpperCase()) : mappedUnicode[unicode];
|
||||
const title = mappedUnicode[unicode];
|
||||
|
||||
replaceWith = `<img class="mx_emojione" title="${title}" alt="${alt}" src="${emojione.imagePathSVG}${fname}.svg${emojione.cacheBustParam}"/>`;
|
||||
if (addAlt) {
|
||||
const alt = (emojione.unicodeAlt) ? emojione.convert(unicode.toUpperCase()) : mappedUnicode[unicode];
|
||||
replaceWith = `<img class="mx_emojione" title="${title}" alt="${alt}" src="${emojione.imagePathSVG}${fname}.svg${emojione.cacheBustParam}"/>`;
|
||||
} else {
|
||||
replaceWith = `<img class="mx_emojione" src="${emojione.imagePathSVG}${fname}.svg${emojione.cacheBustParam}"/>`;
|
||||
}
|
||||
return replaceWith;
|
||||
}
|
||||
});
|
||||
|
@ -503,8 +514,39 @@ export function bodyToHtml(content, highlights, opts={}) {
|
|||
<span className={className} dir="auto">{ strippedBody }</span>;
|
||||
}
|
||||
|
||||
export function emojifyText(text) {
|
||||
export function emojifyText(text, addAlt) {
|
||||
return {
|
||||
__html: unicodeToImage(escape(text)),
|
||||
__html: unicodeToImage(escape(text), addAlt),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Linkifies the given string. This is a wrapper around 'linkifyjs/string'.
|
||||
*
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
export function linkifyString(str) {
|
||||
return _linkifyString(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Linkifies the given DOM element. This is a wrapper around 'linkifyjs/element'.
|
||||
*
|
||||
* @param {object} element DOM element to linkify
|
||||
* @param {object} [options] Options for linkifyElement. Default: linkifyMatrix.options
|
||||
* @returns {object}
|
||||
*/
|
||||
export function linkifyElement(element, options = linkifyMatrix.options) {
|
||||
return _linkifyElement(element, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Linkify the given string and sanitize the HTML afterwards.
|
||||
*
|
||||
* @param {string} dirtyHtml The HTML string to sanitize and linkify
|
||||
* @returns {string}
|
||||
*/
|
||||
export function linkifyAndSanitizeHtml(dirtyHtml) {
|
||||
return sanitizeHtml(linkifyString(dirtyHtml), sanitizeHtmlParams);
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ class MatrixClientPeg {
|
|||
|
||||
/**
|
||||
* Replace this MatrixClientPeg's client with a client instance that has
|
||||
* Home Server / Identity Server URLs and active credentials
|
||||
* homeserver / identity server URLs and active credentials
|
||||
*/
|
||||
replaceUsingCreds(creds: MatrixClientCreds) {
|
||||
this._currentClientCreds = creds;
|
||||
|
@ -135,14 +135,7 @@ class MatrixClientPeg {
|
|||
const opts = utils.deepCopy(this.opts);
|
||||
// the react sdk doesn't work without this, so don't allow
|
||||
opts.pendingEventOrdering = "detached";
|
||||
|
||||
const LAZY_LOADING_FEATURE = "feature_lazyloading";
|
||||
if (SettingsStore.isFeatureEnabled(LAZY_LOADING_FEATURE)) {
|
||||
const userId = this.matrixClient.credentials.userId;
|
||||
if (phasedRollOutExpiredForUser(userId, LAZY_LOADING_FEATURE, Date.now())) {
|
||||
opts.lazyLoadMembers = true;
|
||||
}
|
||||
}
|
||||
opts.lazyLoadMembers = true;
|
||||
|
||||
// Connect the matrix client to the dispatcher
|
||||
MatrixActionCreators.start(this.matrixClient);
|
||||
|
@ -164,14 +157,14 @@ class MatrixClientPeg {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return the server name of the user's home server
|
||||
* Throws an error if unable to deduce the home server name
|
||||
* Return the server name of the user's homeserver
|
||||
* Throws an error if unable to deduce the homeserver name
|
||||
* (eg. if the user is not logged in)
|
||||
*/
|
||||
getHomeServerName() {
|
||||
const matches = /^@.+:(.+)$/.exec(this.matrixClient.credentials.userId);
|
||||
if (matches === null || matches.length < 1) {
|
||||
throw new Error("Failed to derive home server name from user ID!");
|
||||
throw new Error("Failed to derive homeserver name from user ID!");
|
||||
}
|
||||
return matches[1];
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ limitations under the License.
|
|||
export default {
|
||||
HomePage: "home_page",
|
||||
RoomView: "room_view",
|
||||
UserSettings: "user_settings",
|
||||
RoomDirectory: "room_directory",
|
||||
UserView: "user_view",
|
||||
GroupView: "group_view",
|
||||
|
|
|
@ -33,7 +33,7 @@ class Presence {
|
|||
}
|
||||
/**
|
||||
* Start listening the user activity to evaluate his presence state.
|
||||
* Any state change will be sent to the Home Server.
|
||||
* Any state change will be sent to the homeserver.
|
||||
*/
|
||||
async start() {
|
||||
this._unavailableTimer = new Timer(UNAVAILABLE_TIME_MS);
|
||||
|
@ -78,7 +78,7 @@ class Presence {
|
|||
|
||||
/**
|
||||
* Set the presence state.
|
||||
* If the state has changed, the Home Server will be notified.
|
||||
* If the state has changed, the homeserver will be notified.
|
||||
* @param {string} newState the new presence state (see PRESENCE enum)
|
||||
*/
|
||||
async setState(newState) {
|
||||
|
|
|
@ -35,8 +35,10 @@ export const SAFE_LOCALPART_REGEX = /^[a-z0-9=_\-./]+$/;
|
|||
* on what the HS supports
|
||||
*
|
||||
* @param {object} options
|
||||
* @param {bool} options.go_home_on_cancel If true, goes to
|
||||
* the hame page if the user cancels the action
|
||||
* @param {bool} options.go_home_on_cancel
|
||||
* If true, goes to the home page if the user cancels the action
|
||||
* @param {bool} options.go_welcome_on_cancel
|
||||
* If true, goes to the welcome page if the user cancels the action
|
||||
*/
|
||||
export async function startAnyRegistrationFlow(options) {
|
||||
if (options === undefined) options = {};
|
||||
|
@ -73,6 +75,8 @@ export async function startAnyRegistrationFlow(options) {
|
|||
dis.dispatch({action: 'start_registration'});
|
||||
} else if (options.go_home_on_cancel) {
|
||||
dis.dispatch({action: 'view_home_page'});
|
||||
} else if (options.go_welcome_on_cancel) {
|
||||
dis.dispatch({action: 'view_welcome_page'});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -23,6 +23,47 @@ export const ALL_MESSAGES = 'all_messages';
|
|||
export const MENTIONS_ONLY = 'mentions_only';
|
||||
export const MUTE = 'mute';
|
||||
|
||||
|
||||
function _shouldShowNotifBadge(roomNotifState) {
|
||||
const showBadgeInStates = [ALL_MESSAGES, ALL_MESSAGES_LOUD];
|
||||
return showBadgeInStates.indexOf(roomNotifState) > -1;
|
||||
}
|
||||
|
||||
function _shouldShowMentionBadge(roomNotifState) {
|
||||
return roomNotifState !== MUTE;
|
||||
}
|
||||
|
||||
export function aggregateNotificationCount(rooms) {
|
||||
return rooms.reduce((result, room, index) => {
|
||||
const roomNotifState = getRoomNotifsState(room.roomId);
|
||||
const highlight = room.getUnreadNotificationCount('highlight') > 0;
|
||||
const notificationCount = room.getUnreadNotificationCount();
|
||||
|
||||
const notifBadges = notificationCount > 0 && _shouldShowNotifBadge(roomNotifState);
|
||||
const mentionBadges = highlight && _shouldShowMentionBadge(roomNotifState);
|
||||
const badges = notifBadges || mentionBadges;
|
||||
|
||||
if (badges) {
|
||||
result.count += notificationCount;
|
||||
if (highlight) {
|
||||
result.highlight = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}, {count: 0, highlight: false});
|
||||
}
|
||||
|
||||
export function getRoomHasBadge(room) {
|
||||
const roomNotifState = getRoomNotifsState(room.roomId);
|
||||
const highlight = room.getUnreadNotificationCount('highlight') > 0;
|
||||
const notificationCount = room.getUnreadNotificationCount();
|
||||
|
||||
const notifBadges = notificationCount > 0 && _shouldShowNotifBadge(roomNotifState);
|
||||
const mentionBadges = highlight && _shouldShowMentionBadge(roomNotifState);
|
||||
|
||||
return notifBadges || mentionBadges;
|
||||
}
|
||||
|
||||
export function getRoomNotifsState(roomId) {
|
||||
if (MatrixClientPeg.get().isGuest()) return ALL_MESSAGES;
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ import SettingsStore, {SettingLevel} from './settings/SettingsStore';
|
|||
import {MATRIXTO_URL_PATTERN} from "./linkify-matrix";
|
||||
import * as querystring from "querystring";
|
||||
import MultiInviter from './utils/MultiInviter';
|
||||
|
||||
import { linkifyAndSanitizeHtml } from './HtmlUtils';
|
||||
|
||||
class Command {
|
||||
constructor({name, args='', description, runFn, hideCompletionAfterSpace=false}) {
|
||||
|
@ -137,13 +137,26 @@ export const CommandMap = {
|
|||
|
||||
topic: new Command({
|
||||
name: 'topic',
|
||||
args: '<topic>',
|
||||
description: _td('Sets the room topic'),
|
||||
args: '[<topic>]',
|
||||
description: _td('Gets or sets the room topic'),
|
||||
runFn: function(roomId, args) {
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (args) {
|
||||
return success(MatrixClientPeg.get().setRoomTopic(roomId, args));
|
||||
return success(cli.setRoomTopic(roomId, args));
|
||||
}
|
||||
return reject(this.getUsage());
|
||||
const room = cli.getRoom(roomId);
|
||||
if (!room) return reject('Bad room ID: ' + roomId);
|
||||
|
||||
const topicEvents = room.currentState.getStateEvents('m.room.topic', '');
|
||||
const topic = topicEvents && topicEvents.getContent().topic;
|
||||
const topicHtml = topic ? linkifyAndSanitizeHtml(topic) : _t('This room has no topic.');
|
||||
|
||||
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
|
||||
Modal.createTrackedDialog('Slash Commands', 'Topic', InfoDialog, {
|
||||
title: room.name,
|
||||
description: <div dangerouslySetInnerHTML={{ __html: topicHtml }} />,
|
||||
});
|
||||
return success();
|
||||
},
|
||||
}),
|
||||
|
||||
|
@ -391,13 +404,12 @@ export const CommandMap = {
|
|||
ignoredUsers.push(userId); // de-duped internally in the js-sdk
|
||||
return success(
|
||||
cli.setIgnoredUsers(ignoredUsers).then(() => {
|
||||
const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
|
||||
Modal.createTrackedDialog('Slash Commands', 'User ignored', QuestionDialog, {
|
||||
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
|
||||
Modal.createTrackedDialog('Slash Commands', 'User ignored', InfoDialog, {
|
||||
title: _t('Ignored user'),
|
||||
description: <div>
|
||||
<p>{ _t('You are now ignoring %(userId)s', {userId}) }</p>
|
||||
</div>,
|
||||
hasCancelButton: false,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
@ -423,13 +435,12 @@ export const CommandMap = {
|
|||
if (index !== -1) ignoredUsers.splice(index, 1);
|
||||
return success(
|
||||
cli.setIgnoredUsers(ignoredUsers).then(() => {
|
||||
const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
|
||||
Modal.createTrackedDialog('Slash Commands', 'User unignored', QuestionDialog, {
|
||||
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
|
||||
Modal.createTrackedDialog('Slash Commands', 'User unignored', InfoDialog, {
|
||||
title: _t('Unignored user'),
|
||||
description: <div>
|
||||
<p>{ _t('You are no longer ignoring %(userId)s', {userId}) }</p>
|
||||
</div>,
|
||||
hasCancelButton: false,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
@ -546,8 +557,8 @@ export const CommandMap = {
|
|||
return cli.setDeviceVerified(userId, deviceId, true);
|
||||
}).then(() => {
|
||||
// Tell the user we verified everything
|
||||
const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
|
||||
Modal.createTrackedDialog('Slash Commands', 'Verified key', QuestionDialog, {
|
||||
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
|
||||
Modal.createTrackedDialog('Slash Commands', 'Verified key', InfoDialog, {
|
||||
title: _t('Verified key'),
|
||||
description: <div>
|
||||
<p>
|
||||
|
@ -558,7 +569,6 @@ export const CommandMap = {
|
|||
}
|
||||
</p>
|
||||
</div>,
|
||||
hasCancelButton: false,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
|
|
@ -134,6 +134,38 @@ function textForTombstoneEvent(ev) {
|
|||
return _t('%(senderDisplayName)s upgraded this room.', {senderDisplayName});
|
||||
}
|
||||
|
||||
function textForJoinRulesEvent(ev) {
|
||||
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
|
||||
switch (ev.getContent().join_rule) {
|
||||
case "public":
|
||||
return _t('%(senderDisplayName)s made the room public to whoever knows the link.', {senderDisplayName});
|
||||
case "invite":
|
||||
return _t('%(senderDisplayName)s made the room invite only.', {senderDisplayName});
|
||||
default:
|
||||
// The spec supports "knock" and "private", however nothing implements these.
|
||||
return _t('%(senderDisplayName)s changed the join rule to %(rule)s', {
|
||||
senderDisplayName,
|
||||
rule: ev.getContent().join_rule,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function textForGuestAccessEvent(ev) {
|
||||
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
|
||||
switch (ev.getContent().guest_access) {
|
||||
case "can_join":
|
||||
return _t('%(senderDisplayName)s has allowed guests to join the room.', {senderDisplayName});
|
||||
case "forbidden":
|
||||
return _t('%(senderDisplayName)s has prevented guests from joining the room.', {senderDisplayName});
|
||||
default:
|
||||
// There's no other options we can expect, however just for safety's sake we'll do this.
|
||||
return _t('%(senderDisplayName)s changed guest access to %(rule)s', {
|
||||
senderDisplayName,
|
||||
rule: ev.getContent().guest_access,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function textForServerACLEvent(ev) {
|
||||
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
|
||||
const prevContent = ev.getPrevContent();
|
||||
|
@ -439,6 +471,8 @@ const stateHandlers = {
|
|||
'm.room.pinned_events': textForPinnedEvent,
|
||||
'm.room.server_acl': textForServerACLEvent,
|
||||
'm.room.tombstone': textForTombstoneEvent,
|
||||
'm.room.join_rules': textForJoinRulesEvent,
|
||||
'm.room.guest_access': textForGuestAccessEvent,
|
||||
|
||||
'im.vector.modular.widgets': textForWidgetEvent,
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 New Vector Ltd
|
||||
Copyright 2017, 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.
|
||||
|
@ -15,47 +15,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import Promise from 'bluebird';
|
||||
import MatrixClientPeg from './MatrixClientPeg';
|
||||
|
||||
/*
|
||||
* TODO: Make things use this. This is all WIP - see UserSettings.js for usage.
|
||||
*/
|
||||
// TODO: Decommission.
|
||||
// Ref: https://github.com/vector-im/riot-web/issues/8424
|
||||
export default {
|
||||
loadProfileInfo: function() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
return cli.getProfileInfo(cli.credentials.userId);
|
||||
},
|
||||
|
||||
saveDisplayName: function(newDisplayname) {
|
||||
return MatrixClientPeg.get().setDisplayName(newDisplayname);
|
||||
},
|
||||
|
||||
loadThreePids: function() {
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
return Promise.resolve({
|
||||
threepids: [],
|
||||
}); // guests can't poke 3pid endpoint
|
||||
}
|
||||
return MatrixClientPeg.get().getThreePids();
|
||||
},
|
||||
|
||||
saveThreePids: function(threePids) {
|
||||
// TODO
|
||||
},
|
||||
|
||||
changePassword: function(oldPassword, newPassword) {
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
||||
const authDict = {
|
||||
type: 'm.login.password',
|
||||
user: cli.credentials.userId,
|
||||
password: oldPassword,
|
||||
};
|
||||
|
||||
return cli.setPassword(authDict, newPassword);
|
||||
},
|
||||
|
||||
/*
|
||||
* Returns the email pusher (pusher of type 'email') for a given
|
||||
* email address. Email pushers all have the same app ID, so since
|
||||
|
@ -74,10 +38,6 @@ export default {
|
|||
return undefined;
|
||||
},
|
||||
|
||||
hasEmailPusher: function(pushers, address) {
|
||||
return this.getEmailPusher(pushers, address) !== undefined;
|
||||
},
|
||||
|
||||
addEmailPusher: function(address, data) {
|
||||
return MatrixClientPeg.get().setPusher({
|
||||
kind: 'email',
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import CustomRoomTagStore from '../../stores/CustomRoomTagStore';
|
||||
import AutoHideScrollbar from './AutoHideScrollbar';
|
||||
import sdk from '../../index';
|
||||
import dis from '../../dispatcher';
|
||||
import classNames from 'classnames';
|
||||
import * as FormattingUtils from '../../utils/FormattingUtils';
|
||||
|
||||
class CustomRoomTagPanel extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
tags: CustomRoomTagStore.getSortedTags(),
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this._tagStoreToken = CustomRoomTagStore.addListener(() => {
|
||||
this.setState({tags: CustomRoomTagStore.getSortedTags()});
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this._tagStoreToken) {
|
||||
this._tagStoreToken.remove();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const tags = this.state.tags.map((tag) => {
|
||||
return (<CustomRoomTagTile tag={tag} key={tag.name} />);
|
||||
});
|
||||
|
||||
const classes = classNames('mx_CustomRoomTagPanel', {
|
||||
mx_CustomRoomTagPanel_empty: this.state.tags.length === 0,
|
||||
});
|
||||
|
||||
return (<div className={classes}>
|
||||
<div className="mx_CustomRoomTagPanel_divider" />
|
||||
<AutoHideScrollbar className="mx_CustomRoomTagPanel_scroller">
|
||||
{tags}
|
||||
</AutoHideScrollbar>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomRoomTagTile extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {hover: false};
|
||||
this.onClick = this.onClick.bind(this);
|
||||
this.onMouseOut = this.onMouseOut.bind(this);
|
||||
this.onMouseOver = this.onMouseOver.bind(this);
|
||||
}
|
||||
|
||||
onMouseOver() {
|
||||
this.setState({hover: true});
|
||||
}
|
||||
|
||||
onMouseOut() {
|
||||
this.setState({hover: false});
|
||||
}
|
||||
|
||||
onClick() {
|
||||
dis.dispatch({action: 'select_custom_room_tag', tag: this.props.tag.name});
|
||||
}
|
||||
|
||||
render() {
|
||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const RoomTooltip = sdk.getComponent('rooms.RoomTooltip');
|
||||
|
||||
const tag = this.props.tag;
|
||||
const avatarHeight = 40;
|
||||
const className = classNames({
|
||||
CustomRoomTagPanel_tileSelected: tag.selected,
|
||||
});
|
||||
const name = tag.name;
|
||||
const badge = tag.badge;
|
||||
let badgeElement;
|
||||
if (badge) {
|
||||
const badgeClasses = classNames({
|
||||
"mx_TagTile_badge": true,
|
||||
"mx_TagTile_badgeHighlight": badge.highlight,
|
||||
});
|
||||
badgeElement = (<div className={badgeClasses}>{FormattingUtils.formatCount(badge.count)}</div>);
|
||||
}
|
||||
|
||||
const tip = (this.state.hover ?
|
||||
<RoomTooltip className="mx_TagTile_tooltip" label={name} /> :
|
||||
<div />);
|
||||
return (
|
||||
<AccessibleButton className={className} onClick={this.onClick}>
|
||||
<div className="mx_TagTile_avatar" onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut}>
|
||||
<BaseAvatar
|
||||
name={tag.avatarLetter}
|
||||
idName={name}
|
||||
width={avatarHeight}
|
||||
height={avatarHeight}
|
||||
/>
|
||||
{ badgeElement }
|
||||
{ tip }
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CustomRoomTagPanel;
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
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 PropTypes from 'prop-types';
|
||||
import request from 'browser-request';
|
||||
import { _t } from '../../languageHandler';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import sdk from '../../index';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import classnames from 'classnames';
|
||||
|
||||
export default class EmbeddedPage extends React.PureComponent {
|
||||
static propTypes = {
|
||||
// URL to request embedded page content from
|
||||
url: PropTypes.string,
|
||||
// Class name prefix to apply for a given instance
|
||||
className: PropTypes.string,
|
||||
// Whether to wrap the page in a scrollbar
|
||||
scrollbar: PropTypes.bool,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
page: '',
|
||||
};
|
||||
}
|
||||
|
||||
translate(s) {
|
||||
// default implementation - skins may wish to extend this
|
||||
return sanitizeHtml(_t(s));
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this._unmounted = false;
|
||||
|
||||
if (!this.props.url) {
|
||||
return;
|
||||
}
|
||||
|
||||
// we use request() to inline the page into the react component
|
||||
// so that it can inherit CSS and theming easily rather than mess around
|
||||
// with iframes and trying to synchronise document.stylesheets.
|
||||
|
||||
request(
|
||||
{ method: "GET", url: this.props.url },
|
||||
(err, response, body) => {
|
||||
if (this._unmounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (err || response.status < 200 || response.status >= 300) {
|
||||
console.warn(`Error loading page: ${err}`);
|
||||
this.setState({ page: _t("Couldn't load page") });
|
||||
return;
|
||||
}
|
||||
|
||||
body = body.replace(/_t\(['"]([\s\S]*?)['"]\)/mg, (match, g1)=>this.translate(g1));
|
||||
this.setState({ page: body });
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._unmounted = true;
|
||||
}
|
||||
|
||||
render() {
|
||||
const client = this.context.matrixClient;
|
||||
const isGuest = client ? client.isGuest() : true;
|
||||
const className = this.props.className;
|
||||
const classes = classnames({
|
||||
[className]: true,
|
||||
[`${className}_guest`]: isGuest,
|
||||
});
|
||||
|
||||
const content = <div className={`${className}_body`}
|
||||
dangerouslySetInnerHTML={{ __html: this.state.page }}
|
||||
>
|
||||
</div>;
|
||||
|
||||
if (this.props.scrollbar) {
|
||||
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
||||
return <GeminiScrollbarWrapper autoshow={true} className={classes}>
|
||||
{content}
|
||||
</GeminiScrollbarWrapper>;
|
||||
} else {
|
||||
return <div className={classes}>
|
||||
{content}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1324,7 +1324,7 @@ export default React.createClass({
|
|||
} else {
|
||||
let extraText;
|
||||
if (this.state.error.errcode === 'M_UNRECOGNIZED') {
|
||||
extraText = <div>{ _t('This Home server does not support communities') }</div>;
|
||||
extraText = <div>{ _t('This homeserver does not support communities') }</div>;
|
||||
}
|
||||
return (
|
||||
<div className="mx_GroupView_error">
|
||||
|
|
|
@ -1,144 +0,0 @@
|
|||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import request from 'browser-request';
|
||||
import { _t } from '../../languageHandler';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import sdk from '../../index';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import dis from '../../dispatcher';
|
||||
|
||||
class HomePage extends React.Component {
|
||||
static displayName = 'HomePage';
|
||||
|
||||
static propTypes = {
|
||||
// URL to use as the iFrame src. Defaults to /home.html.
|
||||
homePageUrl: PropTypes.string,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||
};
|
||||
|
||||
state = {
|
||||
iframeSrc: '',
|
||||
page: '',
|
||||
};
|
||||
|
||||
translate(s) {
|
||||
// default implementation - skins may wish to extend this
|
||||
return sanitizeHtml(_t(s));
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this._unmounted = false;
|
||||
|
||||
// we use request() to inline the homepage into the react component
|
||||
// so that it can inherit CSS and theming easily rather than mess around
|
||||
// with iframes and trying to synchronise document.stylesheets.
|
||||
|
||||
const src = this.props.homePageUrl || 'home.html';
|
||||
|
||||
request(
|
||||
{ method: "GET", url: src },
|
||||
(err, response, body) => {
|
||||
if (this._unmounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (err || response.status < 200 || response.status >= 300) {
|
||||
console.warn(`Error loading home page: ${err}`);
|
||||
this.setState({ page: _t("Couldn't load home page") });
|
||||
return;
|
||||
}
|
||||
|
||||
body = body.replace(/_t\(['"]([\s\S]*?)['"]\)/mg, (match, g1)=>this.translate(g1));
|
||||
this.setState({ page: body });
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._unmounted = true;
|
||||
}
|
||||
|
||||
onLoginClick(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
dis.dispatch({ action: 'start_login' });
|
||||
}
|
||||
|
||||
onRegisterClick(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
dis.dispatch({ action: 'start_registration' });
|
||||
}
|
||||
|
||||
render() {
|
||||
let guestWarning = "";
|
||||
if (this.context.matrixClient.isGuest()) {
|
||||
guestWarning = (
|
||||
<div className="mx_HomePage_guest_warning">
|
||||
<img src={require("../../../res/img/warning.svg")} width="24" height="23" />
|
||||
<div>
|
||||
<div>
|
||||
{ _t("You are currently using Riot anonymously as a guest.") }
|
||||
</div>
|
||||
<div>
|
||||
{ _t(
|
||||
'If you would like to create a Matrix account you can <a>register</a> now.',
|
||||
{},
|
||||
{ 'a': (sub) => <a href="#" onClick={this.onRegisterClick}>{ sub }</a> },
|
||||
) }
|
||||
</div>
|
||||
<div>
|
||||
{ _t(
|
||||
'If you already have a Matrix account you can <a>log in</a> instead.',
|
||||
{},
|
||||
{ 'a': (sub) => <a href="#" onClick={this.onLoginClick}>{ sub }</a> },
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.iframeSrc) {
|
||||
return (
|
||||
<div className="mx_HomePage">
|
||||
{ guestWarning }
|
||||
<iframe src={ this.state.iframeSrc } />
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
||||
return (
|
||||
<GeminiScrollbarWrapper autoshow={true} className="mx_HomePage">
|
||||
{ guestWarning }
|
||||
<div className="mx_HomePage_body" dangerouslySetInnerHTML={{ __html: this.state.page }}>
|
||||
</div>
|
||||
</GeminiScrollbarWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HomePage;
|
|
@ -24,7 +24,7 @@ import { KeyCode } from '../../Keyboard';
|
|||
import sdk from '../../index';
|
||||
import dis from '../../dispatcher';
|
||||
import VectorConferenceHandler from '../../VectorConferenceHandler';
|
||||
|
||||
import TagPanelButtons from './TagPanelButtons';
|
||||
import SettingsStore from '../../settings/SettingsStore';
|
||||
|
||||
|
||||
|
@ -183,12 +183,23 @@ const LeftPanel = React.createClass({
|
|||
render: function() {
|
||||
const RoomList = sdk.getComponent('rooms.RoomList');
|
||||
const TagPanel = sdk.getComponent('structures.TagPanel');
|
||||
const CustomRoomTagPanel = sdk.getComponent('structures.CustomRoomTagPanel');
|
||||
const TopLeftMenuButton = sdk.getComponent('structures.TopLeftMenuButton');
|
||||
const SearchBox = sdk.getComponent('structures.SearchBox');
|
||||
const CallPreview = sdk.getComponent('voip.CallPreview');
|
||||
|
||||
const tagPanelEnabled = SettingsStore.getValue("TagPanel.enableTagPanel");
|
||||
const tagPanel = tagPanelEnabled ? <TagPanel /> : <div />;
|
||||
let tagPanelContainer;
|
||||
|
||||
const isCustomTagsEnabled = SettingsStore.isFeatureEnabled("feature_custom_tags");
|
||||
|
||||
if (tagPanelEnabled) {
|
||||
tagPanelContainer = (<div className="mx_LeftPanel_tagPanelContainer">
|
||||
<TagPanel />
|
||||
{ isCustomTagsEnabled ? <CustomRoomTagPanel /> : undefined }
|
||||
<TagPanelButtons />
|
||||
</div>);
|
||||
}
|
||||
|
||||
const containerClasses = classNames(
|
||||
"mx_LeftPanel_container", "mx_fadable",
|
||||
|
@ -199,13 +210,15 @@ const LeftPanel = React.createClass({
|
|||
},
|
||||
);
|
||||
|
||||
const searchBox = !this.props.collapsed ?
|
||||
<SearchBox onSearch={ this.onSearch } onCleared={ this.onSearchCleared } /> :
|
||||
undefined;
|
||||
const searchBox = (<SearchBox
|
||||
onSearch={ this.onSearch }
|
||||
onCleared={ this.onSearchCleared }
|
||||
collapsed={this.props.collapsed} />);
|
||||
|
||||
|
||||
return (
|
||||
<div className={containerClasses}>
|
||||
{ tagPanel }
|
||||
{ tagPanelContainer }
|
||||
<aside className={"mx_LeftPanel dark-panel"} onKeyDown={ this._onKeyDown } onFocus={ this._onFocus } onBlur={ this._onBlur }>
|
||||
<TopLeftMenuButton collapsed={ this.props.collapsed } />
|
||||
{ searchBox }
|
||||
|
|
|
@ -57,7 +57,6 @@ const LoggedInView = React.createClass({
|
|||
matrixClient: PropTypes.instanceOf(Matrix.MatrixClient).isRequired,
|
||||
page_type: PropTypes.string.isRequired,
|
||||
onRoomCreated: PropTypes.func,
|
||||
onUserSettingsClose: PropTypes.func,
|
||||
|
||||
// Called with the credentials of a registered user (if they were a ROU that
|
||||
// transitioned to PWLU)
|
||||
|
@ -421,8 +420,7 @@ const LoggedInView = React.createClass({
|
|||
render: function() {
|
||||
const LeftPanel = sdk.getComponent('structures.LeftPanel');
|
||||
const RoomView = sdk.getComponent('structures.RoomView');
|
||||
const UserSettings = sdk.getComponent('structures.UserSettings');
|
||||
const HomePage = sdk.getComponent('structures.HomePage');
|
||||
const EmbeddedPage = sdk.getComponent('structures.EmbeddedPage');
|
||||
const GroupView = sdk.getComponent('structures.GroupView');
|
||||
const MyGroups = sdk.getComponent('structures.MyGroups');
|
||||
const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
|
||||
|
@ -451,13 +449,6 @@ const LoggedInView = React.createClass({
|
|||
/>;
|
||||
break;
|
||||
|
||||
case PageTypes.UserSettings:
|
||||
pageElement = <UserSettings
|
||||
onClose={this.props.onCloseAllSettings}
|
||||
brand={this.props.config.brand}
|
||||
/>;
|
||||
break;
|
||||
|
||||
case PageTypes.MyGroups:
|
||||
pageElement = <MyGroups />;
|
||||
break;
|
||||
|
@ -468,8 +459,20 @@ const LoggedInView = React.createClass({
|
|||
|
||||
case PageTypes.HomePage:
|
||||
{
|
||||
pageElement = <HomePage
|
||||
homePageUrl={this.props.config.welcomePageUrl}
|
||||
const pagesConfig = this.props.config.embeddedPages;
|
||||
let pageUrl = null;
|
||||
if (pagesConfig) {
|
||||
pageUrl = pagesConfig.homeUrl;
|
||||
}
|
||||
if (!pageUrl) {
|
||||
// This is a deprecated config option for the home page
|
||||
// (despite the name, given we also now have a welcome
|
||||
// page, which is not the same).
|
||||
pageUrl = this.props.config.welcomePageUrl;
|
||||
}
|
||||
pageElement = <EmbeddedPage className="mx_HomePage"
|
||||
url={pageUrl}
|
||||
scrollbar={true}
|
||||
/>;
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -60,27 +60,30 @@ const VIEWS = {
|
|||
// trying to re-animate a matrix client or register as a guest.
|
||||
LOADING: 0,
|
||||
|
||||
// we are showing the welcome view
|
||||
WELCOME: 1,
|
||||
|
||||
// we are showing the login view
|
||||
LOGIN: 1,
|
||||
LOGIN: 2,
|
||||
|
||||
// we are showing the registration view
|
||||
REGISTER: 2,
|
||||
REGISTER: 3,
|
||||
|
||||
// completeing the registration flow
|
||||
POST_REGISTRATION: 3,
|
||||
POST_REGISTRATION: 4,
|
||||
|
||||
// showing the 'forgot password' view
|
||||
FORGOT_PASSWORD: 4,
|
||||
FORGOT_PASSWORD: 5,
|
||||
|
||||
// we have valid matrix credentials (either via an explicit login, via the
|
||||
// initial re-animation/guest registration, or via a registration), and are
|
||||
// now setting up a matrixclient to talk to it. This isn't an instant
|
||||
// process because we need to clear out indexeddb. While it is going on we
|
||||
// show a big spinner.
|
||||
LOGGING_IN: 5,
|
||||
LOGGING_IN: 6,
|
||||
|
||||
// we are logged in with an active matrix client.
|
||||
LOGGED_IN: 6,
|
||||
LOGGED_IN: 7,
|
||||
};
|
||||
|
||||
// Actions that are redirected through the onboarding process prior to being
|
||||
|
@ -136,10 +139,6 @@ export default React.createClass({
|
|||
appConfig: PropTypes.object,
|
||||
},
|
||||
|
||||
AuxPanel: {
|
||||
RoomSettings: "room_settings",
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return {
|
||||
appConfig: this.props.config,
|
||||
|
@ -358,8 +357,8 @@ export default React.createClass({
|
|||
});
|
||||
}).then((loadedSession) => {
|
||||
if (!loadedSession) {
|
||||
// fall back to showing the login screen
|
||||
dis.dispatch({action: "start_login"});
|
||||
// fall back to showing the welcome screen
|
||||
dis.dispatch({action: "view_welcome_page"});
|
||||
}
|
||||
});
|
||||
// Note we don't catch errors from this: we catch everything within
|
||||
|
@ -572,40 +571,16 @@ export default React.createClass({
|
|||
this._viewIndexedRoom(payload.roomIndex);
|
||||
break;
|
||||
case 'view_user_settings': {
|
||||
if (SettingsStore.isFeatureEnabled("feature_tabbed_settings")) {
|
||||
const UserSettingsDialog = sdk.getComponent("dialogs.UserSettingsDialog");
|
||||
Modal.createTrackedDialog('User settings', '', UserSettingsDialog, {}, 'mx_SettingsDialog');
|
||||
} else {
|
||||
this._setPage(PageTypes.UserSettings);
|
||||
this.notifyNewScreen('settings');
|
||||
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');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'view_old_user_settings':
|
||||
this._setPage(PageTypes.UserSettings);
|
||||
this.notifyNewScreen('settings');
|
||||
break;
|
||||
case 'close_settings':
|
||||
this.setState({
|
||||
leftDisabled: false,
|
||||
rightDisabled: false,
|
||||
middleDisabled: false,
|
||||
});
|
||||
if (this.state.page_type === PageTypes.UserSettings) {
|
||||
// We do this to get setPage and notifyNewScreen
|
||||
if (this.state.currentRoomId) {
|
||||
this._viewRoom({
|
||||
room_id: this.state.currentRoomId,
|
||||
});
|
||||
} else if (this.state.currentGroupId) {
|
||||
this._viewGroup({
|
||||
group_id: this.state.currentGroupId,
|
||||
});
|
||||
} else {
|
||||
this._viewHome();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'view_create_room':
|
||||
this._createRoom();
|
||||
break;
|
||||
|
@ -634,6 +609,9 @@ export default React.createClass({
|
|||
case 'view_group':
|
||||
this._viewGroup(payload);
|
||||
break;
|
||||
case 'view_welcome_page':
|
||||
this._viewWelcome();
|
||||
break;
|
||||
case 'view_home_page':
|
||||
this._viewHome();
|
||||
break;
|
||||
|
@ -909,6 +887,13 @@ export default React.createClass({
|
|||
this.notifyNewScreen('group/' + groupId);
|
||||
},
|
||||
|
||||
_viewWelcome() {
|
||||
this.setStateForNewView({
|
||||
view: VIEWS.WELCOME,
|
||||
});
|
||||
this.notifyNewScreen('welcome');
|
||||
},
|
||||
|
||||
_viewHome: function() {
|
||||
// The home page requires the "logged in" view, so we'll set that.
|
||||
this.setStateForNewView({
|
||||
|
@ -982,11 +967,11 @@ export default React.createClass({
|
|||
}
|
||||
dis.dispatch({
|
||||
action: 'require_registration',
|
||||
// If the set_mxid dialog is cancelled, view /home because if the browser
|
||||
// was pointing at /user/@someone:domain?action=chat, the URL needs to be
|
||||
// reset so that they can revisit /user/.. // (and trigger
|
||||
// If the set_mxid dialog is cancelled, view /welcome because if the
|
||||
// browser was pointing at /user/@someone:domain?action=chat, the URL
|
||||
// needs to be reset so that they can revisit /user/.. // (and trigger
|
||||
// `_chatCreateOrReuse` again)
|
||||
go_home_on_cancel: true,
|
||||
go_welcome_on_cancel: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -1063,7 +1048,6 @@ export default React.createClass({
|
|||
modal.close();
|
||||
if (this.state.currentRoomId === roomId) {
|
||||
dis.dispatch({action: 'view_next_room'});
|
||||
dis.dispatch({action: 'close_room_settings'});
|
||||
}
|
||||
}, (err) => {
|
||||
modal.close();
|
||||
|
@ -1209,7 +1193,11 @@ export default React.createClass({
|
|||
room_id: localStorage.getItem('mx_last_room_id'),
|
||||
});
|
||||
} else {
|
||||
dis.dispatch({action: 'view_home_page'});
|
||||
if (MatrixClientPeg.get().isGuest) {
|
||||
dis.dispatch({action: 'view_welcome_page'});
|
||||
} else {
|
||||
dis.dispatch({action: 'view_home_page'});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1495,6 +1483,10 @@ export default React.createClass({
|
|||
dis.dispatch({
|
||||
action: 'view_user_settings',
|
||||
});
|
||||
} else if (screen == 'welcome') {
|
||||
dis.dispatch({
|
||||
action: 'view_welcome_page',
|
||||
});
|
||||
} else if (screen == 'home') {
|
||||
dis.dispatch({
|
||||
action: 'view_home_page',
|
||||
|
@ -1676,11 +1668,6 @@ export default React.createClass({
|
|||
this.showScreen("forgot_password");
|
||||
},
|
||||
|
||||
onReturnToAppClick: function() {
|
||||
// treat it the same as if the user had completed the login
|
||||
this._onLoggedIn();
|
||||
},
|
||||
|
||||
// returns a promise which resolves to the new MatrixClient
|
||||
onRegistered: function(credentials) {
|
||||
// XXX: This should be in state or ideally store(s) because we risk not
|
||||
|
@ -1883,6 +1870,11 @@ export default React.createClass({
|
|||
}
|
||||
}
|
||||
|
||||
if (this.state.view === VIEWS.WELCOME) {
|
||||
const Welcome = sdk.getComponent('auth.Welcome');
|
||||
return <Welcome />;
|
||||
}
|
||||
|
||||
if (this.state.view === VIEWS.REGISTER) {
|
||||
const Registration = sdk.getComponent('structures.auth.Registration');
|
||||
return (
|
||||
|
@ -1891,7 +1883,6 @@ export default React.createClass({
|
|||
sessionId={this.state.register_session_id}
|
||||
idSid={this.state.register_id_sid}
|
||||
email={this.props.startingFragmentQueryParams.email}
|
||||
referrer={this.props.startingFragmentQueryParams.referrer}
|
||||
defaultServerDiscoveryError={this.state.defaultServerDiscoveryError}
|
||||
defaultHsUrl={this.getDefaultHsUrl()}
|
||||
defaultIsUrl={this.getDefaultIsUrl()}
|
||||
|
@ -1899,11 +1890,8 @@ export default React.createClass({
|
|||
customHsUrl={this.getCurrentHsUrl()}
|
||||
customIsUrl={this.getCurrentIsUrl()}
|
||||
makeRegistrationUrl={this._makeRegistrationUrl}
|
||||
defaultDeviceDisplayName={this.props.defaultDeviceDisplayName}
|
||||
onLoggedIn={this.onRegistered}
|
||||
onLoginClick={this.onLoginClick}
|
||||
onRegisterClick={this.onRegisterClick}
|
||||
onCancelClick={MatrixClientPeg.get() ? this.onReturnToAppClick : null}
|
||||
onServerConfigChange={this.onServerConfigChange}
|
||||
/>
|
||||
);
|
||||
|
@ -1940,7 +1928,6 @@ export default React.createClass({
|
|||
defaultDeviceDisplayName={this.props.defaultDeviceDisplayName}
|
||||
onForgotPasswordClick={this.onForgotPasswordClick}
|
||||
enableGuest={this.props.enableGuest}
|
||||
onCancelClick={MatrixClientPeg.get() ? this.onReturnToAppClick : null}
|
||||
onServerConfigChange={this.onServerConfigChange}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -642,14 +642,13 @@ module.exports = React.createClass({
|
|||
|
||||
updateTimelineMinHeight: function() {
|
||||
const scrollPanel = this.refs.scrollPanel;
|
||||
const whoIsTyping = this.refs.whoIsTyping;
|
||||
const isTypingVisible = whoIsTyping && whoIsTyping.isVisible();
|
||||
|
||||
if (scrollPanel) {
|
||||
if (isTypingVisible) {
|
||||
const isAtBottom = scrollPanel.isAtBottom();
|
||||
const whoIsTyping = this.refs.whoIsTyping;
|
||||
const isTypingVisible = whoIsTyping && whoIsTyping.isVisible();
|
||||
if (isAtBottom && isTypingVisible) {
|
||||
scrollPanel.blockShrinking();
|
||||
} else {
|
||||
scrollPanel.clearBlockShrinking();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -24,10 +24,7 @@ const Modal = require('../../Modal');
|
|||
const sdk = require('../../index');
|
||||
const dis = require('../../dispatcher');
|
||||
|
||||
const linkify = require('linkifyjs');
|
||||
const linkifyString = require('linkifyjs/string');
|
||||
const linkifyMatrix = require('../../linkify-matrix');
|
||||
const sanitizeHtml = require('sanitize-html');
|
||||
import { linkifyAndSanitizeHtml } from '../../HtmlUtils';
|
||||
import Promise from 'bluebird';
|
||||
|
||||
import { _t } from '../../languageHandler';
|
||||
|
@ -37,8 +34,6 @@ import {instanceForInstanceId, protocolNameForInstanceId} from '../../utils/Dire
|
|||
const MAX_NAME_LENGTH = 80;
|
||||
const MAX_TOPIC_LENGTH = 160;
|
||||
|
||||
linkifyMatrix(linkify);
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'RoomDirectory',
|
||||
|
||||
|
@ -96,9 +91,9 @@ module.exports = React.createClass({
|
|||
return;
|
||||
}
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Failed to get protocol list from Home Server', '', ErrorDialog, {
|
||||
title: _t('Failed to get protocol list from Home Server'),
|
||||
description: _t('The Home Server may be too old to support third party networks'),
|
||||
Modal.createTrackedDialog('Failed to get protocol list from homeserver', '', ErrorDialog, {
|
||||
title: _t('Failed to get protocol list from homeserver'),
|
||||
description: _t('The homeserver may be too old to support third party networks'),
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -438,7 +433,7 @@ module.exports = React.createClass({
|
|||
if (topic.length > MAX_TOPIC_LENGTH) {
|
||||
topic = `${topic.substring(0, MAX_TOPIC_LENGTH)}...`;
|
||||
}
|
||||
topic = linkifyString(sanitizeHtml(topic));
|
||||
topic = linkifyAndSanitizeHtml(topic);
|
||||
|
||||
rows.push(
|
||||
<tr key={ rooms[i].room_id }
|
||||
|
|
|
@ -290,7 +290,7 @@ module.exports = React.createClass({
|
|||
}
|
||||
|
||||
return <div className="mx_RoomStatusBar_connectionLostBar">
|
||||
<img src={require("../../../res/img/warning.svg")} width="24" height="23" title={_t("Warning")} alt="" />
|
||||
<img src={require("../../../res/img/feather-icons/e2e/warning.svg")} width="24" height="24" title={_t("Warning")} alt="" />
|
||||
<div>
|
||||
<div className="mx_RoomStatusBar_connectionLostBar_title">
|
||||
{ title }
|
||||
|
@ -309,7 +309,7 @@ module.exports = React.createClass({
|
|||
if (this._shouldShowConnectionError()) {
|
||||
return (
|
||||
<div className="mx_RoomStatusBar_connectionLostBar">
|
||||
<img src={require("../../../res/img/warning.svg")} width="24" height="23" title="/!\ " alt="/!\ " />
|
||||
<img src={require("../../../res/img/feather-icons/e2e/warning.svg")} width="24" height="24" title="/!\ " alt="/!\ " />
|
||||
<div>
|
||||
<div className="mx_RoomStatusBar_connectionLostBar_title">
|
||||
{ _t('Connectivity to the server has been lost.') }
|
||||
|
|
|
@ -127,46 +127,6 @@ const RoomSubList = React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
_shouldShowNotifBadge: function(roomNotifState) {
|
||||
const showBadgeInStates = [RoomNotifs.ALL_MESSAGES, RoomNotifs.ALL_MESSAGES_LOUD];
|
||||
return showBadgeInStates.indexOf(roomNotifState) > -1;
|
||||
},
|
||||
|
||||
_shouldShowMentionBadge: function(roomNotifState) {
|
||||
return roomNotifState !== RoomNotifs.MUTE;
|
||||
},
|
||||
|
||||
/**
|
||||
* Total up all the notification counts from the rooms
|
||||
*
|
||||
* @returns {Array} The array takes the form [total, highlight] where highlight is a bool
|
||||
*/
|
||||
roomNotificationCount: function() {
|
||||
const self = this;
|
||||
|
||||
if (this.props.isInvite) {
|
||||
return [0, true];
|
||||
}
|
||||
|
||||
return this.props.list.reduce(function(result, room, index) {
|
||||
const roomNotifState = RoomNotifs.getRoomNotifsState(room.roomId);
|
||||
const highlight = room.getUnreadNotificationCount('highlight') > 0;
|
||||
const notificationCount = room.getUnreadNotificationCount();
|
||||
|
||||
const notifBadges = notificationCount > 0 && self._shouldShowNotifBadge(roomNotifState);
|
||||
const mentionBadges = highlight && self._shouldShowMentionBadge(roomNotifState);
|
||||
const badges = notifBadges || mentionBadges;
|
||||
|
||||
if (badges) {
|
||||
result[0] += notificationCount;
|
||||
if (highlight) {
|
||||
result[1] = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}, [0, false]);
|
||||
},
|
||||
|
||||
_updateSubListCount: function() {
|
||||
// Force an update by setting the state to the current state
|
||||
// Doing it this way rather than using forceUpdate(), so that the shouldComponentUpdate()
|
||||
|
@ -197,22 +157,12 @@ const RoomSubList = React.createClass({
|
|||
// prevent the roomsublist collapsing
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// find first room which has notifications and switch to it
|
||||
for (const room of this.props.list) {
|
||||
const roomNotifState = RoomNotifs.getRoomNotifsState(room.roomId);
|
||||
const highlight = room.getUnreadNotificationCount('highlight') > 0;
|
||||
const notificationCount = room.getUnreadNotificationCount();
|
||||
|
||||
const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge(roomNotifState);
|
||||
const mentionBadges = highlight && this._shouldShowMentionBadge(roomNotifState);
|
||||
|
||||
if (notifBadges || mentionBadges) {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: room.roomId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const room = this.props.list.find(room => RoomNotifs.getRoomHasBadge(room));
|
||||
if (room) {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: room.roomId,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -240,9 +190,11 @@ const RoomSubList = React.createClass({
|
|||
|
||||
_getHeaderJsx: function(isCollapsed) {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const subListNotifications = this.roomNotificationCount();
|
||||
const subListNotifCount = subListNotifications[0];
|
||||
const subListNotifHighlight = subListNotifications[1];
|
||||
const subListNotifications = !this.props.isInvite ?
|
||||
RoomNotifs.aggregateNotificationCount(this.props.list) :
|
||||
{count: 0, highlight: true};
|
||||
const subListNotifCount = subListNotifications.count;
|
||||
const subListNotifHighlight = subListNotifications.highlight;
|
||||
|
||||
let badge;
|
||||
if (!this.props.collapsed) {
|
||||
|
|
|
@ -78,7 +78,7 @@ module.exports = React.createClass({
|
|||
// * invitedEmail (string) The email address that was invited to this room
|
||||
thirdPartyInvite: PropTypes.object,
|
||||
|
||||
// Any data about the room that would normally come from the Home Server
|
||||
// Any data about the room that would normally come from the homeserver
|
||||
// but has been passed out-of-band, eg. the room name and avatar URL
|
||||
// from an email invite (a workaround for the fact that we can't
|
||||
// get this information from the HS using an email invite).
|
||||
|
@ -119,8 +119,6 @@ module.exports = React.createClass({
|
|||
isInitialEventHighlighted: null,
|
||||
|
||||
forwardingEvent: null,
|
||||
editingRoomSettings: false,
|
||||
uploadingRoomSettings: false,
|
||||
numUnreadMessages: 0,
|
||||
draggingFile: false,
|
||||
searching: false,
|
||||
|
@ -168,6 +166,7 @@ module.exports = React.createClass({
|
|||
MatrixClientPeg.get().on("Room.myMembership", this.onMyMembership);
|
||||
MatrixClientPeg.get().on("accountData", this.onAccountData);
|
||||
MatrixClientPeg.get().on("crypto.keyBackupStatus", this.onKeyBackupStatus);
|
||||
MatrixClientPeg.get().on("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||
this._fetchMediaConfig();
|
||||
// Start listening for RoomViewStore updates
|
||||
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
|
||||
|
@ -228,11 +227,8 @@ module.exports = React.createClass({
|
|||
forwardingEvent: RoomViewStore.getForwardingEvent(),
|
||||
shouldPeek: RoomViewStore.shouldPeek(),
|
||||
showingPinned: SettingsStore.getValue("PinnedEvents.isOpen", RoomViewStore.getRoomId()),
|
||||
editingRoomSettings: RoomViewStore.isEditingSettings(),
|
||||
};
|
||||
|
||||
if (this.state.editingRoomSettings && !newState.editingRoomSettings) dis.dispatch({action: 'focus_composer'});
|
||||
|
||||
// Temporary logging to diagnose https://github.com/vector-im/riot-web/issues/4307
|
||||
console.log(
|
||||
'RVS update:',
|
||||
|
@ -457,6 +453,7 @@ module.exports = React.createClass({
|
|||
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
|
||||
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
||||
MatrixClientPeg.get().removeListener("crypto.keyBackupStatus", this.onKeyBackupStatus);
|
||||
MatrixClientPeg.get().removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||
}
|
||||
|
||||
window.removeEventListener('beforeunload', this.onPageUnload);
|
||||
|
@ -589,6 +586,10 @@ module.exports = React.createClass({
|
|||
this._updatePreviewUrlVisibility(room);
|
||||
}
|
||||
|
||||
if (ev.getType() === "m.room.encryption") {
|
||||
this._updateE2EStatus(room);
|
||||
}
|
||||
|
||||
// ignore anything but real-time updates at the end of the room:
|
||||
// updates from pagination will happen when the paginate completes.
|
||||
if (toStartOfTimeline || !data || !data.liveEvent) return;
|
||||
|
@ -637,11 +638,11 @@ module.exports = React.createClass({
|
|||
// called when state.room is first initialised (either at initial load,
|
||||
// after a successful peek, or after we join the room).
|
||||
_onRoomLoaded: function(room) {
|
||||
this._warnAboutEncryption(room);
|
||||
this._calculatePeekRules(room);
|
||||
this._updatePreviewUrlVisibility(room);
|
||||
this._loadMembersIfJoined(room);
|
||||
this._calculateRecommendedVersion(room);
|
||||
this._updateE2EStatus(room);
|
||||
},
|
||||
|
||||
_calculateRecommendedVersion: async function(room) {
|
||||
|
@ -670,34 +671,6 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
_warnAboutEncryption: function(room) {
|
||||
if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) {
|
||||
return;
|
||||
}
|
||||
let userHasUsedEncryption = false;
|
||||
if (localStorage) {
|
||||
userHasUsedEncryption = localStorage.getItem('mx_user_has_used_encryption');
|
||||
}
|
||||
if (!userHasUsedEncryption) {
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createTrackedDialog('E2E Warning', '', QuestionDialog, {
|
||||
title: _t("Warning!"),
|
||||
hasCancelButton: false,
|
||||
description: (
|
||||
<div>
|
||||
<p>{ _t("End-to-end encryption is in beta and may not be reliable") }.</p>
|
||||
<p>{ _t("You should not yet trust it to secure data") }.</p>
|
||||
<p>{ _t("Devices will not yet be able to decrypt history from before they joined the room") }.</p>
|
||||
<p>{ _t("Encrypted messages will not be visible on clients that do not yet implement encryption") }.</p>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
}
|
||||
if (localStorage) {
|
||||
localStorage.setItem('mx_user_has_used_encryption', true);
|
||||
}
|
||||
},
|
||||
|
||||
_calculatePeekRules: function(room) {
|
||||
const guestAccessEvent = room.currentState.getStateEvents("m.room.guest_access", "");
|
||||
if (guestAccessEvent && guestAccessEvent.getContent().guest_access === "can_join") {
|
||||
|
@ -733,6 +706,23 @@ module.exports = React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
onDeviceVerificationChanged: function(userId, device) {
|
||||
const room = this.state.room;
|
||||
if (!room.currentState.getMember(userId)) {
|
||||
return;
|
||||
}
|
||||
this._updateE2EStatus(room);
|
||||
},
|
||||
|
||||
_updateE2EStatus: function(room) {
|
||||
if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) {
|
||||
return;
|
||||
}
|
||||
room.hasUnverifiedDevices().then((hasUnverifiedDevices) => {
|
||||
this.setState({e2eStatus: hasUnverifiedDevices ? "warning" : "verified"});
|
||||
});
|
||||
},
|
||||
|
||||
updateTint: function() {
|
||||
const room = this.state.room;
|
||||
if (!room) return;
|
||||
|
@ -1122,7 +1112,7 @@ module.exports = React.createClass({
|
|||
// favour longer (more specific) terms first
|
||||
highlights = highlights.sort(function(a, b) {
|
||||
return b.length - a.length;
|
||||
});
|
||||
});
|
||||
|
||||
self.setState({
|
||||
searchHighlights: highlights,
|
||||
|
@ -1236,50 +1226,9 @@ module.exports = React.createClass({
|
|||
dis.dispatch({ action: 'open_room_settings' });
|
||||
},
|
||||
|
||||
onSettingsSaveClick: function() {
|
||||
if (!this.refs.room_settings) return;
|
||||
|
||||
this.setState({
|
||||
uploadingRoomSettings: true,
|
||||
});
|
||||
|
||||
const newName = this.refs.header.getEditedName();
|
||||
if (newName !== undefined) {
|
||||
this.refs.room_settings.setName(newName);
|
||||
}
|
||||
const newTopic = this.refs.header.getEditedTopic();
|
||||
if (newTopic !== undefined) {
|
||||
this.refs.room_settings.setTopic(newTopic);
|
||||
}
|
||||
|
||||
this.refs.room_settings.save().then((results) => {
|
||||
const fails = results.filter(function(result) { return result.state !== "fulfilled"; });
|
||||
console.log("Settings saved with %s errors", fails.length);
|
||||
if (fails.length) {
|
||||
fails.forEach(function(result) {
|
||||
console.error(result.reason);
|
||||
});
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Failed to save room settings', '', ErrorDialog, {
|
||||
title: _t("Failed to save settings"),
|
||||
description: fails.map(function(result) { return result.reason; }).join("\n"),
|
||||
});
|
||||
// still editing room settings
|
||||
} else {
|
||||
dis.dispatch({ action: 'close_settings' });
|
||||
}
|
||||
}).finally(() => {
|
||||
this.setState({
|
||||
uploadingRoomSettings: false,
|
||||
});
|
||||
dis.dispatch({ action: 'close_settings' });
|
||||
}).done();
|
||||
},
|
||||
|
||||
onCancelClick: function() {
|
||||
console.log("updateTint from onCancelClick");
|
||||
this.updateTint();
|
||||
dis.dispatch({ action: 'close_settings' });
|
||||
if (this.state.forwardingEvent) {
|
||||
dis.dispatch({
|
||||
action: 'forward_event',
|
||||
|
@ -1437,7 +1386,7 @@ module.exports = React.createClass({
|
|||
(83 + // height of RoomHeader
|
||||
36 + // height of the status area
|
||||
72 + // minimum height of the message compmoser
|
||||
(this.state.editingRoomSettings ? (window.innerHeight * 0.3) : 120)); // amount of desired scrollback
|
||||
120); // amount of desired scrollback
|
||||
|
||||
// XXX: this is a bit of a hack and might possibly cause the video to push out the page anyway
|
||||
// but it's better than the video going missing entirely
|
||||
|
@ -1537,7 +1486,6 @@ module.exports = React.createClass({
|
|||
const RoomHeader = sdk.getComponent('rooms.RoomHeader');
|
||||
const MessageComposer = sdk.getComponent('rooms.MessageComposer');
|
||||
const ForwardMessage = sdk.getComponent("rooms.ForwardMessage");
|
||||
const RoomSettings = sdk.getComponent("rooms.RoomSettings");
|
||||
const AuxPanel = sdk.getComponent("rooms.AuxPanel");
|
||||
const SearchBar = sdk.getComponent("rooms.SearchBar");
|
||||
const PinnedEventsPanel = sdk.getComponent("rooms.PinnedEventsPanel");
|
||||
|
@ -1575,6 +1523,7 @@ module.exports = React.createClass({
|
|||
room={this.state.room}
|
||||
oobData={this.props.oobData}
|
||||
collapsedRhs={this.props.collapsedRhs}
|
||||
e2eStatus={this.state.e2eStatus}
|
||||
/>
|
||||
<div className="mx_RoomView_body">
|
||||
<div className="mx_RoomView_auxPanel">
|
||||
|
@ -1622,6 +1571,7 @@ module.exports = React.createClass({
|
|||
ref="header"
|
||||
room={this.state.room}
|
||||
collapsedRhs={this.props.collapsedRhs}
|
||||
e2eStatus={this.state.e2eStatus}
|
||||
/>
|
||||
<div className="mx_RoomView_body">
|
||||
<div className="mx_RoomView_auxPanel">
|
||||
|
@ -1685,7 +1635,6 @@ module.exports = React.createClass({
|
|||
);
|
||||
|
||||
const showRoomRecoveryReminder = (
|
||||
SettingsStore.isFeatureEnabled("feature_keybackup") &&
|
||||
SettingsStore.getValue("showRoomRecoveryReminder") &&
|
||||
MatrixClientPeg.get().isRoomEncrypted(this.state.room.roomId) &&
|
||||
!MatrixClientPeg.get().getKeyBackupEnabled()
|
||||
|
@ -1693,11 +1642,7 @@ module.exports = React.createClass({
|
|||
|
||||
let aux = null;
|
||||
let hideCancel = false;
|
||||
if (this.state.editingRoomSettings) {
|
||||
aux = <RoomSettings ref="room_settings" onSaveClick={this.onSettingsSaveClick} onCancelClick={this.onCancelClick} room={this.state.room} />;
|
||||
} else if (this.state.uploadingRoomSettings) {
|
||||
aux = <Loader />;
|
||||
} else if (this.state.forwardingEvent !== null) {
|
||||
if (this.state.forwardingEvent !== null) {
|
||||
aux = <ForwardMessage onCancelClick={this.onCancelClick} />;
|
||||
} else if (this.state.searching) {
|
||||
hideCancel = true; // has own cancel
|
||||
|
@ -1739,7 +1684,7 @@ module.exports = React.createClass({
|
|||
|
||||
const auxPanel = (
|
||||
<AuxPanel ref="auxPanel" room={this.state.room}
|
||||
fullHeight={this.state.editingRoomSettings}
|
||||
fullHeight={false}
|
||||
userId={MatrixClientPeg.get().credentials.userId}
|
||||
conferenceHandler={this.props.ConferenceHandler}
|
||||
draggingFile={this.state.draggingFile}
|
||||
|
@ -1747,7 +1692,7 @@ module.exports = React.createClass({
|
|||
maxHeight={this.state.auxPanelMaxHeight}
|
||||
onResize={this.onChildResize}
|
||||
showApps={this.state.showApps}
|
||||
hideAppsDrawer={this.state.editingRoomSettings} >
|
||||
hideAppsDrawer={false} >
|
||||
{ aux }
|
||||
</AuxPanel>
|
||||
);
|
||||
|
@ -1767,6 +1712,7 @@ module.exports = React.createClass({
|
|||
disabled={this.props.disabled}
|
||||
showApps={this.state.showApps}
|
||||
uploadAllowed={this.isFileUploadAllowed}
|
||||
e2eStatus={this.state.e2eStatus}
|
||||
/>;
|
||||
}
|
||||
|
||||
|
@ -1906,17 +1852,15 @@ module.exports = React.createClass({
|
|||
<main className={"mx_RoomView" + (inCall ? " mx_RoomView_inCall" : "")} ref="roomView">
|
||||
<RoomHeader ref="header" room={this.state.room} searchInfo={searchInfo}
|
||||
oobData={this.props.oobData}
|
||||
editing={this.state.editingRoomSettings}
|
||||
saving={this.state.uploadingRoomSettings}
|
||||
inRoom={myMembership === 'join'}
|
||||
collapsedRhs={this.props.collapsedRhs}
|
||||
onSearchClick={this.onSearchClick}
|
||||
onSettingsClick={this.onSettingsClick}
|
||||
onPinnedClick={this.onPinnedClick}
|
||||
onSaveClick={this.onSettingsSaveClick}
|
||||
onCancelClick={(aux && !hideCancel) ? this.onCancelClick : null}
|
||||
onForgetClick={(myMembership === "leave") ? this.onForgetClick : null}
|
||||
onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null}
|
||||
e2eStatus={this.state.e2eStatus}
|
||||
/>
|
||||
<MainSplit panel={rightPanel} collapsedRhs={this.props.collapsedRhs}>
|
||||
<div className={fadableSectionClasses}>
|
||||
|
|
|
@ -169,6 +169,10 @@ module.exports = React.createClass({
|
|||
//
|
||||
// This will also re-check the fill state, in case the paginate was inadequate
|
||||
this.checkScroll();
|
||||
|
||||
if (!this.isAtBottom()) {
|
||||
this.clearBlockShrinking();
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
|
|
|
@ -71,7 +71,7 @@ module.exports = React.createClass({
|
|||
function() {
|
||||
this.props.onSearch(this.refs.search.value);
|
||||
},
|
||||
100,
|
||||
500,
|
||||
),
|
||||
|
||||
_onKeyDown: function(ev) {
|
||||
|
@ -95,8 +95,13 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
render: function() {
|
||||
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
||||
|
||||
// check for collapsed here and
|
||||
// not at parent so we keep
|
||||
// searchTerm in our state
|
||||
// when collapsing and expanding
|
||||
if (this.props.collapsed) {
|
||||
return null;
|
||||
}
|
||||
const clearButton = this.state.searchTerm.length > 0 ?
|
||||
(<AccessibleButton key="button"
|
||||
className="mx_SearchBox_closeButton"
|
||||
|
|
|
@ -74,7 +74,6 @@ export class TabbedView extends React.Component {
|
|||
|
||||
const idx = this.props.tabs.indexOf(tab);
|
||||
if (idx === this._getActiveTabIndex()) classes += "mx_TabbedView_tabLabel_active";
|
||||
if (tab.label === "Visit old settings") classes += "mx_TabbedView_tabLabel_TEMP_HACK";
|
||||
|
||||
let tabIcon = null;
|
||||
if (tab.icon) {
|
||||
|
@ -97,7 +96,9 @@ export class TabbedView extends React.Component {
|
|||
_renderTabPanel(tab) {
|
||||
return (
|
||||
<div className="mx_TabbedView_tabPanel" key={"mx_tabpanel_" + tab.label}>
|
||||
{tab.body}
|
||||
<div className='mx_TabbedView_tabPanelContent'>
|
||||
{tab.body}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ import GroupActions from '../../actions/GroupActions';
|
|||
|
||||
import sdk from '../../index';
|
||||
import dis from '../../dispatcher';
|
||||
import Modal from '../../Modal';
|
||||
import { _t } from '../../languageHandler';
|
||||
|
||||
import { Droppable } from 'react-beautiful-dnd';
|
||||
|
@ -48,8 +47,6 @@ const TagPanel = React.createClass({
|
|||
this.context.matrixClient.on("Group.myMembership", this._onGroupMyMembership);
|
||||
this.context.matrixClient.on("sync", this._onClientSync);
|
||||
|
||||
this._dispatcherRef = dis.register(this._onAction);
|
||||
|
||||
this._tagOrderStoreToken = TagOrderStore.addListener(() => {
|
||||
if (this.unmounted) {
|
||||
return;
|
||||
|
@ -70,9 +67,6 @@ const TagPanel = React.createClass({
|
|||
if (this._filterStoreToken) {
|
||||
this._filterStoreToken.remove();
|
||||
}
|
||||
if (this._dispatcherRef) {
|
||||
dis.unregister(this._dispatcherRef);
|
||||
}
|
||||
},
|
||||
|
||||
_onGroupMyMembership() {
|
||||
|
@ -106,21 +100,11 @@ const TagPanel = React.createClass({
|
|||
dis.dispatch({action: 'deselect_tags'});
|
||||
},
|
||||
|
||||
_onAction(payload) {
|
||||
if (payload.action === "show_redesign_feedback_dialog") {
|
||||
const RedesignFeedbackDialog =
|
||||
sdk.getComponent("views.dialogs.RedesignFeedbackDialog");
|
||||
Modal.createDialog(RedesignFeedbackDialog);
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
const GroupsButton = sdk.getComponent('elements.GroupsButton');
|
||||
const DNDTagTile = sdk.getComponent('elements.DNDTagTile');
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
||||
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
||||
const ActionButton = sdk.getComponent("elements.ActionButton");
|
||||
|
||||
const tags = this.state.orderedTags.map((tag, index) => {
|
||||
return <DNDTagTile
|
||||
|
@ -174,13 +158,6 @@ const TagPanel = React.createClass({
|
|||
) }
|
||||
</Droppable>
|
||||
</GeminiScrollbarWrapper>
|
||||
<div className="mx_TagPanel_divider" />
|
||||
<div className="mx_TagPanel_groupsButton">
|
||||
<GroupsButton />
|
||||
<ActionButton
|
||||
className="mx_TagPanel_report" action="show_redesign_feedback_dialog"
|
||||
label={_t("Report bugs & give feedback")} tooltip={true} />
|
||||
</div>
|
||||
</div>;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import sdk from '../../index';
|
||||
import dis from '../../dispatcher';
|
||||
import Modal from '../../Modal';
|
||||
import { _t } from '../../languageHandler';
|
||||
|
||||
const TagPanelButtons = React.createClass({
|
||||
displayName: 'TagPanelButtons',
|
||||
|
||||
|
||||
componentWillMount: function() {
|
||||
this._dispatcherRef = dis.register(this._onAction);
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this._dispatcherRef) {
|
||||
dis.unregister(this._dispatcherRef);
|
||||
this._dispatcherRef = null;
|
||||
}
|
||||
},
|
||||
|
||||
_onAction(payload) {
|
||||
if (payload.action === "show_redesign_feedback_dialog") {
|
||||
const RedesignFeedbackDialog =
|
||||
sdk.getComponent("views.dialogs.RedesignFeedbackDialog");
|
||||
Modal.createDialog(RedesignFeedbackDialog);
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
const GroupsButton = sdk.getComponent('elements.GroupsButton');
|
||||
const ActionButton = sdk.getComponent("elements.ActionButton");
|
||||
|
||||
return (<div className="mx_TagPanelButtons">
|
||||
<GroupsButton />
|
||||
<ActionButton
|
||||
className="mx_TagPanelButtons_report" action="show_redesign_feedback_dialog"
|
||||
label={_t("Report bugs & give feedback")} tooltip={true} />
|
||||
</div>);
|
||||
},
|
||||
});
|
||||
export default TagPanelButtons;
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017, 2018 New Vector Ltd
|
||||
Copyright 2017, 2018, 2019 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -25,6 +25,18 @@ import SdkConfig from "../../../SdkConfig";
|
|||
|
||||
import PasswordReset from "../../../PasswordReset";
|
||||
|
||||
// Phases
|
||||
// Show controls to configure server details
|
||||
const PHASE_SERVER_DETAILS = 0;
|
||||
// Show the forgot password inputs
|
||||
const PHASE_FORGOT = 1;
|
||||
// Email is in the process of being sent
|
||||
const PHASE_SENDING_EMAIL = 2;
|
||||
// Email has been sent
|
||||
const PHASE_EMAIL_SENT = 3;
|
||||
// User has clicked the link in email and completed reset
|
||||
const PHASE_DONE = 4;
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'ForgotPassword',
|
||||
|
||||
|
@ -47,28 +59,29 @@ module.exports = React.createClass({
|
|||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
enteredHomeserverUrl: this.props.customHsUrl || this.props.defaultHsUrl,
|
||||
enteredIdentityServerUrl: this.props.customIsUrl || this.props.defaultIsUrl,
|
||||
progress: null,
|
||||
password: null,
|
||||
password2: null,
|
||||
enteredHsUrl: this.props.customHsUrl || this.props.defaultHsUrl,
|
||||
enteredIsUrl: this.props.customIsUrl || this.props.defaultIsUrl,
|
||||
phase: PHASE_FORGOT,
|
||||
email: "",
|
||||
password: "",
|
||||
password2: "",
|
||||
errorText: null,
|
||||
};
|
||||
},
|
||||
|
||||
submitPasswordReset: function(hsUrl, identityUrl, email, password) {
|
||||
this.setState({
|
||||
progress: "sending_email",
|
||||
phase: PHASE_SENDING_EMAIL,
|
||||
});
|
||||
this.reset = new PasswordReset(hsUrl, identityUrl);
|
||||
this.reset.resetPassword(email, password).done(() => {
|
||||
this.setState({
|
||||
progress: "sent_email",
|
||||
phase: PHASE_EMAIL_SENT,
|
||||
});
|
||||
}, (err) => {
|
||||
this.showErrorDialog(_t('Failed to send email') + ": " + err.message);
|
||||
this.setState({
|
||||
progress: null,
|
||||
phase: PHASE_FORGOT,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@ -80,7 +93,7 @@ module.exports = React.createClass({
|
|||
return;
|
||||
}
|
||||
this.reset.checkEmailLinkClicked().done((res) => {
|
||||
this.setState({ progress: "complete" });
|
||||
this.setState({ phase: PHASE_DONE });
|
||||
}, (err) => {
|
||||
this.showErrorDialog(err.message);
|
||||
});
|
||||
|
@ -126,7 +139,7 @@ module.exports = React.createClass({
|
|||
onFinished: (confirmed) => {
|
||||
if (confirmed) {
|
||||
this.submitPasswordReset(
|
||||
this.state.enteredHomeserverUrl, this.state.enteredIdentityServerUrl,
|
||||
this.state.enteredHsUrl, this.state.enteredIsUrl,
|
||||
this.state.email, this.state.password,
|
||||
);
|
||||
}
|
||||
|
@ -153,14 +166,29 @@ module.exports = React.createClass({
|
|||
onServerConfigChange: function(config) {
|
||||
const newState = {};
|
||||
if (config.hsUrl !== undefined) {
|
||||
newState.enteredHomeserverUrl = config.hsUrl;
|
||||
newState.enteredHsUrl = config.hsUrl;
|
||||
}
|
||||
if (config.isUrl !== undefined) {
|
||||
newState.enteredIdentityServerUrl = config.isUrl;
|
||||
newState.enteredIsUrl = config.isUrl;
|
||||
}
|
||||
this.setState(newState);
|
||||
},
|
||||
|
||||
onServerDetailsNextPhaseClick(ev) {
|
||||
ev.stopPropagation();
|
||||
this.setState({
|
||||
phase: PHASE_FORGOT,
|
||||
});
|
||||
},
|
||||
|
||||
onEditServerDetailsClick(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.setState({
|
||||
phase: PHASE_SERVER_DETAILS,
|
||||
});
|
||||
},
|
||||
|
||||
onLoginClick: function(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
@ -175,95 +203,152 @@ module.exports = React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
renderServerDetails() {
|
||||
const ServerConfig = sdk.getComponent("auth.ServerConfig");
|
||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
||||
|
||||
if (SdkConfig.get()['disable_custom_urls']) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <div>
|
||||
<ServerConfig
|
||||
defaultHsUrl={this.props.defaultHsUrl}
|
||||
defaultIsUrl={this.props.defaultIsUrl}
|
||||
customHsUrl={this.state.enteredHsUrl}
|
||||
customIsUrl={this.state.enteredIsUrl}
|
||||
onServerConfigChange={this.onServerConfigChange}
|
||||
delayTimeMs={0} />
|
||||
<AccessibleButton className="mx_Login_submit"
|
||||
onClick={this.onServerDetailsNextPhaseClick}
|
||||
>
|
||||
{_t("Next")}
|
||||
</AccessibleButton>
|
||||
</div>;
|
||||
},
|
||||
|
||||
renderForgot() {
|
||||
let errorText = null;
|
||||
const err = this.state.errorText || this.props.defaultServerDiscoveryError;
|
||||
if (err) {
|
||||
errorText = <div className="mx_Login_error">{ err }</div>;
|
||||
}
|
||||
|
||||
let yourMatrixAccountText = _t('Your account');
|
||||
try {
|
||||
const parsedHsUrl = new URL(this.state.enteredHsUrl);
|
||||
yourMatrixAccountText = _t('Your account on %(serverName)s', {
|
||||
serverName: parsedHsUrl.hostname,
|
||||
});
|
||||
} catch (e) {
|
||||
errorText = <div className="mx_Login_error">{_t(
|
||||
"The homeserver URL %(hsUrl)s doesn't seem to be valid URL. Please " +
|
||||
"enter a valid URL including the protocol prefix.",
|
||||
{
|
||||
hsUrl: this.state.enteredHsUrl,
|
||||
})}</div>;
|
||||
}
|
||||
|
||||
// If custom URLs are allowed, wire up the server details edit link.
|
||||
let editLink = null;
|
||||
if (!SdkConfig.get()['disable_custom_urls']) {
|
||||
editLink = <a className="mx_AuthBody_editServerDetails"
|
||||
href="#" onClick={this.onEditServerDetailsClick}
|
||||
>
|
||||
{_t('Change')}
|
||||
</a>;
|
||||
}
|
||||
|
||||
return <div>
|
||||
<h3>
|
||||
{yourMatrixAccountText}
|
||||
{editLink}
|
||||
</h3>
|
||||
{errorText}
|
||||
<form onSubmit={this.onSubmitForm}>
|
||||
<div className="mx_AuthBody_fieldRow">
|
||||
<input className="mx_Login_field" type="text"
|
||||
name="reset_email" // define a name so browser's password autofill gets less confused
|
||||
value={this.state.email}
|
||||
onChange={this.onInputChanged.bind(this, "email")}
|
||||
placeholder={_t('Email')} autoFocus />
|
||||
</div>
|
||||
<div className="mx_AuthBody_fieldRow">
|
||||
<input className="mx_Login_field" type="password"
|
||||
name="reset_password"
|
||||
value={this.state.password}
|
||||
onChange={this.onInputChanged.bind(this, "password")}
|
||||
placeholder={_t('Password')} />
|
||||
<input className="mx_Login_field" type="password"
|
||||
name="reset_password_confirm"
|
||||
value={this.state.password2}
|
||||
onChange={this.onInputChanged.bind(this, "password2")}
|
||||
placeholder={_t('Confirm')} />
|
||||
</div>
|
||||
<span>{_t(
|
||||
'A verification email will be sent to your inbox to confirm ' +
|
||||
'setting your new password.',
|
||||
)}</span>
|
||||
<input className="mx_Login_submit" type="submit" value={_t('Send Reset Email')} />
|
||||
</form>
|
||||
<a className="mx_AuthBody_changeFlow" onClick={this.onLoginClick} href="#">
|
||||
{_t('Sign in instead')}
|
||||
</a>
|
||||
</div>;
|
||||
},
|
||||
|
||||
renderSendingEmail() {
|
||||
const Spinner = sdk.getComponent("elements.Spinner");
|
||||
return <Spinner />;
|
||||
},
|
||||
|
||||
renderEmailSent() {
|
||||
return <div>
|
||||
{_t("An email has been sent to %(emailAddress)s. Once you've followed the " +
|
||||
"link it contains, click below.", { emailAddress: this.state.email })}
|
||||
<br />
|
||||
<input className="mx_Login_submit" type="button" onClick={this.onVerify}
|
||||
value={_t('I have verified my email address')} />
|
||||
</div>;
|
||||
},
|
||||
|
||||
renderDone() {
|
||||
return <div>
|
||||
<p>{_t("Your password has been reset.")}</p>
|
||||
<p>{_t(
|
||||
"You have been logged out of all devices and will no longer receive " +
|
||||
"push notifications. To re-enable notifications, sign in again on each " +
|
||||
"device.",
|
||||
)}</p>
|
||||
<input className="mx_Login_submit" type="button" onClick={this.props.onComplete}
|
||||
value={_t('Return to login screen')} />
|
||||
</div>;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const AuthPage = sdk.getComponent("auth.AuthPage");
|
||||
const AuthHeader = sdk.getComponent("auth.AuthHeader");
|
||||
const AuthBody = sdk.getComponent("auth.AuthBody");
|
||||
const ServerConfig = sdk.getComponent("auth.ServerConfig");
|
||||
const Spinner = sdk.getComponent("elements.Spinner");
|
||||
|
||||
let resetPasswordJsx;
|
||||
|
||||
if (this.state.progress === "sending_email") {
|
||||
resetPasswordJsx = <Spinner />;
|
||||
} else if (this.state.progress === "sent_email") {
|
||||
resetPasswordJsx = (
|
||||
<div>
|
||||
{ _t("An email has been sent to %(emailAddress)s. Once you've followed the link it contains, " +
|
||||
"click below.", { emailAddress: this.state.email }) }
|
||||
<br />
|
||||
<input className="mx_Login_submit" type="button" onClick={this.onVerify}
|
||||
value={_t('I have verified my email address')} />
|
||||
</div>
|
||||
);
|
||||
} else if (this.state.progress === "complete") {
|
||||
resetPasswordJsx = (
|
||||
<div>
|
||||
<p>{ _t('Your password has been reset') }.</p>
|
||||
<p>{ _t('You have been logged out of all devices and will no longer receive push notifications. ' +
|
||||
'To re-enable notifications, sign in again on each device') }.</p>
|
||||
<input className="mx_Login_submit" type="button" onClick={this.props.onComplete}
|
||||
value={_t('Return to login screen')} />
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
let serverConfigSection;
|
||||
if (!SdkConfig.get()['disable_custom_urls']) {
|
||||
serverConfigSection = (
|
||||
<ServerConfig ref="serverConfig"
|
||||
defaultHsUrl={this.props.defaultHsUrl}
|
||||
defaultIsUrl={this.props.defaultIsUrl}
|
||||
customHsUrl={this.props.customHsUrl}
|
||||
customIsUrl={this.props.customIsUrl}
|
||||
onServerConfigChange={this.onServerConfigChange}
|
||||
delayTimeMs={0} />
|
||||
);
|
||||
}
|
||||
|
||||
let errorText = null;
|
||||
const err = this.state.errorText || this.props.defaultServerDiscoveryError;
|
||||
if (err) {
|
||||
errorText = <div className="mx_Login_error">{ err }</div>;
|
||||
}
|
||||
|
||||
resetPasswordJsx = (
|
||||
<div>
|
||||
<p>
|
||||
{ _t('To reset your password, enter the email address linked to your account') }:
|
||||
</p>
|
||||
<div>
|
||||
<form onSubmit={this.onSubmitForm}>
|
||||
<input className="mx_Login_field" ref="user" type="text"
|
||||
name="reset_email" // define a name so browser's password autofill gets less confused
|
||||
value={this.state.email}
|
||||
onChange={this.onInputChanged.bind(this, "email")}
|
||||
placeholder={_t('Email address')} autoFocus />
|
||||
<br />
|
||||
<input className="mx_Login_field" ref="pass" type="password"
|
||||
name="reset_password"
|
||||
value={this.state.password}
|
||||
onChange={this.onInputChanged.bind(this, "password")}
|
||||
placeholder={_t('New password')} />
|
||||
<br />
|
||||
<input className="mx_Login_field" ref="pass" type="password"
|
||||
name="reset_password_confirm"
|
||||
value={this.state.password2}
|
||||
onChange={this.onInputChanged.bind(this, "password2")}
|
||||
placeholder={_t('Confirm your new password')} />
|
||||
<br />
|
||||
<input className="mx_Login_submit" type="submit" value={_t('Send Reset Email')} />
|
||||
</form>
|
||||
{ serverConfigSection }
|
||||
{ errorText }
|
||||
<a className="mx_AuthBody_changeFlow" onClick={this.onLoginClick} href="#">
|
||||
{ _t('Sign in instead') }
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
switch (this.state.phase) {
|
||||
case PHASE_SERVER_DETAILS:
|
||||
resetPasswordJsx = this.renderServerDetails();
|
||||
break;
|
||||
case PHASE_FORGOT:
|
||||
resetPasswordJsx = this.renderForgot();
|
||||
break;
|
||||
case PHASE_SENDING_EMAIL:
|
||||
resetPasswordJsx = this.renderSendingEmail();
|
||||
break;
|
||||
case PHASE_EMAIL_SENT:
|
||||
resetPasswordJsx = this.renderEmailSent();
|
||||
break;
|
||||
case PHASE_DONE:
|
||||
resetPasswordJsx = this.renderDone();
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<AuthPage>
|
||||
<AuthHeader />
|
||||
|
|
|
@ -26,7 +26,6 @@ import Login from '../../../Login';
|
|||
import SdkConfig from '../../../SdkConfig';
|
||||
import { messageForResourceLimitError } from '../../../utils/ErrorUtils';
|
||||
import { AutoDiscovery } from "matrix-js-sdk";
|
||||
import * as ServerType from '../../views/auth/ServerTypeSelector';
|
||||
|
||||
// For validating phone numbers without country codes
|
||||
const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/;
|
||||
|
@ -37,8 +36,8 @@ const PHASE_SERVER_DETAILS = 0;
|
|||
// Show the appropriate login flow(s) for the server
|
||||
const PHASE_LOGIN = 1;
|
||||
|
||||
// Disable phases for now, pending UX discussion on WK discovery
|
||||
const PHASES_ENABLED = false;
|
||||
// Enable phases for login
|
||||
const PHASES_ENABLED = true;
|
||||
|
||||
// These are used in several places, and come from the js-sdk's autodiscovery
|
||||
// stuff. We define them here so that they'll be picked up by i18n.
|
||||
|
@ -63,7 +62,7 @@ module.exports = React.createClass({
|
|||
defaultIsUrl: PropTypes.string,
|
||||
// Secondary HS which we try to log into if the user is using
|
||||
// the default HS but login fails. Useful for migrating to a
|
||||
// different home server without confusing users.
|
||||
// different homeserver without confusing users.
|
||||
fallbackHsUrl: PropTypes.string,
|
||||
|
||||
// An error passed along from higher up explaining that something
|
||||
|
@ -77,7 +76,6 @@ module.exports = React.createClass({
|
|||
|
||||
// login shouldn't care how password recovery is done.
|
||||
onForgotPasswordClick: PropTypes.func,
|
||||
onCancelClick: PropTypes.func,
|
||||
onServerConfigChange: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
|
@ -87,9 +85,8 @@ module.exports = React.createClass({
|
|||
errorText: null,
|
||||
loginIncorrect: false,
|
||||
|
||||
serverType: null,
|
||||
enteredHomeserverUrl: this.props.customHsUrl || this.props.defaultHsUrl,
|
||||
enteredIdentityServerUrl: this.props.customIsUrl || this.props.defaultIsUrl,
|
||||
enteredHsUrl: this.props.customHsUrl || this.props.defaultHsUrl,
|
||||
enteredIsUrl: this.props.customIsUrl || this.props.defaultIsUrl,
|
||||
|
||||
// used for preserving form values when changing homeserver
|
||||
username: "",
|
||||
|
@ -97,13 +94,11 @@ module.exports = React.createClass({
|
|||
phoneNumber: "",
|
||||
|
||||
// Phase of the overall login dialog.
|
||||
phase: PHASE_SERVER_DETAILS,
|
||||
phase: PHASE_LOGIN,
|
||||
// The current login flow, such as password, SSO, etc.
|
||||
currentFlow: "m.login.password",
|
||||
|
||||
// .well-known discovery
|
||||
discoveredHsUrl: "",
|
||||
discoveredIsUrl: "",
|
||||
discoveryError: "",
|
||||
findingHomeserver: false,
|
||||
};
|
||||
|
@ -160,7 +155,7 @@ module.exports = React.createClass({
|
|||
// Some error strings only apply for logging in
|
||||
const usingEmail = username.indexOf("@") > 0;
|
||||
if (error.httpStatus === 400 && usingEmail) {
|
||||
errorText = _t('This Home Server does not support login using email address.');
|
||||
errorText = _t('This homeserver does not support login using email address.');
|
||||
} else if (error.errcode == 'M_RESOURCE_LIMIT_EXCEEDED') {
|
||||
const errorTop = messageForResourceLimitError(
|
||||
error.data.limit_type,
|
||||
|
@ -241,7 +236,7 @@ module.exports = React.createClass({
|
|||
}, function(error) {
|
||||
let errorText;
|
||||
if (error.httpStatus === 403) {
|
||||
errorText = _t("Guest access is disabled on this Home Server.");
|
||||
errorText = _t("Guest access is disabled on this homeserver.");
|
||||
} else {
|
||||
errorText = self._errorTextFromError(error);
|
||||
}
|
||||
|
@ -308,10 +303,10 @@ module.exports = React.createClass({
|
|||
errorText: null, // reset err messages
|
||||
};
|
||||
if (config.hsUrl !== undefined) {
|
||||
newState.enteredHomeserverUrl = config.hsUrl;
|
||||
newState.enteredHsUrl = config.hsUrl;
|
||||
}
|
||||
if (config.isUrl !== undefined) {
|
||||
newState.enteredIdentityServerUrl = config.isUrl;
|
||||
newState.enteredIsUrl = config.isUrl;
|
||||
}
|
||||
|
||||
this.props.onServerConfigChange(config);
|
||||
|
@ -320,39 +315,6 @@ module.exports = React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
onServerTypeChange(type) {
|
||||
this.setState({
|
||||
serverType: type,
|
||||
});
|
||||
|
||||
// When changing server types, set the HS / IS URLs to reasonable defaults for the
|
||||
// the new type.
|
||||
switch (type) {
|
||||
case ServerType.FREE: {
|
||||
const { hsUrl, isUrl } = ServerType.TYPES.FREE;
|
||||
this.onServerConfigChange({
|
||||
hsUrl,
|
||||
isUrl,
|
||||
});
|
||||
// Move directly to the login phase since the server details are fixed.
|
||||
this.setState({
|
||||
phase: PHASE_LOGIN,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ServerType.PREMIUM:
|
||||
case ServerType.ADVANCED:
|
||||
this.onServerConfigChange({
|
||||
hsUrl: this.props.defaultHsUrl,
|
||||
isUrl: this.props.defaultIsUrl,
|
||||
});
|
||||
this.setState({
|
||||
phase: PHASE_SERVER_DETAILS,
|
||||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
onRegisterClick: function(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
@ -377,43 +339,42 @@ module.exports = React.createClass({
|
|||
_tryWellKnownDiscovery: async function(serverName) {
|
||||
if (!serverName.trim()) {
|
||||
// Nothing to discover
|
||||
this.setState({discoveryError: "", discoveredHsUrl: "", discoveredIsUrl: "", findingHomeserver: false});
|
||||
this.setState({
|
||||
discoveryError: "",
|
||||
findingHomeserver: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({findingHomeserver: true});
|
||||
try {
|
||||
const discovery = await AutoDiscovery.findClientConfig(serverName);
|
||||
|
||||
const state = discovery["m.homeserver"].state;
|
||||
if (state !== AutoDiscovery.SUCCESS && state !== AutoDiscovery.PROMPT) {
|
||||
this.setState({
|
||||
discoveredHsUrl: "",
|
||||
discoveredIsUrl: "",
|
||||
discoveryError: discovery["m.homeserver"].error,
|
||||
findingHomeserver: false,
|
||||
});
|
||||
} else if (state === AutoDiscovery.PROMPT) {
|
||||
this.setState({
|
||||
discoveredHsUrl: "",
|
||||
discoveredIsUrl: "",
|
||||
discoveryError: "",
|
||||
findingHomeserver: false,
|
||||
});
|
||||
} else if (state === AutoDiscovery.SUCCESS) {
|
||||
this.setState({
|
||||
discoveredHsUrl: discovery["m.homeserver"].base_url,
|
||||
discoveredIsUrl:
|
||||
discovery["m.identity_server"].state === AutoDiscovery.SUCCESS
|
||||
? discovery["m.identity_server"].base_url
|
||||
: "",
|
||||
discoveryError: "",
|
||||
findingHomeserver: false,
|
||||
});
|
||||
this.onServerConfigChange({
|
||||
hsUrl: discovery["m.homeserver"].base_url,
|
||||
isUrl: discovery["m.identity_server"].state === AutoDiscovery.SUCCESS
|
||||
? discovery["m.identity_server"].base_url
|
||||
: "",
|
||||
});
|
||||
} else {
|
||||
console.warn("Unknown state for m.homeserver in discovery response: ", discovery);
|
||||
this.setState({
|
||||
discoveredHsUrl: "",
|
||||
discoveredIsUrl: "",
|
||||
discoveryError: _t("Unknown failure discovering homeserver"),
|
||||
findingHomeserver: false,
|
||||
});
|
||||
|
@ -429,8 +390,8 @@ module.exports = React.createClass({
|
|||
|
||||
_initLoginLogic: function(hsUrl, isUrl) {
|
||||
const self = this;
|
||||
hsUrl = hsUrl || this.state.enteredHomeserverUrl;
|
||||
isUrl = isUrl || this.state.enteredIdentityServerUrl;
|
||||
hsUrl = hsUrl || this.state.enteredHsUrl;
|
||||
isUrl = isUrl || this.state.enteredIsUrl;
|
||||
|
||||
const fallbackHsUrl = hsUrl === this.props.defaultHsUrl ? this.props.fallbackHsUrl : null;
|
||||
|
||||
|
@ -440,8 +401,8 @@ module.exports = React.createClass({
|
|||
this._loginLogic = loginLogic;
|
||||
|
||||
this.setState({
|
||||
enteredHomeserverUrl: hsUrl,
|
||||
enteredIdentityServerUrl: isUrl,
|
||||
enteredHsUrl: hsUrl,
|
||||
enteredIsUrl: isUrl,
|
||||
busy: true,
|
||||
loginIncorrect: false,
|
||||
});
|
||||
|
@ -507,8 +468,8 @@ module.exports = React.createClass({
|
|||
|
||||
if (err.cors === 'rejected') {
|
||||
if (window.location.protocol === 'https:' &&
|
||||
(this.state.enteredHomeserverUrl.startsWith("http:") ||
|
||||
!this.state.enteredHomeserverUrl.startsWith("http"))
|
||||
(this.state.enteredHsUrl.startsWith("http:") ||
|
||||
!this.state.enteredHsUrl.startsWith("http"))
|
||||
) {
|
||||
errorText = <span>
|
||||
{ _t("Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. " +
|
||||
|
@ -532,7 +493,7 @@ module.exports = React.createClass({
|
|||
{
|
||||
'a': (sub) => {
|
||||
return <a target="_blank" rel="noopener"
|
||||
href={this.state.enteredHomeserverUrl}
|
||||
href={this.state.enteredHsUrl}
|
||||
>{ sub }</a>;
|
||||
},
|
||||
},
|
||||
|
@ -544,52 +505,26 @@ module.exports = React.createClass({
|
|||
return errorText;
|
||||
},
|
||||
|
||||
renderServerComponentForStep() {
|
||||
const ServerTypeSelector = sdk.getComponent("auth.ServerTypeSelector");
|
||||
renderServerComponent() {
|
||||
const ServerConfig = sdk.getComponent("auth.ServerConfig");
|
||||
const ModularServerConfig = sdk.getComponent("auth.ModularServerConfig");
|
||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
||||
|
||||
// TODO: May need to adjust the behavior of this config option
|
||||
if (SdkConfig.get()['disable_custom_urls']) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return <div>
|
||||
<ServerTypeSelector
|
||||
defaultHsUrl={this.props.defaultHsUrl}
|
||||
onChange={this.onServerTypeChange}
|
||||
/>
|
||||
</div>;
|
||||
return null;
|
||||
}
|
||||
|
||||
let serverDetails = null;
|
||||
switch (this.state.serverType) {
|
||||
case ServerType.FREE:
|
||||
break;
|
||||
case ServerType.PREMIUM:
|
||||
serverDetails = <ModularServerConfig
|
||||
customHsUrl={this.state.discoveredHsUrl || this.props.customHsUrl}
|
||||
defaultHsUrl={this.props.defaultHsUrl}
|
||||
defaultIsUrl={this.props.defaultIsUrl}
|
||||
onServerConfigChange={this.onServerConfigChange}
|
||||
delayTimeMs={250}
|
||||
/>;
|
||||
break;
|
||||
case ServerType.ADVANCED:
|
||||
serverDetails = <ServerConfig
|
||||
customHsUrl={this.state.discoveredHsUrl || this.props.customHsUrl}
|
||||
customIsUrl={this.state.discoveredIsUrl || this.props.customIsUrl}
|
||||
defaultHsUrl={this.props.defaultHsUrl}
|
||||
defaultIsUrl={this.props.defaultIsUrl}
|
||||
onServerConfigChange={this.onServerConfigChange}
|
||||
delayTimeMs={250}
|
||||
/>;
|
||||
break;
|
||||
}
|
||||
const serverDetails = <ServerConfig
|
||||
customHsUrl={this.state.enteredHsUrl}
|
||||
customIsUrl={this.state.enteredIsUrl}
|
||||
defaultHsUrl={this.props.defaultHsUrl}
|
||||
defaultIsUrl={this.props.defaultIsUrl}
|
||||
onServerConfigChange={this.onServerConfigChange}
|
||||
delayTimeMs={250}
|
||||
/>;
|
||||
|
||||
let nextButton = null;
|
||||
if (PHASES_ENABLED) {
|
||||
|
@ -601,10 +536,6 @@ module.exports = React.createClass({
|
|||
}
|
||||
|
||||
return <div>
|
||||
<ServerTypeSelector
|
||||
defaultHsUrl={this.props.defaultHsUrl}
|
||||
onChange={this.onServerTypeChange}
|
||||
/>
|
||||
{serverDetails}
|
||||
{nextButton}
|
||||
</div>;
|
||||
|
@ -633,13 +564,8 @@ module.exports = React.createClass({
|
|||
_renderPasswordStep: function() {
|
||||
const PasswordLogin = sdk.getComponent('auth.PasswordLogin');
|
||||
let onEditServerDetailsClick = null;
|
||||
// If custom URLs are allowed and we haven't selected the Free server type, wire
|
||||
// up the server details edit link.
|
||||
if (
|
||||
PHASES_ENABLED &&
|
||||
!SdkConfig.get()['disable_custom_urls'] &&
|
||||
this.state.serverType !== ServerType.FREE
|
||||
) {
|
||||
// If custom URLs are allowed, wire up the server details edit link.
|
||||
if (PHASES_ENABLED && !SdkConfig.get()['disable_custom_urls']) {
|
||||
onEditServerDetailsClick = this.onEditServerDetailsClick;
|
||||
}
|
||||
return (
|
||||
|
@ -657,7 +583,7 @@ module.exports = React.createClass({
|
|||
onPhoneNumberBlur={this.onPhoneNumberBlur}
|
||||
onForgotPasswordClick={this.props.onForgotPasswordClick}
|
||||
loginIncorrect={this.state.loginIncorrect}
|
||||
hsUrl={this.state.enteredHomeserverUrl}
|
||||
hsUrl={this.state.enteredHsUrl}
|
||||
disableSubmit={this.state.findingHomeserver}
|
||||
/>
|
||||
);
|
||||
|
@ -708,11 +634,11 @@ module.exports = React.createClass({
|
|||
<AuthHeader />
|
||||
<AuthBody>
|
||||
<h2>
|
||||
{_t('Sign in to your account')}
|
||||
{_t('Sign in')}
|
||||
{loader}
|
||||
</h2>
|
||||
{ errorTextSection }
|
||||
{ this.renderServerComponentForStep() }
|
||||
{ this.renderServerComponent() }
|
||||
{ this.renderLoginComponentForStep() }
|
||||
<a className="mx_AuthBody_changeFlow" onClick={this.onRegisterClick} href="#">
|
||||
{ _t('Create account') }
|
||||
|
|
|
@ -54,21 +54,17 @@ module.exports = React.createClass({
|
|||
defaultIsUrl: PropTypes.string,
|
||||
brand: PropTypes.string,
|
||||
email: PropTypes.string,
|
||||
referrer: PropTypes.string,
|
||||
|
||||
// An error passed along from higher up explaining that something
|
||||
// went wrong when finding the defaultHsUrl.
|
||||
defaultServerDiscoveryError: PropTypes.string,
|
||||
|
||||
defaultDeviceDisplayName: PropTypes.string,
|
||||
|
||||
// registration shouldn't know or care how login is done.
|
||||
onLoginClick: PropTypes.func.isRequired,
|
||||
onCancelClick: PropTypes.func,
|
||||
onServerConfigChange: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
const customURLsAllowed = !SdkConfig.get()['disable_custom_urls'];
|
||||
|
||||
return {
|
||||
busy: false,
|
||||
errorText: null,
|
||||
|
@ -90,6 +86,8 @@ module.exports = React.createClass({
|
|||
serverType: null,
|
||||
hsUrl: this.props.customHsUrl,
|
||||
isUrl: this.props.customIsUrl,
|
||||
// Phase of the overall registration dialog.
|
||||
phase: customURLsAllowed ? PHASE_SERVER_DETAILS : PHASE_REGISTRATION,
|
||||
flows: null,
|
||||
};
|
||||
},
|
||||
|
@ -164,9 +162,13 @@ module.exports = React.createClass({
|
|||
this.setState({
|
||||
flows: e.data.flows,
|
||||
});
|
||||
} else if (e.httpStatus === 403 && e.errcode === "M_UNKNOWN") {
|
||||
this.setState({
|
||||
errorText: _t("Registration has been disabled on this homeserver."),
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
errorText: _t("Unable to query for supported registration methods"),
|
||||
errorText: _t("Unable to query for supported registration methods."),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -363,7 +365,6 @@ module.exports = React.createClass({
|
|||
const ModularServerConfig = sdk.getComponent("auth.ModularServerConfig");
|
||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
||||
|
||||
// TODO: May need to adjust the behavior of this config option
|
||||
if (SdkConfig.get()['disable_custom_urls']) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -141,7 +141,7 @@ module.exports = React.createClass({
|
|||
|
||||
return (
|
||||
<div ref="recaptchaContainer">
|
||||
{ _t("This Home Server would like to make sure you are not a robot") }
|
||||
{ _t("This homeserver would like to make sure you are not a robot.") }
|
||||
<br />
|
||||
<div id={DIV_ID}></div>
|
||||
{ error }
|
||||
|
|
|
@ -334,7 +334,7 @@ export const TermsAuthEntry = React.createClass({
|
|||
let submitButton;
|
||||
if (this.props.showContinue !== false) {
|
||||
// XXX: button classes
|
||||
submitButton = <button className="mx_InteractiveAuthEntryComponents_termsSubmit mx_UserSettings_button"
|
||||
submitButton = <button className="mx_InteractiveAuthEntryComponents_termsSubmit mx_GeneralButton"
|
||||
onClick={this._trySubmit} disabled={!allChecked}>{_t("Accept")}</button>;
|
||||
}
|
||||
|
||||
|
@ -525,7 +525,7 @@ export const MsisdnAuthEntry = React.createClass({
|
|||
const enableSubmit = Boolean(this.state.token);
|
||||
const submitClasses = classnames({
|
||||
mx_InteractiveAuthEntryComponents_msisdnSubmit: true,
|
||||
mx_UserSettings_button: true, // XXX button classes
|
||||
mx_GeneralButton: true,
|
||||
});
|
||||
let errorSection;
|
||||
if (this.state.errorText) {
|
||||
|
|
|
@ -249,10 +249,10 @@ class PasswordLogin extends React.Component {
|
|||
</span>;
|
||||
}
|
||||
|
||||
let yourMatrixAccountText = _t('Your account');
|
||||
let signInToText = _t('Sign in');
|
||||
try {
|
||||
const parsedHsUrl = new URL(this.props.hsUrl);
|
||||
yourMatrixAccountText = _t('Your %(serverName)s account', {
|
||||
signInToText = _t('Sign in to %(serverName)s', {
|
||||
serverName: parsedHsUrl.hostname,
|
||||
});
|
||||
} catch (e) {
|
||||
|
@ -264,7 +264,7 @@ class PasswordLogin extends React.Component {
|
|||
editLink = <a className="mx_AuthBody_editServerDetails"
|
||||
href="#" onClick={this.props.onEditServerDetailsClick}
|
||||
>
|
||||
{_t('Edit')}
|
||||
{_t('Change')}
|
||||
</a>;
|
||||
}
|
||||
|
||||
|
@ -297,7 +297,7 @@ class PasswordLogin extends React.Component {
|
|||
return (
|
||||
<div>
|
||||
<h3>
|
||||
{yourMatrixAccountText}
|
||||
{signInToText}
|
||||
{editLink}
|
||||
</h3>
|
||||
<form onSubmit={this.onSubmitForm}>
|
||||
|
|
|
@ -270,11 +270,27 @@ module.exports = React.createClass({
|
|||
this.validateField(FIELD_USERNAME, ev.type);
|
||||
},
|
||||
|
||||
/**
|
||||
* A step is required if all flows include that step.
|
||||
*
|
||||
* @param {string} step A stage name to check
|
||||
* @returns {boolean} Whether it is required
|
||||
*/
|
||||
_authStepIsRequired(step) {
|
||||
// A step is required if no flow exists which does not include that step
|
||||
// (Notwithstanding setups like either email or msisdn being required)
|
||||
return !this.props.flows.some((flow) => {
|
||||
return !flow.stages.includes(step);
|
||||
return this.props.flows.every((flow) => {
|
||||
return flow.stages.includes(step);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* A step is used if any flows include that step.
|
||||
*
|
||||
* @param {string} step A stage name to check
|
||||
* @returns {boolean} Whether it is used
|
||||
*/
|
||||
_authStepIsUsed(step) {
|
||||
return this.props.flows.some((flow) => {
|
||||
return flow.stages.includes(step);
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -298,24 +314,28 @@ module.exports = React.createClass({
|
|||
</a>;
|
||||
}
|
||||
|
||||
const emailPlaceholder = this._authStepIsRequired('m.login.email.identity') ?
|
||||
_t("Email") :
|
||||
_t("Email (optional)");
|
||||
let emailSection;
|
||||
if (this._authStepIsUsed('m.login.email.identity')) {
|
||||
const emailPlaceholder = this._authStepIsRequired('m.login.email.identity') ?
|
||||
_t("Email") :
|
||||
_t("Email (optional)");
|
||||
|
||||
const emailSection = (
|
||||
<div>
|
||||
<input type="text" ref="email"
|
||||
autoFocus={true} placeholder={emailPlaceholder}
|
||||
defaultValue={this.props.defaultEmail}
|
||||
className={this._classForField(FIELD_EMAIL, 'mx_Login_field')}
|
||||
onBlur={this.onEmailBlur}
|
||||
value={this.state.email} />
|
||||
</div>
|
||||
);
|
||||
emailSection = (
|
||||
<div>
|
||||
<input type="text" ref="email"
|
||||
placeholder={emailPlaceholder}
|
||||
defaultValue={this.props.defaultEmail}
|
||||
className={this._classForField(FIELD_EMAIL, 'mx_Login_field')}
|
||||
onBlur={this.onEmailBlur}
|
||||
value={this.state.email} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const threePidLogin = !SdkConfig.get().disable_3pid_login;
|
||||
const CountryDropdown = sdk.getComponent('views.auth.CountryDropdown');
|
||||
let phoneSection;
|
||||
if (!SdkConfig.get().disable_3pid_login) {
|
||||
if (threePidLogin && this._authStepIsUsed('m.login.msisdn')) {
|
||||
const phonePlaceholder = this._authStepIsRequired('m.login.msisdn') ?
|
||||
_t("Phone") :
|
||||
_t("Phone (optional)");
|
||||
|
@ -359,6 +379,7 @@ module.exports = React.createClass({
|
|||
<form onSubmit={this.onSubmit}>
|
||||
<div className="mx_AuthBody_fieldRow">
|
||||
<input type="text" ref="username"
|
||||
autoFocus={true}
|
||||
placeholder={placeholderUsername} defaultValue={this.props.defaultUsername}
|
||||
className={this._classForField(FIELD_USERNAME, 'mx_Login_field')}
|
||||
onBlur={this.onUsernameBlur} />
|
||||
|
@ -379,7 +400,7 @@ module.exports = React.createClass({
|
|||
{ phoneSection }
|
||||
</div>
|
||||
{_t(
|
||||
"Use an email address to receover your account. Other users " +
|
||||
"Use an email address to recover your account. Other users " +
|
||||
"can invite you to rooms using your contact details.",
|
||||
)}
|
||||
{ registerButton }
|
||||
|
|