mirror of https://github.com/vector-im/riot-web
Merge branches 'develop' and 't3chguy/report_event' of https://github.com/matrix-org/matrix-react-sdk into t3chguy/report_event
Conflicts: src/i18n/strings/en_EN.jsonpull/21833/head
commit
9a15d4cfc1
|
@ -34,7 +34,7 @@ src/components/views/rooms/LinkPreviewWidget.js
|
|||
src/components/views/rooms/MemberDeviceInfo.js
|
||||
src/components/views/rooms/MemberInfo.js
|
||||
src/components/views/rooms/MemberList.js
|
||||
src/components/views/rooms/MessageComposer.js
|
||||
src/components/views/rooms/SlateMessageComposer.js
|
||||
src/components/views/rooms/PinnedEventTile.js
|
||||
src/components/views/rooms/RoomList.js
|
||||
src/components/views/rooms/RoomPreviewBar.js
|
||||
|
@ -50,7 +50,6 @@ src/components/views/settings/Notifications.js
|
|||
src/GroupAddressPicker.js
|
||||
src/HtmlUtils.js
|
||||
src/ImageUtils.js
|
||||
src/languageHandler.js
|
||||
src/linkify-matrix.js
|
||||
src/Markdown.js
|
||||
src/MatrixClientPeg.js
|
||||
|
|
|
@ -15,6 +15,9 @@ module.exports = {
|
|||
"number-leading-zero": null,
|
||||
"selector-list-comma-newline-after": null,
|
||||
"at-rule-no-unknown": null,
|
||||
"scss/at-rule-no-unknown": true,
|
||||
"scss/at-rule-no-unknown": [true, {
|
||||
// https://github.com/vector-im/riot-web/issues/10544
|
||||
"ignoreAtRules": ["define-mixin"],
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ The parts are then reconciled with the DOM.
|
|||
When typing in the `contenteditable` element, the `input` event fires and
|
||||
the DOM of the editor is turned into a string. The way this is done has
|
||||
some logic to it to deal with adding newlines for block elements, to make sure
|
||||
the caret offset is calculated in the same way as the content string, and the ignore
|
||||
the caret offset is calculated in the same way as the content string, and to ignore
|
||||
caret nodes (more on that later).
|
||||
For these reasons it doesn't use `innerText`, `textContent` or anything similar.
|
||||
The model addresses any content in the editor within as an offset within this string.
|
||||
|
@ -25,13 +25,13 @@ The caret position is thus also converted from a position in the DOM tree
|
|||
to an offset in the content string. This happens in `getCaretOffsetAndText` in `dom.js`.
|
||||
|
||||
Once the content string and caret offset is calculated, it is passed to the `update()`
|
||||
method of the model. The model first calculates the same content string its current parts,
|
||||
method of the model. The model first calculates the same content string of its current parts,
|
||||
basically just concatenating their text. It then looks for differences between
|
||||
the current and the new content string. The diffing algorithm is very basic,
|
||||
and assumes there is only one change around the caret offset,
|
||||
so this should be very inexpensive. See `diff.js` for details.
|
||||
|
||||
The result of the diffing is the strings that was added and/or removed from
|
||||
The result of the diffing is the strings that were added and/or removed from
|
||||
the current content. These differences are then applied to the parts,
|
||||
where parts can apply validation logic to these changes.
|
||||
|
||||
|
@ -48,7 +48,8 @@ to leave the parts it intersects alone.
|
|||
|
||||
The benefit of this is that we can use the `input` event, which is broadly supported,
|
||||
to find changes in the editor. We don't have to rely on keyboard events,
|
||||
which relate poorly to text input or changes.
|
||||
which relate poorly to text input or changes, and don't need the `beforeinput` event,
|
||||
which isn't broadly supported yet.
|
||||
|
||||
Once the parts of the model are updated, the DOM of the editor is then reconciled
|
||||
with the new model state, see `renderModel` in `render.js` for this.
|
||||
|
|
|
@ -28,7 +28,7 @@ process.env.PHANTOMJS_BIN = 'node_modules/.bin/phantomjs';
|
|||
|
||||
function fileExists(name) {
|
||||
try {
|
||||
fs.statSync(gsCss);
|
||||
fs.statSync(name);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
|
@ -166,7 +166,7 @@ module.exports = function (config) {
|
|||
]
|
||||
},
|
||||
{
|
||||
test: /\.(gif|png|svg|ttf)$/,
|
||||
test: /\.(gif|png|svg|ttf|woff2)$/,
|
||||
loader: 'file-loader',
|
||||
},
|
||||
],
|
||||
|
|
|
@ -93,10 +93,10 @@
|
|||
"qrcode-react": "^0.1.16",
|
||||
"qs": "^6.6.0",
|
||||
"querystring": "^0.2.0",
|
||||
"react": "^15.6.0",
|
||||
"react-addons-css-transition-group": "15.3.2",
|
||||
"react": "^16.9.0",
|
||||
"react-addons-css-transition-group": "15.6.2",
|
||||
"react-beautiful-dnd": "^4.0.1",
|
||||
"react-dom": "^15.6.0",
|
||||
"react-dom": "^16.9.0",
|
||||
"react-gemini-scrollbar": "github:matrix-org/react-gemini-scrollbar#f644523",
|
||||
"resize-observer-polyfill": "^1.5.0",
|
||||
"sanitize-html": "^1.18.4",
|
||||
|
@ -148,9 +148,8 @@
|
|||
"karma-summary-reporter": "^1.5.1",
|
||||
"karma-webpack": "^4.0.0-beta.0",
|
||||
"matrix-mock-request": "^1.2.3",
|
||||
"matrix-react-test-utils": "^0.1.1",
|
||||
"matrix-react-test-utils": "^0.2.2",
|
||||
"mocha": "^5.0.5",
|
||||
"react-addons-test-utils": "^15.4.0",
|
||||
"require-json": "0.0.1",
|
||||
"rimraf": "^2.4.3",
|
||||
"sinon": "^5.0.7",
|
||||
|
|
|
@ -171,7 +171,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
|
|||
:not(.mx_textinput):not(.mx_Field):not(.mx_no_textinput) > input[type=search],
|
||||
.mx_textinput {
|
||||
color: $input-darker-fg-color;
|
||||
background-color: $input-darker-bg-color;
|
||||
background-color: $primary-bg-color;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
@ -330,7 +330,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
|
|||
|
||||
.mx_Dialog_header {
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.mx_Dialog_title {
|
||||
|
@ -456,16 +456,6 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
|
|||
background-color: $primary-bg-color;
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
background-color: $accent-color;
|
||||
color: $selection-fg-color;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: $accent-color;
|
||||
color: $selection-fg-color;
|
||||
}
|
||||
|
||||
.mx_textButton {
|
||||
@mixin mx_DialogButton_small;
|
||||
}
|
||||
|
@ -559,3 +549,12 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
|
|||
.mx_Username_color8 {
|
||||
color: $username-variant8-color;
|
||||
}
|
||||
|
||||
@define-mixin mx_Settings_fullWidthField {
|
||||
margin-right: 100px;
|
||||
}
|
||||
|
||||
@define-mixin mx_Settings_tooltip {
|
||||
// So it fits in the space provided by the page
|
||||
max-width: 120px;
|
||||
}
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
@import "./views/dialogs/_SettingsDialog.scss";
|
||||
@import "./views/dialogs/_ShareDialog.scss";
|
||||
@import "./views/dialogs/_SlashCommandHelpDialog.scss";
|
||||
@import "./views/dialogs/_TabbedIntegrationManagerDialog.scss";
|
||||
@import "./views/dialogs/_TermsDialog.scss";
|
||||
@import "./views/dialogs/_UnknownDeviceDialog.scss";
|
||||
@import "./views/dialogs/_UploadConfirmDialog.scss";
|
||||
|
@ -92,7 +93,6 @@
|
|||
@import "./views/elements/_InteractiveTooltip.scss";
|
||||
@import "./views/elements/_ManageIntegsButton.scss";
|
||||
@import "./views/elements/_MemberEventListSummary.scss";
|
||||
@import "./views/elements/_MessageEditor.scss";
|
||||
@import "./views/elements/_PowerSelector.scss";
|
||||
@import "./views/elements/_ProgressBar.scss";
|
||||
@import "./views/elements/_ReplyThread.scss";
|
||||
|
@ -135,7 +135,9 @@
|
|||
@import "./views/rooms/_AppsDrawer.scss";
|
||||
@import "./views/rooms/_Autocomplete.scss";
|
||||
@import "./views/rooms/_AuxPanel.scss";
|
||||
@import "./views/rooms/_BasicMessageComposer.scss";
|
||||
@import "./views/rooms/_E2EIcon.scss";
|
||||
@import "./views/rooms/_EditMessageComposer.scss";
|
||||
@import "./views/rooms/_EntityTile.scss";
|
||||
@import "./views/rooms/_EventTile.scss";
|
||||
@import "./views/rooms/_JumpToBottomButton.scss";
|
||||
|
@ -144,6 +146,7 @@
|
|||
@import "./views/rooms/_MemberInfo.scss";
|
||||
@import "./views/rooms/_MemberList.scss";
|
||||
@import "./views/rooms/_MessageComposer.scss";
|
||||
@import "./views/rooms/_MessageComposerFormatBar.scss";
|
||||
@import "./views/rooms/_PinnedEventTile.scss";
|
||||
@import "./views/rooms/_PinnedEventsPanel.scss";
|
||||
@import "./views/rooms/_PresenceLabel.scss";
|
||||
|
@ -158,6 +161,7 @@
|
|||
@import "./views/rooms/_RoomUpgradeWarningBar.scss";
|
||||
@import "./views/rooms/_SearchBar.scss";
|
||||
@import "./views/rooms/_SearchableEntityList.scss";
|
||||
@import "./views/rooms/_SendMessageComposer.scss";
|
||||
@import "./views/rooms/_Stickers.scss";
|
||||
@import "./views/rooms/_TopUnreadMessagesBar.scss";
|
||||
@import "./views/rooms/_WhoIsTypingTile.scss";
|
||||
|
@ -168,6 +172,8 @@
|
|||
@import "./views/settings/_Notifications.scss";
|
||||
@import "./views/settings/_PhoneNumbers.scss";
|
||||
@import "./views/settings/_ProfileSettings.scss";
|
||||
@import "./views/settings/_SetIdServer.scss";
|
||||
@import "./views/settings/_SetIntegrationManager.scss";
|
||||
@import "./views/settings/tabs/_SettingsTab.scss";
|
||||
@import "./views/settings/tabs/room/_GeneralRoomSettingsTab.scss";
|
||||
@import "./views/settings/tabs/room/_RolesRoomSettingsTab.scss";
|
||||
|
@ -178,6 +184,7 @@
|
|||
@import "./views/settings/tabs/user/_PreferencesUserSettingsTab.scss";
|
||||
@import "./views/settings/tabs/user/_SecurityUserSettingsTab.scss";
|
||||
@import "./views/settings/tabs/user/_VoiceUserSettingsTab.scss";
|
||||
@import "./views/terms/_InlineTermsAgreement.scss";
|
||||
@import "./views/verification/_VerificationShowSas.scss";
|
||||
@import "./views/voip/_CallView.scss";
|
||||
@import "./views/voip/_IncomingCallbox.scss";
|
||||
|
|
|
@ -125,3 +125,53 @@ limitations under the License.
|
|||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_LeftPanel_exploreAndFilterRow {
|
||||
display: flex;
|
||||
|
||||
.mx_SearchBox {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
margin: 4px 9px 1px 9px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_LeftPanel_explore {
|
||||
flex: 0 0 50%;
|
||||
overflow: hidden;
|
||||
transition: flex-basis 0.2s;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.mx_LeftPanel_explore_hidden {
|
||||
flex-basis: 0;
|
||||
}
|
||||
|
||||
.mx_AccessibleButton {
|
||||
font-size: 14px;
|
||||
margin: 4px 0 1px 9px;
|
||||
padding: 9px;
|
||||
padding-left: 42px;
|
||||
font-weight: 600;
|
||||
color: $notice-secondary-color;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background-color: $primary-bg-color;
|
||||
}
|
||||
|
||||
&::before {
|
||||
cursor: pointer;
|
||||
mask: url('$(res)/img/explore.svg');
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center center;
|
||||
content: "";
|
||||
left: 14px;
|
||||
top: 10px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: $notice-secondary-color;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
.mx_RoomDirectory_dialogWrapper > .mx_Dialog {
|
||||
max-width: 960px;
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.mx_RoomDirectory_dialog {
|
||||
|
@ -35,17 +34,6 @@ limitations under the License.
|
|||
flex: 1;
|
||||
}
|
||||
|
||||
.mx_RoomDirectory_createRoom {
|
||||
background-color: $button-bg-color;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
color: $button-fg-color;
|
||||
font-weight: 600;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.mx_RoomDirectory_list {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
|
@ -84,9 +72,8 @@ limitations under the License.
|
|||
}
|
||||
|
||||
.mx_RoomDirectory_roomAvatar {
|
||||
width: 24px;
|
||||
padding-left: 12px;
|
||||
padding-right: 24px;
|
||||
width: 32px;
|
||||
padding-right: 14px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
|
@ -94,6 +81,34 @@ limitations under the License.
|
|||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.mx_RoomDirectory_roomMemberCount {
|
||||
color: $light-fg-color;
|
||||
width: 60px;
|
||||
padding: 0 10px;
|
||||
text-align: center;
|
||||
|
||||
&::before {
|
||||
background-color: $light-fg-color;
|
||||
display: inline-block;
|
||||
vertical-align: text-top;
|
||||
margin-right: 2px;
|
||||
content: "";
|
||||
mask: url('$(res)/img/feather-customised/user.svg');
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
// scale it down and make the size slightly bigger (16 instead of 14px)
|
||||
// to avoid rendering artifacts
|
||||
mask-size: 80%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomDirectory_join, .mx_RoomDirectory_preview {
|
||||
width: 80px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mx_RoomDirectory_name {
|
||||
display: inline-block;
|
||||
font-weight: 600;
|
||||
|
@ -103,22 +118,9 @@ limitations under the License.
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
.mx_RoomDirectory_perm {
|
||||
display: inline;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
margin-right: 5px;
|
||||
height: 15px;
|
||||
border-radius: 11px;
|
||||
background-color: $plinth-bg-color;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
font-size: 11px;
|
||||
color: $accent-color;
|
||||
}
|
||||
|
||||
.mx_RoomDirectory_topic {
|
||||
cursor: initial;
|
||||
color: $light-fg-color;
|
||||
}
|
||||
|
||||
.mx_RoomDirectory_alias {
|
||||
|
@ -126,13 +128,20 @@ limitations under the License.
|
|||
color: $settings-grey-fg-color;
|
||||
}
|
||||
|
||||
.mx_RoomDirectory_roomMemberCount {
|
||||
text-align: right;
|
||||
width: 100px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.mx_RoomDirectory_table tr {
|
||||
padding-bottom: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_RoomDirectory .mx_RoomView_MessageList {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mx_RoomDirectory p {
|
||||
font-size: 14px;
|
||||
margin-top: 0;
|
||||
|
||||
.mx_AccessibleButton {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,12 +14,21 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_SearchBox_closeButton {
|
||||
cursor: pointer;
|
||||
background-image: url('$(res)/img/icons-close.svg');
|
||||
background-repeat: no-repeat;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-position: center;
|
||||
padding: 9px;
|
||||
.mx_SearchBox {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
|
||||
&.mx_SearchBox_blurred:not(:hover) {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.mx_SearchBox_closeButton {
|
||||
cursor: pointer;
|
||||
background-image: url('$(res)/img/icons-close.svg');
|
||||
background-repeat: no-repeat;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-position: center;
|
||||
padding: 9px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,8 +39,7 @@ limitations under the License.
|
|||
a:link,
|
||||
a:hover,
|
||||
a:visited {
|
||||
color: $accent-color;
|
||||
text-decoration: none;
|
||||
@mixin mx_Dialog_link;
|
||||
}
|
||||
|
||||
input[type=text],
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -14,23 +15,6 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_ServerConfig_fields {
|
||||
display: flex;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.mx_ServerConfig_fields .mx_Field {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.mx_ServerConfig_fields .mx_Field:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.mx_ServerConfig_fields .mx_Field:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.mx_ServerConfig_help:link {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
@ -39,3 +23,13 @@ limitations under the License.
|
|||
display: block;
|
||||
color: $warning-color;
|
||||
}
|
||||
|
||||
.mx_ServerConfig_identityServer {
|
||||
transform: scaleY(0);
|
||||
transform-origin: top;
|
||||
transition: transform 0.25s;
|
||||
|
||||
&.mx_ServerConfig_identityServer_shown {
|
||||
transform: scaleY(1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2019 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -15,6 +16,14 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_AddressPickerDialog {
|
||||
a:link,
|
||||
a:hover,
|
||||
a:visited {
|
||||
@mixin mx_Dialog_link;
|
||||
}
|
||||
}
|
||||
|
||||
/* Using a textarea for this element, to circumvent autofill */
|
||||
.mx_AddressPickerDialog_input,
|
||||
.mx_AddressPickerDialog_input:focus {
|
||||
|
@ -67,3 +76,6 @@ limitations under the License.
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
.mx_AddressPickerDialog_identityServer {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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_TabbedIntegrationManagerDialog .mx_Dialog {
|
||||
width: 60%;
|
||||
height: 70%;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
max-width: initial;
|
||||
max-height: initial;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mx_TabbedIntegrationManagerDialog_container {
|
||||
// Full size of the dialog, whatever it is
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
.mx_TabbedIntegrationManagerDialog_currentManager {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-top: 1px solid $accent-color;
|
||||
|
||||
iframe {
|
||||
background-color: #fff;
|
||||
border: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_TabbedIntegrationManagerDialog_tab {
|
||||
display: inline-block;
|
||||
border: 1px solid $accent-color;
|
||||
border-bottom: 0;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
padding: 10px 8px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.mx_TabbedIntegrationManagerDialog_currentTab {
|
||||
background-color: $accent-color;
|
||||
color: $accent-fg-color;
|
||||
}
|
|
@ -55,7 +55,7 @@ limitations under the License.
|
|||
border-radius: 4px;
|
||||
box-shadow: 4px 4px 12px 0 $menu-box-shadow-color;
|
||||
background-color: $menu-bg-color;
|
||||
z-index: 2000;
|
||||
z-index: 4000; // Higher than dialogs so tooltips can be used in dialogs
|
||||
padding: 10px;
|
||||
pointer-events: none;
|
||||
line-height: 14px;
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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_BasicMessageComposer {
|
||||
position: relative;
|
||||
|
||||
.mx_BasicMessageComposer_inputEmpty > :first-child::before {
|
||||
content: var(--placeholder);
|
||||
opacity: 0.333;
|
||||
width: 0;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
display: inline-block;
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@keyframes visualbell {
|
||||
from { background-color: $visual-bell-bg-color; }
|
||||
to { background-color: $primary-bg-color; }
|
||||
}
|
||||
|
||||
&.mx_BasicMessageComposer_input_error {
|
||||
animation: 0.2s visualbell;
|
||||
}
|
||||
|
||||
.mx_BasicMessageComposer_input {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
outline: none;
|
||||
overflow-x: auto;
|
||||
|
||||
span.mx_UserPill, span.mx_RoomPill {
|
||||
padding-left: 21px;
|
||||
position: relative;
|
||||
|
||||
// avatar psuedo element
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
top: 2px;
|
||||
content: var(--avatar-letter);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: var(--avatar-background), $avatar-bg-color;
|
||||
color: $avatar-initial-color;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 16px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
font-weight: normal;
|
||||
line-height: 16px;
|
||||
font-size: 10.4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_BasicMessageComposer_AutoCompleteWrapper {
|
||||
position: relative;
|
||||
height: 0;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -14,8 +15,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_MessageEditor {
|
||||
border-radius: 4px;
|
||||
.mx_EditMessageComposer {
|
||||
|
||||
padding: 3px;
|
||||
// this is to try not make the text move but still have some
|
||||
// padding around and in the editor.
|
||||
|
@ -23,47 +24,20 @@ limitations under the License.
|
|||
margin: -7px -10px -5px -10px;
|
||||
overflow: visible !important; // override mx_EventTile_content
|
||||
|
||||
.mx_MessageEditor_editor {
|
||||
|
||||
.mx_BasicMessageComposer_input {
|
||||
border-radius: 4px;
|
||||
border: solid 1px $primary-hairline-color;
|
||||
background-color: $primary-bg-color;
|
||||
padding: 3px 6px;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
outline: none;
|
||||
max-height: 200px;
|
||||
overflow-x: auto;
|
||||
padding: 3px 6px;
|
||||
|
||||
&:focus {
|
||||
border-color: $accent-color-50pct;
|
||||
}
|
||||
|
||||
span.mx_UserPill, span.mx_RoomPill {
|
||||
padding-left: 21px;
|
||||
position: relative;
|
||||
|
||||
// avatar psuedo element
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
top: 2px;
|
||||
content: var(--avatar-letter);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: var(--avatar-background), $avatar-bg-color;
|
||||
color: $avatar-initial-color;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 16px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
font-weight: normal;
|
||||
line-height: 16px;
|
||||
font-size: 10.4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_MessageEditor_buttons {
|
||||
.mx_EditMessageComposer_buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
|
@ -81,14 +55,9 @@ limitations under the License.
|
|||
padding: 5px 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_MessageEditor_AutoCompleteWrapper {
|
||||
position: relative;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_EventTile_last .mx_MessageEditor_buttons {
|
||||
.mx_EventTile_last .mx_EditMessageComposer_buttons {
|
||||
position: static;
|
||||
margin-right: -147px;
|
||||
}
|
|
@ -296,6 +296,25 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody {
|
|||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
/* Spoiler stuff */
|
||||
.mx_EventTile_spoiler {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_EventTile_spoiler_reason {
|
||||
color: $event-timestamp-color;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.mx_EventTile_spoiler_content {
|
||||
filter: blur(5px) saturate(0.1) sepia(1);
|
||||
transition-duration: 0.5s;
|
||||
}
|
||||
|
||||
.mx_EventTile_spoiler.visible > .mx_EventTile_spoiler_content {
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.mx_EventTile_e2eIcon {
|
||||
display: block;
|
||||
position: absolute;
|
||||
|
|
|
@ -129,7 +129,7 @@ limitations under the License.
|
|||
}
|
||||
|
||||
@keyframes visualbell {
|
||||
from { background-color: #faa; }
|
||||
from { background-color: $visual-bell-bg-color; }
|
||||
to { background-color: $primary-bg-color; }
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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_MessageComposerFormatBar {
|
||||
display: none;
|
||||
width: calc(26px * 5);
|
||||
height: 24px;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
background-color: $message-action-bar-bg-color;
|
||||
user-select: none;
|
||||
|
||||
&.mx_MessageComposerFormatBar_shown {
|
||||
display: block;
|
||||
}
|
||||
|
||||
> * {
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
border: 1px solid $message-action-bar-border-color;
|
||||
margin-left: -1px;
|
||||
|
||||
&:hover {
|
||||
border-color: $message-action-bar-hover-border-color;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_MessageComposerFormatBar_button {
|
||||
width: 27px;
|
||||
height: 24px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.mx_MessageComposerFormatBar_button::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
background-color: $message-action-bar-fg-color;
|
||||
}
|
||||
|
||||
.mx_MessageComposerFormatBar_buttonIconBold::after {
|
||||
mask-image: url('$(res)/img/format/bold.svg');
|
||||
}
|
||||
|
||||
.mx_MessageComposerFormatBar_buttonIconItalic::after {
|
||||
mask-image: url('$(res)/img/format/italics.svg');
|
||||
}
|
||||
|
||||
.mx_MessageComposerFormatBar_buttonIconStrikethrough::after {
|
||||
mask-image: url('$(res)/img/format/strikethrough.svg');
|
||||
}
|
||||
|
||||
.mx_MessageComposerFormatBar_buttonIconQuote::after {
|
||||
mask-image: url('$(res)/img/format/quote.svg');
|
||||
}
|
||||
|
||||
.mx_MessageComposerFormatBar_buttonIconCode::after {
|
||||
mask-image: url('$(res)/img/format/code.svg');
|
||||
}
|
||||
}
|
||||
|
||||
.mx_MessageComposerFormatBar_buttonTooltip {
|
||||
white-space: nowrap;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
min-width: 54px;
|
||||
text-align: center;
|
||||
|
||||
.mx_MessageComposerFormatBar_tooltipShortcut {
|
||||
font-size: 9px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
|
@ -27,10 +27,6 @@ limitations under the License.
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.mx_SearchBox {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
/* hide resize handles next to collapsed / empty sublists */
|
||||
.mx_RoomList .mx_RoomSubList:not(.mx_RoomSubList_nonEmpty) + .mx_ResizeHandle {
|
||||
display: none;
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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_SendMessageComposer {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 14px;
|
||||
justify-content: center;
|
||||
margin-right: 6px;
|
||||
// don't grow wider than available space
|
||||
min-width: 0;
|
||||
|
||||
.mx_BasicMessageComposer {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// min-height at this level so the mx_BasicMessageComposer_input
|
||||
// still stays vertically centered when less than 50px
|
||||
min-height: 50px;
|
||||
|
||||
.mx_BasicMessageComposer_input {
|
||||
padding: 3px 0;
|
||||
// this will center the contenteditable
|
||||
// in it's parent vertically
|
||||
// while keeping the autocomplete at the top
|
||||
// of the composer. The parent needs to be a flex container for this to work.
|
||||
margin: auto 0;
|
||||
// max-height at this level so autocomplete doesn't get scrolled too
|
||||
max-height: 140px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_SendMessageComposer_overlayWrapper {
|
||||
position: relative;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -26,8 +26,13 @@ limitations under the License.
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.mx_DevicesPanel_header > .mx_DevicesPanel_deviceButtons {
|
||||
height: 48px; // make this tall so the table doesn't move down when the delete button appears
|
||||
}
|
||||
|
||||
.mx_DevicesPanel_header > div {
|
||||
display: table-cell;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.mx_DevicesPanel_header .mx_DevicesPanel_deviceLastSeen {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -36,6 +37,15 @@ limitations under the License.
|
|||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.mx_ExistingPhoneNumber_verification {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
.mx_Field {
|
||||
margin: 0 0 0 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_PhoneNumbers_input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
@ -43,7 +43,6 @@ limitations under the License.
|
|||
height: 88px;
|
||||
margin-left: 13px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_ProfileSettings_avatar > * {
|
||||
|
@ -71,6 +70,7 @@ limitations under the License.
|
|||
text-align: center;
|
||||
vertical-align: middle;
|
||||
font-size: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_ProfileSettings_avatar:hover .mx_ProfileSettings_avatarOverlay:not(.mx_ProfileSettings_avatarOverlay_disabled) {
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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_SetIdServer .mx_Field_input {
|
||||
@mixin mx_Settings_fullWidthField;
|
||||
}
|
||||
|
||||
.mx_SetIdServer_tooltip {
|
||||
@mixin mx_Settings_tooltip;
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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_SetIntegrationManager .mx_Field_input {
|
||||
@mixin mx_Settings_fullWidthField;
|
||||
}
|
||||
|
||||
.mx_SetIntegrationManager {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.mx_SetIntegrationManager > .mx_SettingsTab_heading {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.mx_SetIntegrationManager > .mx_SettingsTab_heading > .mx_SettingsTab_subheading {
|
||||
display: inline-block;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.mx_SetIntegrationManager_tooltip {
|
||||
@mixin mx_Settings_tooltip;
|
||||
}
|
|
@ -24,6 +24,10 @@ limitations under the License.
|
|||
color: $primary-fg-color;
|
||||
}
|
||||
|
||||
.mx_SettingsTab_heading:nth-child(n + 2) {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.mx_SettingsTab_subheading {
|
||||
font-size: 16px;
|
||||
display: block;
|
||||
|
@ -37,9 +41,8 @@ limitations under the License.
|
|||
.mx_SettingsTab_subsectionText {
|
||||
color: $settings-subsection-fg-color;
|
||||
font-size: 14px;
|
||||
padding-bottom: 12px;
|
||||
display: block;
|
||||
margin: 0 100px 0 0; // Align with the rest of the view
|
||||
margin: 10px 100px 10px 0; // Align with the rest of the view
|
||||
}
|
||||
|
||||
.mx_SettingsTab_section .mx_SettingsFlag {
|
||||
|
@ -67,12 +70,6 @@ limitations under the License.
|
|||
word-break: break-all;
|
||||
}
|
||||
|
||||
.mx_SettingsTab .mx_SettingsTab_subheading:nth-child(n + 2) {
|
||||
// These views have a lot of the same repetitive information on it, so
|
||||
// give them more visual distinction between the sections.
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.mx_SettingsTab a {
|
||||
color: $accent-color-alt;
|
||||
}
|
||||
|
|
|
@ -16,15 +16,21 @@ limitations under the License.
|
|||
|
||||
.mx_GeneralUserSettingsTab_changePassword .mx_Field,
|
||||
.mx_GeneralUserSettingsTab_themeSection .mx_Field {
|
||||
margin-right: 100px; // Align with the other fields on the page
|
||||
@mixin mx_Settings_fullWidthField;
|
||||
}
|
||||
|
||||
.mx_GeneralUserSettingsTab_changePassword .mx_Field:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.mx_GeneralUserSettingsTab_accountSection > .mx_EmailAddresses,
|
||||
.mx_GeneralUserSettingsTab_accountSection > .mx_PhoneNumbers,
|
||||
.mx_GeneralUserSettingsTab_accountSection .mx_EmailAddresses,
|
||||
.mx_GeneralUserSettingsTab_accountSection .mx_PhoneNumbers,
|
||||
.mx_GeneralUserSettingsTab_discovery .mx_ExistingEmailAddress,
|
||||
.mx_GeneralUserSettingsTab_discovery .mx_ExistingPhoneNumber,
|
||||
.mx_GeneralUserSettingsTab_languageInput {
|
||||
margin-right: 100px; // Align with the other fields on the page
|
||||
@mixin mx_Settings_fullWidthField;
|
||||
}
|
||||
|
||||
.mx_GeneralUserSettingsTab_warningIcon {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
|
|
@ -15,5 +15,5 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
.mx_PreferencesUserSettingsTab .mx_Field {
|
||||
margin-right: 100px; // Align with the rest of the controls
|
||||
@mixin mx_Settings_fullWidthField;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
.mx_VoiceUserSettingsTab .mx_Field {
|
||||
margin-right: 100px; // align with the rest of the fields
|
||||
@mixin mx_Settings_fullWidthField;
|
||||
}
|
||||
|
||||
.mx_VoiceUserSettingsTab_missingMediaPermissions {
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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_InlineTermsAgreement_cbContainer {
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
|
||||
a {
|
||||
color: $accent-color;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.mx_InlineTermsAgreement_checkbox {
|
||||
margin-top: 10px;
|
||||
|
||||
input {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_InlineTermsAgreement_link {
|
||||
display: inline-block;
|
||||
mask-image: url('$(res)/img/external-link.svg');
|
||||
background-color: $accent-color;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-left: 3px;
|
||||
vertical-align: middle;
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
<?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="21.124001"
|
||||
height="14.896"
|
||||
viewBox="0 0 21.124001 14.896"
|
||||
version="1.1"
|
||||
id="svg21"
|
||||
sodipodi:docname="explore.svg"
|
||||
inkscape:version="0.92.4 (unknown)">
|
||||
<metadata
|
||||
id="metadata25">
|
||||
<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>
|
||||
<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="namedview23"
|
||||
showgrid="false"
|
||||
fit-margin-top="0.5"
|
||||
fit-margin-left="0.5"
|
||||
fit-margin-right="0.5"
|
||||
fit-margin-bottom="0.5"
|
||||
inkscape:zoom="3.1891892"
|
||||
inkscape:cx="-23.683763"
|
||||
inkscape:cy="5.4480001"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg21" />
|
||||
<defs
|
||||
id="defs15">
|
||||
<filter
|
||||
id="a"
|
||||
width="1.118"
|
||||
height="1.158"
|
||||
x="-0.059"
|
||||
y="-0.079000004"
|
||||
filterUnits="objectBoundingBox">
|
||||
<feOffset
|
||||
dy="2"
|
||||
in="SourceAlpha"
|
||||
result="shadowOffsetOuter1"
|
||||
id="feOffset2" />
|
||||
<feGaussianBlur
|
||||
in="shadowOffsetOuter1"
|
||||
result="shadowBlurOuter1"
|
||||
stdDeviation="16"
|
||||
id="feGaussianBlur4" />
|
||||
<feColorMatrix
|
||||
in="shadowBlurOuter1"
|
||||
result="shadowMatrixOuter1"
|
||||
values="0 0 0 0 0 0 0 0 0 0.473684211 0 0 0 0 1 0 0 0 0.241258741 0"
|
||||
id="feColorMatrix6" />
|
||||
<feMerge
|
||||
id="feMerge12">
|
||||
<feMergeNode
|
||||
in="shadowMatrixOuter1"
|
||||
id="feMergeNode8" />
|
||||
<feMergeNode
|
||||
in="SourceGraphic"
|
||||
id="feMergeNode10" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<g
|
||||
transform="translate(-91.438,-120.552)"
|
||||
id="g19"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#61708b;stroke-width:1.29999995;stroke-linecap:round;filter:url(#a)">
|
||||
<path
|
||||
d="m 98,122 h 13 m -13,6 h 13 m -13,6 h 13 M 93,122 h 0.5 m -0.5,6 h 0.5 m -0.5,6 h 0.5"
|
||||
id="path17"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="13" viewBox="0 0 10 13">
|
||||
<path fill="#212121" fill-rule="nonzero" d="M7.47 6.156c.732.204 1.299.57 1.701 1.098.402.528.603 1.188.603 1.98 0 1.092-.375 1.941-1.125 2.547-.75.606-1.797.909-3.141.909H.918c-.288 0-.513-.081-.675-.243C.081 12.285 0 12.066 0 11.79V.9C0 .624.081.405.243.243.405.081.63 0 .918 0H5.31c1.308 0 2.331.291 3.069.873.738.582 1.107 1.395 1.107 2.439 0 .672-.177 1.254-.531 1.746-.354.492-.849.858-1.485 1.098zM1.818 5.49h3.204c1.776 0 2.664-.672 2.664-2.016 0-.672-.219-1.17-.657-1.494-.438-.324-1.107-.486-2.007-.486H1.818V5.49zm3.492 5.706c.924 0 1.602-.168 2.034-.504.432-.336.648-.858.648-1.566 0-.72-.219-1.257-.657-1.611-.438-.354-1.113-.531-2.025-.531H1.818v4.212H5.31z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 770 B |
|
@ -0,0 +1,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="19" height="12" viewBox="0 0 19 12">
|
||||
<g fill="none" fill-rule="evenodd" stroke="#212121" stroke-linecap="round">
|
||||
<path stroke-linejoin="round" d="M14.1 9.8L18 5.9 14.1 2"/>
|
||||
<path d="M7.5 11.5l4-11"/>
|
||||
<path stroke-linejoin="round" d="M4.9 2L1 5.9l3.9 3.9"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 348 B |
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="3" height="13" viewBox="0 0 3 13">
|
||||
<path fill="#212121" fill-rule="nonzero" d="M.542 12.87a.539.539 0 0 1-.396-.162.506.506 0 0 1-.144-.414L1.92.522A.551.551 0 0 1 2.478 0c.168 0 .303.051.405.153.102.102.147.243.135.423L1.1 12.348a.544.544 0 0 1-.171.387.563.563 0 0 1-.387.135z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 340 B |
|
@ -0,0 +1,5 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="11" viewBox="0 0 13 11">
|
||||
<g fill="#212121" fill-rule="nonzero">
|
||||
<path d="M1.05 2.375c.25.017.458.112.625.288.167.175.25.404.25.687 0 .3-.087.542-.262.725A.877.877 0 0 1 1 4.35c-.667 0-1-.492-1-1.475C0 1.858.317.975.95.225 1.1.075 1.25 0 1.4 0a.44.44 0 0 1 .325.125.44.44 0 0 1 .125.325c0 .15-.05.275-.15.375a2.26 2.26 0 0 0-.462.7 3.215 3.215 0 0 0-.188.85zm3.575 0c.25.017.458.112.625.288.167.175.25.404.25.687 0 .3-.087.542-.263.725a.877.877 0 0 1-.662.275c-.667 0-1-.492-1-1.475 0-1.017.317-1.9.95-2.65.15-.15.3-.225.45-.225A.44.44 0 0 1 5.3.125a.44.44 0 0 1 .125.325c0 .15-.05.275-.15.375a2.26 2.26 0 0 0-.463.7 3.215 3.215 0 0 0-.187.85zM11.95 7.975a.916.916 0 0 1-.625-.287c-.167-.176-.25-.405-.25-.688 0-.3.087-.542.262-.725A.877.877 0 0 1 12 6c.667 0 1 .492 1 1.475 0 1.017-.317 1.9-.95 2.65-.15.15-.3.225-.45.225a.44.44 0 0 1-.325-.125.44.44 0 0 1-.125-.325c0-.15.05-.275.15-.375a2.26 2.26 0 0 0 .462-.7c.092-.233.155-.517.188-.85zm-3.575 0a.916.916 0 0 1-.625-.287C7.583 7.512 7.5 7.283 7.5 7c0-.3.087-.542.262-.725A.877.877 0 0 1 8.425 6c.667 0 1 .492 1 1.475 0 1.017-.317 1.9-.95 2.65-.15.15-.3.225-.45.225a.44.44 0 0 1-.325-.125.44.44 0 0 1-.125-.325c0-.15.05-.275.15-.375a2.26 2.26 0 0 0 .462-.7c.092-.233.155-.517.188-.85z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="13" viewBox="0 0 12 13">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path fill="#212121" fill-rule="nonzero" d="M9.566 7.834l-.071-.085c.39.426.585.999.585 1.719 0 .684-.189 1.293-.567 1.827-.378.534-.912.948-1.602 1.242-.69.294-1.479.441-2.367.441a7.85 7.85 0 0 1-2.565-.423c-.822-.282-1.467-.663-1.935-1.143a.65.65 0 0 1-.234-.522c0-.132.039-.246.117-.342.078-.096.165-.144.261-.144.12 0 .258.06.414.18 1.128.936 2.442 1.404 3.942 1.404 1.092 0 1.935-.219 2.529-.657.594-.438.891-1.059.891-1.863 0-.468-.144-.846-.432-1.134a2.753 2.753 0 0 0-.696-.5h1.73zM5.616 0c.828 0 1.608.135 2.34.405.732.27 1.338.657 1.818 1.161.156.156.234.33.234.522a.526.526 0 0 1-.117.342c-.078.096-.165.144-.261.144-.12 0-.258-.06-.414-.18-.648-.528-1.233-.894-1.755-1.098C6.939 1.092 6.324.99 5.616.99c-1.068 0-1.902.228-2.502.684-.6.456-.9 1.092-.9 1.908 0 .492.132.891.396 1.197l.052.055H1.326c-.152-.354-.228-.765-.228-1.234 0-.708.189-1.335.567-1.881.378-.546.909-.969 1.593-1.269C3.942.15 4.728 0 5.616 0z"/>
|
||||
<rect width="12" height="1" y="5.834" fill="#000" rx=".5"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -146,6 +146,8 @@ $button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color
|
|||
$button-link-fg-color: $accent-color;
|
||||
$button-link-bg-color: transparent;
|
||||
|
||||
$visual-bell-bg-color: #800;
|
||||
|
||||
$room-warning-bg-color: $header-panel-bg-color;
|
||||
|
||||
$dark-panel-bg-color: $header-panel-bg-color;
|
||||
|
@ -200,6 +202,11 @@ $interactive-tooltip-fg-color: #ffffff;
|
|||
background-color: $button-secondary-bg-color;
|
||||
}
|
||||
|
||||
@define-mixin mx_Dialog_link {
|
||||
color: $accent-color;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
// Nasty hacks to apply a filter to arbitrary monochrome artwork to make it
|
||||
// better match the theme. Typically applied to dark grey 'off' buttons or
|
||||
// light grey 'on' buttons.
|
||||
|
|
|
@ -247,6 +247,8 @@ $button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color
|
|||
$button-link-fg-color: $accent-color;
|
||||
$button-link-bg-color: transparent;
|
||||
|
||||
$visual-bell-bg-color: #faa;
|
||||
|
||||
// Toggle switch
|
||||
$togglesw-off-color: #c1c9d6;
|
||||
$togglesw-on-color: $accent-color;
|
||||
|
@ -326,3 +328,8 @@ $interactive-tooltip-fg-color: #ffffff;
|
|||
color: $accent-color;
|
||||
background-color: $button-secondary-bg-color;
|
||||
}
|
||||
|
||||
@define-mixin mx_Dialog_link {
|
||||
color: $accent-color;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017, 2018 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -63,7 +64,8 @@ import SdkConfig from './SdkConfig';
|
|||
import { showUnknownDeviceDialogForCalls } from './cryptodevices';
|
||||
import WidgetUtils from './utils/WidgetUtils';
|
||||
import WidgetEchoStore from './stores/WidgetEchoStore';
|
||||
import ScalarAuthClient from './ScalarAuthClient';
|
||||
import {IntegrationManagers} from "./integrations/IntegrationManagers";
|
||||
import SettingsStore, { SettingLevel } from './settings/SettingsStore';
|
||||
|
||||
global.mxCalls = {
|
||||
//room_id: MatrixCall
|
||||
|
@ -117,8 +119,7 @@ function _reAttemptCall(call) {
|
|||
|
||||
function _setCallListeners(call) {
|
||||
call.on("error", function(err) {
|
||||
console.error("Call error: %s", err);
|
||||
console.error(err.stack);
|
||||
console.error("Call error:", err);
|
||||
if (err.code === 'unknown_devices') {
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
|
||||
|
@ -146,8 +147,15 @@ function _setCallListeners(call) {
|
|||
},
|
||||
});
|
||||
} else {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
if (
|
||||
MatrixClientPeg.get().getTurnServers().length === 0 &&
|
||||
SettingsStore.getValue("fallbackICEServerAllowed") === null
|
||||
) {
|
||||
_showICEFallbackPrompt();
|
||||
return;
|
||||
}
|
||||
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Call Failed', '', ErrorDialog, {
|
||||
title: _t('Call Failed'),
|
||||
description: err.message,
|
||||
|
@ -217,6 +225,36 @@ function _setCallState(call, roomId, status) {
|
|||
});
|
||||
}
|
||||
|
||||
function _showICEFallbackPrompt() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
const code = sub => <code>{sub}</code>;
|
||||
Modal.createTrackedDialog('No TURN servers', '', QuestionDialog, {
|
||||
title: _t("Call failed due to misconfigured server"),
|
||||
description: <div>
|
||||
<p>{_t(
|
||||
"Please ask the administrator of your homeserver " +
|
||||
"(<code>%(homeserverDomain)s</code>) to configure a TURN server in " +
|
||||
"order for calls to work reliably.",
|
||||
{ homeserverDomain: cli.getDomain() }, { code },
|
||||
)}</p>
|
||||
<p>{_t(
|
||||
"Alternatively, you can try to use the public server at " +
|
||||
"<code>turn.matrix.org</code>, but this will not be as reliable, and " +
|
||||
"it will share your IP address with that server. You can also manage " +
|
||||
"this in Settings.",
|
||||
null, { code },
|
||||
)}</p>
|
||||
</div>,
|
||||
button: _t('Try using turn.matrix.org'),
|
||||
cancelButton: _t('OK'),
|
||||
onFinished: (allow) => {
|
||||
SettingsStore.setValue("fallbackICEServerAllowed", null, SettingLevel.DEVICE, allow);
|
||||
cli.setFallbackICEServerAllowed(allow);
|
||||
},
|
||||
}, null, true);
|
||||
}
|
||||
|
||||
function _onAction(payload) {
|
||||
function placeCall(newCall) {
|
||||
_setCallListeners(newCall);
|
||||
|
@ -348,14 +386,20 @@ async function _startCallApp(roomId, type) {
|
|||
// the state event in anyway, but the resulting widget would then not
|
||||
// work for us. Better that the user knows before everyone else in the
|
||||
// room sees it.
|
||||
const scalarClient = new ScalarAuthClient();
|
||||
let haveScalar = false;
|
||||
try {
|
||||
await scalarClient.connect();
|
||||
haveScalar = scalarClient.hasCredentials();
|
||||
} catch (e) {
|
||||
// fall through
|
||||
const managers = IntegrationManagers.sharedInstance();
|
||||
let haveScalar = true;
|
||||
if (managers.hasManager()) {
|
||||
try {
|
||||
const scalarClient = managers.getPrimaryManager().getScalarClient();
|
||||
await scalarClient.connect();
|
||||
haveScalar = scalarClient.hasCredentials();
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
} else {
|
||||
haveScalar = false;
|
||||
}
|
||||
|
||||
if (!haveScalar) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
|
||||
|
@ -421,7 +465,8 @@ async function _startCallApp(roomId, type) {
|
|||
// URL, but this will at least allow the integration manager to not be hardcoded.
|
||||
widgetUrl = SdkConfig.get().integrations_jitsi_widget_url + '?' + queryString;
|
||||
} else {
|
||||
widgetUrl = SdkConfig.get().integrations_rest_url + '/widgets/jitsi.html?' + queryString;
|
||||
const apiUrl = IntegrationManagers.sharedInstance().getPrimaryManager().apiUrl;
|
||||
widgetUrl = apiUrl + '/widgets/jitsi.html?' + queryString;
|
||||
}
|
||||
|
||||
const widgetData = { widgetSessionId };
|
||||
|
|
|
@ -22,7 +22,8 @@ import WidgetMessagingEndpoint from './WidgetMessagingEndpoint';
|
|||
import ActiveWidgetStore from './stores/ActiveWidgetStore';
|
||||
import MatrixClientPeg from "./MatrixClientPeg";
|
||||
import RoomViewStore from "./stores/RoomViewStore";
|
||||
import { showIntegrationsManager } from './integrations/integrations';
|
||||
import {IntegrationManagers} from "./integrations/IntegrationManagers";
|
||||
import SettingsStore from "./settings/SettingsStore";
|
||||
|
||||
const WIDGET_API_VERSION = '0.0.2'; // Current API version
|
||||
const SUPPORTED_WIDGET_API_VERSIONS = [
|
||||
|
@ -193,11 +194,20 @@ export default class FromWidgetPostMessageApi {
|
|||
const integType = (data && data.integType) ? data.integType : null;
|
||||
const integId = (data && data.integId) ? data.integId : null;
|
||||
|
||||
showIntegrationsManager({
|
||||
room: MatrixClientPeg.get().getRoom(RoomViewStore.getRoomId()),
|
||||
screen: 'type_' + integType,
|
||||
integrationId: integId,
|
||||
});
|
||||
// TODO: Open the right integration manager for the widget
|
||||
if (SettingsStore.isFeatureEnabled("feature_many_integration_managers")) {
|
||||
IntegrationManagers.sharedInstance().openAll(
|
||||
MatrixClientPeg.get().getRoom(RoomViewStore.getRoomId()),
|
||||
`type_${integType}`,
|
||||
integId,
|
||||
);
|
||||
} else {
|
||||
IntegrationManagers.sharedInstance().getPrimaryManager().open(
|
||||
MatrixClientPeg.get().getRoom(RoomViewStore.getRoomId()),
|
||||
`type_${integType}`,
|
||||
integId,
|
||||
);
|
||||
}
|
||||
} else if (action === 'set_always_on_screen') {
|
||||
// This is a new message: there is no reason to support the deprecated widgetData here
|
||||
const data = event.data.data;
|
||||
|
|
|
@ -46,7 +46,7 @@ export function showGroupInviteDialog(groupId) {
|
|||
|
||||
_onGroupInviteFinished(groupId, addrs).then(resolve, reject);
|
||||
},
|
||||
});
|
||||
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -81,7 +81,7 @@ export function showGroupAddRoomDialog(groupId) {
|
|||
|
||||
_onGroupAddRoomFinished(groupId, addrs, addRoomsPublicly).then(resolve, reject);
|
||||
},
|
||||
});
|
||||
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -256,7 +256,7 @@ const sanitizeHtmlParams = {
|
|||
allowedAttributes: {
|
||||
// custom ones first:
|
||||
font: ['color', 'data-mx-bg-color', 'data-mx-color', 'style'], // custom to matrix
|
||||
span: ['data-mx-bg-color', 'data-mx-color', 'style'], // custom to matrix
|
||||
span: ['data-mx-bg-color', 'data-mx-color', 'data-mx-spoiler', 'style'], // custom to matrix
|
||||
a: ['href', 'name', 'target', 'rel'], // remote target: custom to matrix
|
||||
img: ['src', 'width', 'height', 'alt', 'title'],
|
||||
ol: ['start'],
|
||||
|
|
|
@ -14,15 +14,50 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { SERVICE_TYPES } from 'matrix-js-sdk';
|
||||
import { createClient, SERVICE_TYPES } from 'matrix-js-sdk';
|
||||
|
||||
import MatrixClientPeg from './MatrixClientPeg';
|
||||
import { Service, startTermsFlow, TermsNotSignedError } from './Terms';
|
||||
|
||||
export default class IdentityAuthClient {
|
||||
constructor() {
|
||||
/**
|
||||
* Creates a new identity auth client
|
||||
* @param {string} identityUrl The URL to contact the identity server with.
|
||||
* When provided, this class will operate solely within memory, refusing to
|
||||
* persist any information such as tokens. Default null (not provided).
|
||||
*/
|
||||
constructor(identityUrl = null) {
|
||||
this.accessToken = null;
|
||||
this.authEnabled = true;
|
||||
|
||||
if (identityUrl) {
|
||||
// XXX: We shouldn't have to create a whole new MatrixClient just to
|
||||
// do identity server auth. The functions don't take an identity URL
|
||||
// though, and making all of them take one could lead to developer
|
||||
// confusion about what the idBaseUrl does on a client. Therefore, we
|
||||
// just make a new client and live with it.
|
||||
this.tempClient = createClient({
|
||||
baseUrl: "", // invalid by design
|
||||
idBaseUrl: identityUrl,
|
||||
});
|
||||
} else {
|
||||
// Indicates that we're using the real client, not some workaround.
|
||||
this.tempClient = null;
|
||||
}
|
||||
}
|
||||
|
||||
get _matrixClient() {
|
||||
return this.tempClient ? this.tempClient : MatrixClientPeg.get();
|
||||
}
|
||||
|
||||
_writeToken() {
|
||||
if (this.tempClient) return; // temporary client: ignore
|
||||
window.localStorage.setItem("mx_is_access_token", this.accessToken);
|
||||
}
|
||||
|
||||
_readToken() {
|
||||
if (this.tempClient) return null; // temporary client: ignore
|
||||
return window.localStorage.getItem("mx_is_access_token");
|
||||
}
|
||||
|
||||
hasCredentials() {
|
||||
|
@ -30,7 +65,7 @@ export default class IdentityAuthClient {
|
|||
}
|
||||
|
||||
// Returns a promise that resolves to the access_token string from the IS
|
||||
async getAccessToken() {
|
||||
async getAccessToken({ check = true } = {}) {
|
||||
if (!this.authEnabled) {
|
||||
// The current IS doesn't support authentication
|
||||
return null;
|
||||
|
@ -38,30 +73,32 @@ export default class IdentityAuthClient {
|
|||
|
||||
let token = this.accessToken;
|
||||
if (!token) {
|
||||
token = window.localStorage.getItem("mx_is_access_token");
|
||||
token = this._readToken();
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
token = await this.registerForToken();
|
||||
token = await this.registerForToken(check);
|
||||
if (token) {
|
||||
this.accessToken = token;
|
||||
window.localStorage.setItem("mx_is_access_token", token);
|
||||
this._writeToken();
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
try {
|
||||
await this._checkToken(token);
|
||||
} catch (e) {
|
||||
if (e instanceof TermsNotSignedError) {
|
||||
// Retrying won't help this
|
||||
throw e;
|
||||
}
|
||||
// Retry in case token expired
|
||||
token = await this.registerForToken();
|
||||
if (token) {
|
||||
this.accessToken = token;
|
||||
window.localStorage.setItem("mx_is_access_token", token);
|
||||
if (check) {
|
||||
try {
|
||||
await this._checkToken(token);
|
||||
} catch (e) {
|
||||
if (e instanceof TermsNotSignedError) {
|
||||
// Retrying won't help this
|
||||
throw e;
|
||||
}
|
||||
// Retry in case token expired
|
||||
token = await this.registerForToken();
|
||||
if (token) {
|
||||
this.accessToken = token;
|
||||
this._writeToken();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,13 +107,13 @@ export default class IdentityAuthClient {
|
|||
|
||||
async _checkToken(token) {
|
||||
try {
|
||||
await MatrixClientPeg.get().getIdentityAccount(token);
|
||||
await this._matrixClient.getIdentityAccount(token);
|
||||
} catch (e) {
|
||||
if (e.errcode === "M_TERMS_NOT_SIGNED") {
|
||||
console.log("Identity Server requires new terms to be agreed to");
|
||||
await startTermsFlow([new Service(
|
||||
SERVICE_TYPES.IS,
|
||||
MatrixClientPeg.get().idBaseUrl,
|
||||
this._matrixClient.getIdentityServerUrl(),
|
||||
token,
|
||||
)]);
|
||||
return;
|
||||
|
@ -91,12 +128,12 @@ export default class IdentityAuthClient {
|
|||
// See also https://github.com/vector-im/riot-web/issues/10455.
|
||||
}
|
||||
|
||||
async registerForToken() {
|
||||
async registerForToken(check=true) {
|
||||
try {
|
||||
const hsOpenIdToken = await MatrixClientPeg.get().getOpenIdToken();
|
||||
const { access_token: identityAccessToken } =
|
||||
await MatrixClientPeg.get().registerWithIdentityServer(hsOpenIdToken);
|
||||
await this._checkToken(identityAccessToken);
|
||||
await this._matrixClient.registerWithIdentityServer(hsOpenIdToken);
|
||||
if (check) await this._checkToken(identityAccessToken);
|
||||
return identityAccessToken;
|
||||
} catch (e) {
|
||||
if (e.cors === "rejected" || e.httpStatus === 404) {
|
||||
|
|
|
@ -35,6 +35,7 @@ import { sendLoginRequest } from "./Login";
|
|||
import * as StorageManager from './utils/StorageManager';
|
||||
import SettingsStore from "./settings/SettingsStore";
|
||||
import TypingStore from "./stores/TypingStore";
|
||||
import {IntegrationManagers} from "./integrations/IntegrationManagers";
|
||||
|
||||
/**
|
||||
* Called at startup, to attempt to build a logged-in Matrix session. It tries
|
||||
|
@ -580,6 +581,7 @@ async function startMatrixClient(startSyncing=true) {
|
|||
Presence.start();
|
||||
}
|
||||
DMRoomMap.makeShared().start();
|
||||
IntegrationManagers.sharedInstance().startWatching();
|
||||
ActiveWidgetStore.start();
|
||||
|
||||
if (startSyncing) {
|
||||
|
@ -638,6 +640,7 @@ export function stopMatrixClient(unsetClient=true) {
|
|||
TypingStore.sharedInstance().reset();
|
||||
Presence.stop();
|
||||
ActiveWidgetStore.stop();
|
||||
IntegrationManagers.sharedInstance().stopWatching();
|
||||
if (DMRoomMap.shared()) DMRoomMap.shared().stop();
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (cli) {
|
||||
|
|
11
src/Login.js
11
src/Login.js
|
@ -2,6 +2,7 @@
|
|||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -87,32 +88,23 @@ export default class Login {
|
|||
const isEmail = username.indexOf("@") > 0;
|
||||
|
||||
let identifier;
|
||||
let legacyParams; // parameters added to support old HSes
|
||||
if (phoneCountry && phoneNumber) {
|
||||
identifier = {
|
||||
type: 'm.id.phone',
|
||||
country: phoneCountry,
|
||||
number: phoneNumber,
|
||||
};
|
||||
// No legacy support for phone number login
|
||||
} else if (isEmail) {
|
||||
identifier = {
|
||||
type: 'm.id.thirdparty',
|
||||
medium: 'email',
|
||||
address: username,
|
||||
};
|
||||
legacyParams = {
|
||||
medium: 'email',
|
||||
address: username,
|
||||
};
|
||||
} else {
|
||||
identifier = {
|
||||
type: 'm.id.user',
|
||||
user: username,
|
||||
};
|
||||
legacyParams = {
|
||||
user: username,
|
||||
};
|
||||
}
|
||||
|
||||
const loginParams = {
|
||||
|
@ -120,7 +112,6 @@ export default class Login {
|
|||
identifier: identifier,
|
||||
initial_device_display_name: this._defaultDeviceDisplayName,
|
||||
};
|
||||
Object.assign(loginParams, legacyParams);
|
||||
|
||||
const tryFallbackHs = (originalError) => {
|
||||
return sendLoginRequest(
|
||||
|
|
|
@ -32,6 +32,7 @@ import Modal from './Modal';
|
|||
import {verificationMethods} from 'matrix-js-sdk/lib/crypto';
|
||||
import MatrixClientBackedSettingsHandler from "./settings/handlers/MatrixClientBackedSettingsHandler";
|
||||
import * as StorageManager from './utils/StorageManager';
|
||||
import IdentityAuthClient from './IdentityAuthClient';
|
||||
|
||||
interface MatrixClientCreds {
|
||||
homeserverUrl: string,
|
||||
|
@ -216,8 +217,10 @@ class MatrixClientPeg {
|
|||
deviceId: creds.deviceId,
|
||||
timelineSupport: true,
|
||||
forceTURN: !SettingsStore.getValue('webRtcAllowPeerToPeer', false),
|
||||
fallbackICEServerAllowed: !!SettingsStore.getValue('fallbackICEServerAllowed'),
|
||||
verificationMethods: [verificationMethods.SAS],
|
||||
unstableClientRelationAggregation: true,
|
||||
identityServer: new IdentityAuthClient(),
|
||||
};
|
||||
|
||||
this.matrixClient = createMatrixClient(opts);
|
||||
|
|
25
src/Modal.js
25
src/Modal.js
|
@ -23,6 +23,7 @@ import Analytics from './Analytics';
|
|||
import sdk from './index';
|
||||
import dis from './dispatcher';
|
||||
import { _t } from './languageHandler';
|
||||
import Promise from "bluebird";
|
||||
|
||||
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
||||
const STATIC_DIALOG_CONTAINER_ID = "mx_Dialog_StaticContainer";
|
||||
|
@ -182,7 +183,7 @@ class ModalManager {
|
|||
const modal = {};
|
||||
|
||||
// never call this from onFinished() otherwise it will loop
|
||||
const closeDialog = this._getCloseFn(modal, props);
|
||||
const [closeDialog, onFinishedProm] = this._getCloseFn(modal, props);
|
||||
|
||||
// don't attempt to reuse the same AsyncWrapper for different dialogs,
|
||||
// otherwise we'll get confused.
|
||||
|
@ -197,11 +198,13 @@ class ModalManager {
|
|||
modal.onFinished = props ? props.onFinished : null;
|
||||
modal.className = className;
|
||||
|
||||
return {modal, closeDialog};
|
||||
return {modal, closeDialog, onFinishedProm};
|
||||
}
|
||||
|
||||
_getCloseFn(modal, props) {
|
||||
return (...args) => {
|
||||
const deferred = Promise.defer();
|
||||
return [(...args) => {
|
||||
deferred.resolve(args);
|
||||
if (props && props.onFinished) props.onFinished.apply(null, args);
|
||||
const i = this._modals.indexOf(modal);
|
||||
if (i >= 0) {
|
||||
|
@ -223,7 +226,7 @@ class ModalManager {
|
|||
}
|
||||
|
||||
this._reRender();
|
||||
};
|
||||
}, deferred.promise];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -256,7 +259,7 @@ class ModalManager {
|
|||
* @returns {object} Object with 'close' parameter being a function that will close the dialog
|
||||
*/
|
||||
createDialogAsync(prom, props, className, isPriorityModal, isStaticModal) {
|
||||
const {modal, closeDialog} = this._buildModal(prom, props, className);
|
||||
const {modal, closeDialog, onFinishedProm} = this._buildModal(prom, props, className);
|
||||
|
||||
if (isPriorityModal) {
|
||||
// XXX: This is destructive
|
||||
|
@ -269,15 +272,21 @@ class ModalManager {
|
|||
}
|
||||
|
||||
this._reRender();
|
||||
return {close: closeDialog};
|
||||
return {
|
||||
close: closeDialog,
|
||||
finished: onFinishedProm,
|
||||
};
|
||||
}
|
||||
|
||||
appendDialogAsync(prom, props, className) {
|
||||
const {modal, closeDialog} = this._buildModal(prom, props, className);
|
||||
const {modal, closeDialog, onFinishedProm} = this._buildModal(prom, props, className);
|
||||
|
||||
this._modals.push(modal);
|
||||
this._reRender();
|
||||
return {close: closeDialog};
|
||||
return {
|
||||
close: closeDialog,
|
||||
finished: onFinishedProm,
|
||||
};
|
||||
}
|
||||
|
||||
closeAll() {
|
||||
|
|
|
@ -36,7 +36,11 @@ class PasswordReset {
|
|||
idBaseUrl: identityUrl,
|
||||
});
|
||||
this.clientSecret = this.client.generateClientSecret();
|
||||
this.identityServerDomain = identityUrl.split("://")[1];
|
||||
this.identityServerDomain = identityUrl ? identityUrl.split("://")[1] : null;
|
||||
}
|
||||
|
||||
doesServerRequireIdServerParam() {
|
||||
return this.client.doesServerRequireIdServerParam();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -51,11 +51,18 @@ export function showStartChatInviteDialog() {
|
|||
Modal.createTrackedDialog('Start a chat', '', AddressPickerDialog, {
|
||||
title: _t('Start a chat'),
|
||||
description: _t("Who would you like to communicate with?"),
|
||||
placeholder: _t("Email, name or Matrix ID"),
|
||||
placeholder: (validAddressTypes) => {
|
||||
// The set of valid address type can be mutated inside the dialog
|
||||
// when you first have no IS but agree to use one in the dialog.
|
||||
if (validAddressTypes.includes('email')) {
|
||||
return _t("Email, name or Matrix ID");
|
||||
}
|
||||
return _t("Name or Matrix ID");
|
||||
},
|
||||
validAddressTypes,
|
||||
button: _t("Start Chat"),
|
||||
onFinished: _onStartDmFinished,
|
||||
});
|
||||
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
||||
}
|
||||
|
||||
export function showRoomInviteDialog(roomId) {
|
||||
|
@ -68,14 +75,20 @@ export function showRoomInviteDialog(roomId) {
|
|||
|
||||
Modal.createTrackedDialog('Chat Invite', '', AddressPickerDialog, {
|
||||
title: _t('Invite new room members'),
|
||||
description: _t('Who would you like to add to this room?'),
|
||||
button: _t('Send Invites'),
|
||||
placeholder: _t("Email, name or Matrix ID"),
|
||||
placeholder: (validAddressTypes) => {
|
||||
// The set of valid address type can be mutated inside the dialog
|
||||
// when you first have no IS but agree to use one in the dialog.
|
||||
if (validAddressTypes.includes('email')) {
|
||||
return _t("Email, name or Matrix ID");
|
||||
}
|
||||
return _t("Name or Matrix ID");
|
||||
},
|
||||
validAddressTypes,
|
||||
onFinished: (shouldInvite, addrs) => {
|
||||
_onRoomInviteFinished(roomId, shouldInvite, addrs);
|
||||
},
|
||||
});
|
||||
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -29,20 +29,43 @@ import * as Matrix from 'matrix-js-sdk';
|
|||
// The version of the integration manager API we're intending to work with
|
||||
const imApiVersion = "1.1";
|
||||
|
||||
class ScalarAuthClient {
|
||||
constructor() {
|
||||
export default class ScalarAuthClient {
|
||||
constructor(apiUrl, uiUrl) {
|
||||
this.apiUrl = apiUrl;
|
||||
this.uiUrl = uiUrl;
|
||||
this.scalarToken = null;
|
||||
// `undefined` to allow `startTermsFlow` to fallback to a default
|
||||
// callback if this is unset.
|
||||
this.termsInteractionCallback = undefined;
|
||||
|
||||
// We try and store the token on a per-manager basis, but need a fallback
|
||||
// for the default manager.
|
||||
const configApiUrl = SdkConfig.get()['integrations_rest_url'];
|
||||
const configUiUrl = SdkConfig.get()['integrations_ui_url'];
|
||||
this.isDefaultManager = apiUrl === configApiUrl && configUiUrl === uiUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if setting up a ScalarAuthClient is even possible
|
||||
* @returns {boolean} true if possible, false otherwise.
|
||||
*/
|
||||
static isPossible() {
|
||||
return SdkConfig.get()['integrations_rest_url'] && SdkConfig.get()['integrations_ui_url'];
|
||||
_writeTokenToStore() {
|
||||
window.localStorage.setItem("mx_scalar_token_at_" + this.apiUrl, this.scalarToken);
|
||||
if (this.isDefaultManager) {
|
||||
// We remove the old token from storage to migrate upwards. This is safe
|
||||
// to do because even if the user switches to /app when this is on /develop
|
||||
// they'll at worst register for a new token.
|
||||
window.localStorage.removeItem("mx_scalar_token"); // no-op when not present
|
||||
}
|
||||
}
|
||||
|
||||
_readTokenFromStore() {
|
||||
let token = window.localStorage.getItem("mx_scalar_token_at_" + this.apiUrl);
|
||||
if (!token && this.isDefaultManager) {
|
||||
token = window.localStorage.getItem("mx_scalar_token");
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
_readToken() {
|
||||
if (this.scalarToken) return this.scalarToken;
|
||||
return this._readTokenFromStore();
|
||||
}
|
||||
|
||||
setTermsInteractionCallback(callback) {
|
||||
|
@ -61,8 +84,7 @@ class ScalarAuthClient {
|
|||
|
||||
// Returns a promise that resolves to a scalar_token string
|
||||
getScalarToken() {
|
||||
let token = this.scalarToken;
|
||||
if (!token) token = window.localStorage.getItem("mx_scalar_token");
|
||||
const token = this._readToken();
|
||||
|
||||
if (!token) {
|
||||
return this.registerForToken();
|
||||
|
@ -78,7 +100,7 @@ class ScalarAuthClient {
|
|||
}
|
||||
|
||||
_getAccountName(token) {
|
||||
const url = SdkConfig.get().integrations_rest_url + "/account";
|
||||
const url = this.apiUrl + "/account";
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
request({
|
||||
|
@ -111,7 +133,7 @@ class ScalarAuthClient {
|
|||
return token;
|
||||
}).catch((e) => {
|
||||
if (e instanceof TermsNotSignedError) {
|
||||
console.log("Integrations manager requires new terms to be agreed to");
|
||||
console.log("Integration manager requires new terms to be agreed to");
|
||||
// The terms endpoints are new and so live on standard _matrix prefixes,
|
||||
// but IM rest urls are currently configured with paths, so remove the
|
||||
// path from the base URL before passing it to the js-sdk
|
||||
|
@ -126,7 +148,7 @@ class ScalarAuthClient {
|
|||
// Once we've fully transitioned to _matrix URLs, we can give people
|
||||
// a grace period to update their configs, then use the rest url as
|
||||
// a regular base url.
|
||||
const parsedImRestUrl = url.parse(SdkConfig.get().integrations_rest_url);
|
||||
const parsedImRestUrl = url.parse(this.apiUrl);
|
||||
parsedImRestUrl.path = '';
|
||||
parsedImRestUrl.pathname = '';
|
||||
return startTermsFlow([new Service(
|
||||
|
@ -147,17 +169,18 @@ class ScalarAuthClient {
|
|||
return MatrixClientPeg.get().getOpenIdToken().then((tokenObject) => {
|
||||
// Now we can send that to scalar and exchange it for a scalar token
|
||||
return this.exchangeForScalarToken(tokenObject);
|
||||
}).then((tokenObject) => {
|
||||
}).then((token) => {
|
||||
// Validate it (this mostly checks to see if the IM needs us to agree to some terms)
|
||||
return this._checkToken(tokenObject);
|
||||
}).then((tokenObject) => {
|
||||
window.localStorage.setItem("mx_scalar_token", tokenObject);
|
||||
return tokenObject;
|
||||
return this._checkToken(token);
|
||||
}).then((token) => {
|
||||
this.scalarToken = token;
|
||||
this._writeTokenToStore();
|
||||
return token;
|
||||
});
|
||||
}
|
||||
|
||||
exchangeForScalarToken(openidTokenObject) {
|
||||
const scalarRestUrl = SdkConfig.get().integrations_rest_url;
|
||||
const scalarRestUrl = this.apiUrl;
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
request({
|
||||
|
@ -181,7 +204,7 @@ class ScalarAuthClient {
|
|||
}
|
||||
|
||||
getScalarPageTitle(url) {
|
||||
let scalarPageLookupUrl = SdkConfig.get().integrations_rest_url + '/widgets/title_lookup';
|
||||
let scalarPageLookupUrl = this.apiUrl + '/widgets/title_lookup';
|
||||
scalarPageLookupUrl = this.getStarterLink(scalarPageLookupUrl);
|
||||
scalarPageLookupUrl += '&curl=' + encodeURIComponent(url);
|
||||
|
||||
|
@ -217,7 +240,7 @@ class ScalarAuthClient {
|
|||
* @return {Promise} Resolves on completion
|
||||
*/
|
||||
disableWidgetAssets(widgetType, widgetId) {
|
||||
let url = SdkConfig.get().integrations_rest_url + '/widgets/set_assets_state';
|
||||
let url = this.apiUrl + '/widgets/set_assets_state';
|
||||
url = this.getStarterLink(url);
|
||||
return new Promise((resolve, reject) => {
|
||||
request({
|
||||
|
@ -246,7 +269,7 @@ class ScalarAuthClient {
|
|||
getScalarInterfaceUrlForRoom(room, screen, id) {
|
||||
const roomId = room.roomId;
|
||||
const roomName = room.name;
|
||||
let url = SdkConfig.get().integrations_ui_url;
|
||||
let url = this.uiUrl;
|
||||
url += "?scalar_token=" + encodeURIComponent(this.scalarToken);
|
||||
url += "&room_id=" + encodeURIComponent(roomId);
|
||||
url += "&room_name=" + encodeURIComponent(roomName);
|
||||
|
@ -264,5 +287,3 @@ class ScalarAuthClient {
|
|||
return starterLinkUrl + "?scalar_token=" + encodeURIComponent(this.scalarToken);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ScalarAuthClient;
|
||||
|
|
|
@ -232,13 +232,13 @@ Example:
|
|||
}
|
||||
*/
|
||||
|
||||
import SdkConfig from './SdkConfig';
|
||||
import MatrixClientPeg from './MatrixClientPeg';
|
||||
import { MatrixEvent } from 'matrix-js-sdk';
|
||||
import dis from './dispatcher';
|
||||
import WidgetUtils from './utils/WidgetUtils';
|
||||
import RoomViewStore from './stores/RoomViewStore';
|
||||
import { _t } from './languageHandler';
|
||||
import {IntegrationManagers} from "./integrations/IntegrationManagers";
|
||||
|
||||
function sendResponse(event, res) {
|
||||
const data = JSON.parse(JSON.stringify(event.data));
|
||||
|
@ -548,7 +548,8 @@ const onMessage = function(event) {
|
|||
// (See https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage)
|
||||
let configUrl;
|
||||
try {
|
||||
configUrl = new URL(SdkConfig.get().integrations_ui_url);
|
||||
if (!openManagerUrl) openManagerUrl = IntegrationManagers.sharedInstance().getPrimaryManager().uiUrl;
|
||||
configUrl = new URL(openManagerUrl);
|
||||
} catch (e) {
|
||||
// No integrations UI URL, ignore silently.
|
||||
return;
|
||||
|
@ -656,6 +657,7 @@ const onMessage = function(event) {
|
|||
};
|
||||
|
||||
let listenerCount = 0;
|
||||
let openManagerUrl = null;
|
||||
module.exports = {
|
||||
startListening: function() {
|
||||
if (listenerCount === 0) {
|
||||
|
@ -678,4 +680,8 @@ module.exports = {
|
|||
console.error(e);
|
||||
}
|
||||
},
|
||||
|
||||
setOpenManagerUrl: function(url) {
|
||||
openManagerUrl = url;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
//@flow
|
||||
/*
|
||||
Copyright 2017 Aviral Dasgupta
|
||||
|
||||
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 _clamp from 'lodash/clamp';
|
||||
|
||||
export default class SendHistoryManager {
|
||||
history: Array<HistoryItem> = [];
|
||||
prefix: string;
|
||||
lastIndex: number = 0; // used for indexing the storage
|
||||
currentIndex: number = 0; // used for indexing the loaded validated history Array
|
||||
|
||||
constructor(roomId: string, prefix: string) {
|
||||
this.prefix = prefix + roomId;
|
||||
|
||||
// TODO: Performance issues?
|
||||
let index = 0;
|
||||
let itemJSON;
|
||||
|
||||
while (itemJSON = sessionStorage.getItem(`${this.prefix}[${index}]`)) {
|
||||
try {
|
||||
const serializedParts = JSON.parse(itemJSON);
|
||||
this.history.push(serializedParts);
|
||||
} catch (e) {
|
||||
console.warn("Throwing away unserialisable history", e);
|
||||
break;
|
||||
}
|
||||
++index;
|
||||
}
|
||||
this.lastIndex = this.history.length - 1;
|
||||
// reset currentIndex to account for any unserialisable history
|
||||
this.currentIndex = this.lastIndex + 1;
|
||||
}
|
||||
|
||||
save(editorModel: Object) {
|
||||
const serializedParts = editorModel.serializeParts();
|
||||
this.history.push(serializedParts);
|
||||
this.currentIndex = this.history.length;
|
||||
this.lastIndex += 1;
|
||||
sessionStorage.setItem(`${this.prefix}[${this.lastIndex}]`, JSON.stringify(serializedParts));
|
||||
}
|
||||
|
||||
getItem(offset: number): ?HistoryItem {
|
||||
this.currentIndex = _clamp(this.currentIndex + offset, 0, this.history.length - 1);
|
||||
return this.history[this.currentIndex];
|
||||
}
|
||||
}
|
|
@ -31,6 +31,9 @@ import QuestionDialog from "./components/views/dialogs/QuestionDialog";
|
|||
import WidgetUtils from "./utils/WidgetUtils";
|
||||
import {textToHtmlRainbow} from "./utils/colour";
|
||||
import Promise from "bluebird";
|
||||
import { getAddressType } from './UserAddress';
|
||||
import { abbreviateUrl } from './utils/UrlUtils';
|
||||
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from './utils/IdentityServerUtils';
|
||||
|
||||
const singleMxcUpload = async () => {
|
||||
return new Promise((resolve) => {
|
||||
|
@ -115,7 +118,15 @@ export const CommandMap = {
|
|||
},
|
||||
category: CommandCategories.messages,
|
||||
}),
|
||||
|
||||
plain: new Command({
|
||||
name: 'plain',
|
||||
args: '<message>',
|
||||
description: _td('Sends a message as plain text, without interpreting it as markdown'),
|
||||
runFn: function(roomId, messages) {
|
||||
return success(MatrixClientPeg.get().sendTextMessage(roomId, messages));
|
||||
},
|
||||
category: CommandCategories.messages,
|
||||
}),
|
||||
ddg: new Command({
|
||||
name: 'ddg',
|
||||
args: '<query>',
|
||||
|
@ -139,8 +150,13 @@ export const CommandMap = {
|
|||
description: _td('Upgrades a room to a new version'),
|
||||
runFn: function(roomId, args) {
|
||||
if (args) {
|
||||
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||
Modal.createTrackedDialog('Slash Commands', 'upgrade room confirmation',
|
||||
const cli = MatrixClientPeg.get();
|
||||
const room = cli.getRoom(roomId);
|
||||
if (!room.currentState.mayClientSendStateEvent("m.room.tombstone", cli)) {
|
||||
return reject(_t("You do not have the required permissions to use this command."));
|
||||
}
|
||||
|
||||
const {finished} = Modal.createTrackedDialog('Slash Commands', 'upgrade room confirmation',
|
||||
QuestionDialog, {
|
||||
title: _t('Room upgrade confirmation'),
|
||||
description: (
|
||||
|
@ -198,13 +214,13 @@ export const CommandMap = {
|
|||
</div>
|
||||
),
|
||||
button: _t("Upgrade"),
|
||||
onFinished: (confirm) => {
|
||||
if (!confirm) return;
|
||||
|
||||
MatrixClientPeg.get().upgradeRoom(roomId, args);
|
||||
},
|
||||
});
|
||||
return success();
|
||||
|
||||
return success(finished.then((confirm) => {
|
||||
if (!confirm) return;
|
||||
|
||||
return cli.upgradeRoom(roomId, args);
|
||||
}));
|
||||
}
|
||||
return reject(this.getUsage());
|
||||
},
|
||||
|
@ -337,11 +353,46 @@ export const CommandMap = {
|
|||
if (matches) {
|
||||
// We use a MultiInviter to re-use the invite logic, even though
|
||||
// we're only inviting one user.
|
||||
const userId = matches[1];
|
||||
const address = matches[1];
|
||||
// If we need an identity server but don't have one, things
|
||||
// get a bit more complex here, but we try to show something
|
||||
// meaningful.
|
||||
let finished = Promise.resolve();
|
||||
if (
|
||||
getAddressType(address) === 'email' &&
|
||||
!MatrixClientPeg.get().getIdentityServerUrl()
|
||||
) {
|
||||
const defaultIdentityServerUrl = getDefaultIdentityServerUrl();
|
||||
if (defaultIdentityServerUrl) {
|
||||
({ finished } = Modal.createTrackedDialog('Slash Commands', 'Identity server',
|
||||
QuestionDialog, {
|
||||
title: _t("Use an identity server"),
|
||||
description: <p>{_t(
|
||||
"Use an identity server to invite by email. " +
|
||||
"Click continue to use the default identity server " +
|
||||
"(%(defaultIdentityServerName)s) or manage in Settings.",
|
||||
{
|
||||
defaultIdentityServerName: abbreviateUrl(defaultIdentityServerUrl),
|
||||
},
|
||||
)}</p>,
|
||||
button: _t("Continue"),
|
||||
},
|
||||
));
|
||||
} else {
|
||||
return reject(_t("Use an identity server to invite by email. Manage in Settings."));
|
||||
}
|
||||
}
|
||||
const inviter = new MultiInviter(roomId);
|
||||
return success(inviter.invite([userId]).then(() => {
|
||||
if (inviter.getCompletionState(userId) !== "invited") {
|
||||
throw new Error(inviter.getErrorText(userId));
|
||||
return success(finished.then(([useDefault] = []) => {
|
||||
if (useDefault) {
|
||||
useDefaultIdentityServer();
|
||||
} else if (useDefault === false) {
|
||||
throw new Error(_t("Use an identity server to invite by email. Manage in Settings."));
|
||||
}
|
||||
return inviter.invite([address]);
|
||||
}).then(() => {
|
||||
if (inviter.getCompletionState(address) !== "invited") {
|
||||
throw new Error(inviter.getErrorText(address));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ class HistoryItem {
|
|||
}
|
||||
}
|
||||
|
||||
export default class ComposerHistoryManager {
|
||||
export default class SlateComposerHistoryManager {
|
||||
history: Array<HistoryItem> = [];
|
||||
prefix: string;
|
||||
lastIndex: number = 0; // used for indexing the storage
|
11
src/Terms.js
11
src/Terms.js
|
@ -116,16 +116,21 @@ export async function startTermsFlow(
|
|||
}
|
||||
|
||||
// if there's anything left to agree to, prompt the user
|
||||
const numAcceptedBeforeAgreement = agreedUrlSet.size;
|
||||
if (unagreedPoliciesAndServicePairs.length > 0) {
|
||||
const newlyAgreedUrls = await interactionCallback(unagreedPoliciesAndServicePairs, [...agreedUrlSet]);
|
||||
console.log("User has agreed to URLs", newlyAgreedUrls);
|
||||
agreedUrlSet = new Set(newlyAgreedUrls);
|
||||
// Merge with previously agreed URLs
|
||||
newlyAgreedUrls.forEach(url => agreedUrlSet.add(url));
|
||||
} else {
|
||||
console.log("User has already agreed to all required policies");
|
||||
}
|
||||
|
||||
const newAcceptedTerms = { accepted: Array.from(agreedUrlSet) };
|
||||
await MatrixClientPeg.get().setAccountData('m.accepted_terms', newAcceptedTerms);
|
||||
// We only ever add to the set of URLs, so if anything has changed then we'd see a different length
|
||||
if (agreedUrlSet.size !== numAcceptedBeforeAgreement) {
|
||||
const newAcceptedTerms = {accepted: Array.from(agreedUrlSet)};
|
||||
await MatrixClientPeg.get().setAccountData('m.accepted_terms', newAcceptedTerms);
|
||||
}
|
||||
|
||||
const agreePromises = policiesAndServicePairs.map((policiesAndService) => {
|
||||
// filter the agreed URL list for ones that are actually for this service
|
||||
|
|
|
@ -15,12 +15,13 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
const React = require("react");
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
const sdk = require('../../../index');
|
||||
const MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||
|
||||
module.exports = React.createClass({
|
||||
module.exports = createReactClass({
|
||||
displayName: 'EncryptedEventDialog',
|
||||
|
||||
propTypes: {
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
import FileSaver from 'file-saver';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
|
@ -26,7 +27,7 @@ import sdk from '../../../index';
|
|||
const PHASE_EDIT = 1;
|
||||
const PHASE_EXPORTING = 2;
|
||||
|
||||
export default React.createClass({
|
||||
export default createReactClass({
|
||||
displayName: 'ExportE2eKeysDialog',
|
||||
|
||||
propTypes: {
|
||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption';
|
||||
|
@ -37,7 +38,7 @@ function readFileAsArrayBuffer(file) {
|
|||
const PHASE_EDIT = 1;
|
||||
const PHASE_IMPORTING = 2;
|
||||
|
||||
export default React.createClass({
|
||||
export default createReactClass({
|
||||
displayName: 'ImportE2eKeysDialog',
|
||||
|
||||
propTypes: {
|
||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import sdk from '../../../../index';
|
||||
import MatrixClientPeg from '../../../../MatrixClientPeg';
|
||||
import { scorePassword } from '../../../../utils/PasswordScorer';
|
||||
|
@ -48,7 +49,7 @@ function selectText(target) {
|
|||
* Walks the user through the process of creating an e2e key backup
|
||||
* on the server.
|
||||
*/
|
||||
export default React.createClass({
|
||||
export default createReactClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
phase: PHASE_PASSPHRASE,
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 IdentityAuthClient from './IdentityAuthClient';
|
||||
|
||||
export async function getThreepidsWithBindStatus(client, filterMedium) {
|
||||
const userId = client.getUserId();
|
||||
|
||||
let { threepids } = await client.getThreePids();
|
||||
if (filterMedium) {
|
||||
threepids = threepids.filter((a) => a.medium === filterMedium);
|
||||
}
|
||||
|
||||
// Check bind status assuming we have an IS and terms are agreed
|
||||
if (threepids.length > 0 && !!client.getIdentityServerUrl()) {
|
||||
try {
|
||||
const authClient = new IdentityAuthClient();
|
||||
const identityAccessToken = await authClient.getAccessToken({ check: false });
|
||||
|
||||
// Restructure for lookup query
|
||||
const query = threepids.map(({ medium, address }) => [medium, address]);
|
||||
const lookupResults = await client.bulkLookupThreePids(query, identityAccessToken);
|
||||
|
||||
// Record which are already bound
|
||||
for (const [medium, address, mxid] of lookupResults.threepids) {
|
||||
if (mxid !== userId) {
|
||||
continue;
|
||||
}
|
||||
if (filterMedium && medium !== filterMedium) {
|
||||
continue;
|
||||
}
|
||||
const threepid = threepids.find(e => e.medium === medium && e.address === address);
|
||||
if (!threepid) continue;
|
||||
threepid.bound = true;
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore terms errors here and assume other flows handle this
|
||||
if (!(e.errcode === "M_TERMS_NOT_SIGNED")) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return threepids;
|
||||
}
|
|
@ -16,10 +16,11 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
module.exports = createReactClass({
|
||||
displayName: 'CompatibilityPage',
|
||||
propTypes: {
|
||||
onAccept: PropTypes.func,
|
||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Matrix from 'matrix-js-sdk';
|
||||
|
@ -25,7 +26,7 @@ import { _t } from '../../languageHandler';
|
|||
/*
|
||||
* Component which shows the filtered file using a TimelinePanel
|
||||
*/
|
||||
const FilePanel = React.createClass({
|
||||
const FilePanel = createReactClass({
|
||||
displayName: 'FilePanel',
|
||||
|
||||
propTypes: {
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import Promise from 'bluebird';
|
||||
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||
|
@ -67,7 +68,7 @@ const UserSummaryType = PropTypes.shape({
|
|||
}).isRequired,
|
||||
});
|
||||
|
||||
const CategoryRoomList = React.createClass({
|
||||
const CategoryRoomList = createReactClass({
|
||||
displayName: 'CategoryRoomList',
|
||||
|
||||
props: {
|
||||
|
@ -119,7 +120,7 @@ const CategoryRoomList = React.createClass({
|
|||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
@ -156,7 +157,7 @@ const CategoryRoomList = React.createClass({
|
|||
},
|
||||
});
|
||||
|
||||
const FeaturedRoom = React.createClass({
|
||||
const FeaturedRoom = createReactClass({
|
||||
displayName: 'FeaturedRoom',
|
||||
|
||||
props: {
|
||||
|
@ -244,7 +245,7 @@ const FeaturedRoom = React.createClass({
|
|||
},
|
||||
});
|
||||
|
||||
const RoleUserList = React.createClass({
|
||||
const RoleUserList = createReactClass({
|
||||
displayName: 'RoleUserList',
|
||||
|
||||
props: {
|
||||
|
@ -296,7 +297,7 @@ const RoleUserList = React.createClass({
|
|||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
@ -327,7 +328,7 @@ const RoleUserList = React.createClass({
|
|||
},
|
||||
});
|
||||
|
||||
const FeaturedUser = React.createClass({
|
||||
const FeaturedUser = createReactClass({
|
||||
displayName: 'FeaturedUser',
|
||||
|
||||
props: {
|
||||
|
@ -399,7 +400,7 @@ const FeaturedUser = React.createClass({
|
|||
const GROUP_JOINPOLICY_OPEN = "open";
|
||||
const GROUP_JOINPOLICY_INVITE = "invite";
|
||||
|
||||
export default React.createClass({
|
||||
export default createReactClass({
|
||||
displayName: 'GroupView',
|
||||
|
||||
propTypes: {
|
||||
|
|
|
@ -19,7 +19,7 @@ import PropTypes from "prop-types";
|
|||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||
|
||||
export default class IndicatorScrollbar extends React.Component {
|
||||
static PropTypes = {
|
||||
static propTypes = {
|
||||
// If true, the scrollbar will append mx_IndicatorScrollbar_leftOverflowIndicator
|
||||
// and mx_IndicatorScrollbar_rightOverflowIndicator elements to the list for positioning
|
||||
// by the parent element.
|
||||
|
|
|
@ -19,13 +19,14 @@ import Matrix from 'matrix-js-sdk';
|
|||
const InteractiveAuth = Matrix.InteractiveAuth;
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {getEntryComponentForLoginType} from '../views/auth/InteractiveAuthEntryComponents';
|
||||
|
||||
import sdk from '../../index';
|
||||
|
||||
export default React.createClass({
|
||||
export default createReactClass({
|
||||
displayName: 'InteractiveAuth',
|
||||
|
||||
propTypes: {
|
||||
|
|
|
@ -14,9 +14,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
|
@ -30,7 +29,7 @@ import {_t} from "../../languageHandler";
|
|||
import Analytics from "../../Analytics";
|
||||
|
||||
|
||||
const LeftPanel = React.createClass({
|
||||
const LeftPanel = createReactClass({
|
||||
displayName: 'LeftPanel',
|
||||
|
||||
// NB. If you add props, don't forget to update
|
||||
|
@ -82,6 +81,9 @@ const LeftPanel = React.createClass({
|
|||
if (this.state.searchFilter !== nextState.searchFilter) {
|
||||
return true;
|
||||
}
|
||||
if (this.state.searchExpanded !== nextState.searchExpanded) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
@ -204,12 +206,23 @@ const LeftPanel = React.createClass({
|
|||
if (source === "keyboard") {
|
||||
dis.dispatch({action: 'focus_composer'});
|
||||
}
|
||||
this.setState({searchExpanded: false});
|
||||
},
|
||||
|
||||
collectRoomList: function(ref) {
|
||||
this._roomList = ref;
|
||||
},
|
||||
|
||||
_onSearchFocus: function() {
|
||||
this.setState({searchExpanded: true});
|
||||
},
|
||||
|
||||
_onSearchBlur: function(event) {
|
||||
if (event.target.value.length === 0) {
|
||||
this.setState({searchExpanded: false});
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const RoomList = sdk.getComponent('rooms.RoomList');
|
||||
const RoomBreadcrumbs = sdk.getComponent('rooms.RoomBreadcrumbs');
|
||||
|
@ -218,6 +231,7 @@ const LeftPanel = React.createClass({
|
|||
const TopLeftMenuButton = sdk.getComponent('structures.TopLeftMenuButton');
|
||||
const SearchBox = sdk.getComponent('structures.SearchBox');
|
||||
const CallPreview = sdk.getComponent('voip.CallPreview');
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
|
||||
const tagPanelEnabled = SettingsStore.getValue("TagPanel.enableTagPanel");
|
||||
let tagPanelContainer;
|
||||
|
@ -241,11 +255,23 @@ const LeftPanel = React.createClass({
|
|||
},
|
||||
);
|
||||
|
||||
let exploreButton;
|
||||
if (!this.props.collapsed) {
|
||||
exploreButton = (
|
||||
<div className={classNames("mx_LeftPanel_explore", {"mx_LeftPanel_explore_hidden": this.state.searchExpanded})}>
|
||||
<AccessibleButton onClick={() => dis.dispatch({action: 'view_room_directory'})}>{_t("Explore")}</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const searchBox = (<SearchBox
|
||||
enableRoomSearchFocus={true}
|
||||
placeholder={ _t('Filter room names') }
|
||||
blurredPlaceholder={ _t('Filter') }
|
||||
placeholder={ _t('Filter rooms…') }
|
||||
onSearch={ this.onSearch }
|
||||
onCleared={ this.onSearchCleared }
|
||||
onFocus={this._onSearchFocus}
|
||||
onBlur={this._onSearchBlur}
|
||||
collapsed={this.props.collapsed} />);
|
||||
|
||||
let breadcrumbs;
|
||||
|
@ -259,7 +285,10 @@ const LeftPanel = React.createClass({
|
|||
<aside className={"mx_LeftPanel dark-panel"} onKeyDown={ this._onKeyDown } onFocus={ this._onFocus } onBlur={ this._onBlur }>
|
||||
<TopLeftMenuButton collapsed={ this.props.collapsed } />
|
||||
{ breadcrumbs }
|
||||
{ searchBox }
|
||||
<div className="mx_LeftPanel_exploreAndFilterRow">
|
||||
{ exploreButton }
|
||||
{ searchBox }
|
||||
</div>
|
||||
<CallPreview ConferenceHandler={VectorConferenceHandler} />
|
||||
<RoomList
|
||||
ref={this.collectRoomList}
|
||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
|||
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import { DragDropContext } from 'react-beautiful-dnd';
|
||||
|
||||
|
@ -58,7 +59,7 @@ function canElementReceiveInput(el) {
|
|||
*
|
||||
* Components mounted below us can access the matrix client via the react context.
|
||||
*/
|
||||
const LoggedInView = React.createClass({
|
||||
const LoggedInView = createReactClass({
|
||||
displayName: 'LoggedInView',
|
||||
|
||||
propTypes: {
|
||||
|
@ -349,7 +350,8 @@ const LoggedInView = React.createClass({
|
|||
|
||||
let handled = false;
|
||||
const ctrlCmdOnly = isOnlyCtrlOrCmdKeyEvent(ev);
|
||||
const hasModifier = ev.altKey || ev.ctrlKey || ev.metaKey || ev.shiftKey;
|
||||
const hasModifier = ev.altKey || ev.ctrlKey || ev.metaKey || ev.shiftKey ||
|
||||
ev.key === "Alt" || ev.key === "Control" || ev.key === "Meta" || ev.key === "Shift";
|
||||
|
||||
switch (ev.keyCode) {
|
||||
case KeyCode.PAGE_UP:
|
||||
|
|
|
@ -20,6 +20,7 @@ limitations under the License.
|
|||
import Promise from 'bluebird';
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import Matrix from "matrix-js-sdk";
|
||||
|
||||
|
@ -106,7 +107,7 @@ const ONBOARDING_FLOW_STARTERS = [
|
|||
'view_create_group',
|
||||
];
|
||||
|
||||
export default React.createClass({
|
||||
export default createReactClass({
|
||||
// we export this so that the integration tests can use it :-S
|
||||
statics: {
|
||||
VIEWS: VIEWS,
|
||||
|
@ -446,6 +447,29 @@ export default React.createClass({
|
|||
}
|
||||
|
||||
switch (payload.action) {
|
||||
case 'MatrixActions.accountData':
|
||||
// XXX: This is a collection of several hacks to solve a minor problem. We want to
|
||||
// update our local state when the ID server changes, but don't want to put that in
|
||||
// the js-sdk as we'd be then dictating how all consumers need to behave. However,
|
||||
// this component is already bloated and we probably don't want this tiny logic in
|
||||
// here, but there's no better place in the react-sdk for it. Additionally, we're
|
||||
// abusing the MatrixActionCreator stuff to avoid errors on dispatches.
|
||||
if (payload.event_type === 'm.identity_server') {
|
||||
const fullUrl = payload.event_content ? payload.event_content['base_url'] : null;
|
||||
if (!fullUrl) {
|
||||
MatrixClientPeg.get().setIdentityServerUrl(null);
|
||||
localStorage.removeItem("mx_is_access_token");
|
||||
localStorage.removeItem("mx_is_url");
|
||||
} else {
|
||||
MatrixClientPeg.get().setIdentityServerUrl(fullUrl);
|
||||
localStorage.removeItem("mx_is_access_token"); // clear token
|
||||
localStorage.setItem("mx_is_url", fullUrl); // XXX: Do we still need this?
|
||||
}
|
||||
|
||||
// redispatch the change with a more specific action
|
||||
dis.dispatch({action: 'id_server_changed'});
|
||||
}
|
||||
break;
|
||||
case 'logout':
|
||||
Lifecycle.logout();
|
||||
break;
|
||||
|
@ -931,18 +955,17 @@ export default React.createClass({
|
|||
}).close;
|
||||
},
|
||||
|
||||
_createRoom: function() {
|
||||
_createRoom: async function() {
|
||||
const CreateRoomDialog = sdk.getComponent('dialogs.CreateRoomDialog');
|
||||
Modal.createTrackedDialog('Create Room', '', CreateRoomDialog, {
|
||||
onFinished: (shouldCreate, name, noFederate) => {
|
||||
if (shouldCreate) {
|
||||
const createOpts = {};
|
||||
if (name) createOpts.name = name;
|
||||
if (noFederate) createOpts.creation_content = {'m.federate': false};
|
||||
createRoom({createOpts}).done();
|
||||
}
|
||||
},
|
||||
});
|
||||
const modal = Modal.createTrackedDialog('Create Room', '', CreateRoomDialog);
|
||||
|
||||
const [shouldCreate, name, noFederate] = await modal.finished;
|
||||
if (shouldCreate) {
|
||||
const createOpts = {};
|
||||
if (name) createOpts.name = name;
|
||||
if (noFederate) createOpts.creation_content = {'m.federate': false};
|
||||
createRoom({createOpts}).done();
|
||||
}
|
||||
},
|
||||
|
||||
_chatCreateOrReuse: function(userId) {
|
||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
|||
/* global Velocity */
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import ReactDOM from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
@ -35,7 +36,7 @@ const isMembershipChange = (e) => e.getType() === 'm.room.member' || e.getType()
|
|||
|
||||
/* (almost) stateless UI component which builds the event tiles in the room timeline.
|
||||
*/
|
||||
module.exports = React.createClass({
|
||||
module.exports = createReactClass({
|
||||
displayName: 'MessagePanel',
|
||||
|
||||
propTypes: {
|
||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import sdk from '../../index';
|
||||
|
@ -23,7 +24,7 @@ import { _t } from '../../languageHandler';
|
|||
import dis from '../../dispatcher';
|
||||
import AccessibleButton from '../views/elements/AccessibleButton';
|
||||
|
||||
export default React.createClass({
|
||||
export default createReactClass({
|
||||
displayName: 'MyGroups',
|
||||
|
||||
getInitialState: function() {
|
||||
|
|
|
@ -15,7 +15,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const React = require('react');
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import { _t } from '../../languageHandler';
|
||||
const sdk = require('../../index');
|
||||
const MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
|
@ -23,7 +24,7 @@ const MatrixClientPeg = require("../../MatrixClientPeg");
|
|||
/*
|
||||
* Component which shows the global notification list using a TimelinePanel
|
||||
*/
|
||||
const NotificationPanel = React.createClass({
|
||||
const NotificationPanel = createReactClass({
|
||||
displayName: 'NotificationPanel',
|
||||
|
||||
propTypes: {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -15,9 +16,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const React = require('react');
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
|
||||
const MatrixClientPeg = require('../../MatrixClientPeg');
|
||||
const ContentRepo = require("matrix-js-sdk").ContentRepo;
|
||||
|
@ -39,7 +39,7 @@ function track(action) {
|
|||
Analytics.trackEvent('RoomDirectory', action);
|
||||
}
|
||||
|
||||
module.exports = React.createClass({
|
||||
module.exports = createReactClass({
|
||||
displayName: 'RoomDirectory',
|
||||
|
||||
propTypes: {
|
||||
|
@ -141,6 +141,10 @@ module.exports = React.createClass({
|
|||
getMoreRooms: function() {
|
||||
if (!MatrixClientPeg.get()) return Promise.resolve();
|
||||
|
||||
this.setState({
|
||||
loading: true,
|
||||
});
|
||||
|
||||
const my_filter_string = this.state.filterString;
|
||||
const my_server = this.state.roomServer;
|
||||
// remember the next batch token when we sent the request
|
||||
|
@ -322,12 +326,7 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
onCreateRoomClicked: function() {
|
||||
this.props.onFinished();
|
||||
dis.dispatch({action: 'view_create_room'});
|
||||
},
|
||||
|
||||
onJoinClick: function(alias) {
|
||||
onJoinFromSearchClick: function(alias) {
|
||||
// If we don't have a particular instance id selected, just show that rooms alias
|
||||
if (!this.state.instanceId) {
|
||||
// If the user specified an alias without a domain, add on whichever server is selected
|
||||
|
@ -369,6 +368,39 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
onPreviewClick: function(room) {
|
||||
this.props.onFinished();
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: room.room_id,
|
||||
should_peek: true,
|
||||
});
|
||||
},
|
||||
|
||||
onViewClick: function(room) {
|
||||
this.props.onFinished();
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: room.room_id,
|
||||
should_peek: false,
|
||||
});
|
||||
},
|
||||
|
||||
onJoinClick: function(room) {
|
||||
this.props.onFinished();
|
||||
MatrixClientPeg.get().joinRoom(room.room_id);
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: room.room_id,
|
||||
joining: true,
|
||||
});
|
||||
},
|
||||
|
||||
onCreateRoomClick: function(room) {
|
||||
this.props.onFinished();
|
||||
dis.dispatch({action: 'view_create_room'});
|
||||
},
|
||||
|
||||
showRoomAlias: function(alias, autoJoin=false) {
|
||||
this.showRoom(null, alias, autoJoin);
|
||||
},
|
||||
|
@ -413,74 +445,70 @@ module.exports = React.createClass({
|
|||
dis.dispatch(payload);
|
||||
},
|
||||
|
||||
getRows: function() {
|
||||
getRow(room) {
|
||||
const client = MatrixClientPeg.get();
|
||||
const clientRoom = client.getRoom(room.room_id);
|
||||
const hasJoinedRoom = clientRoom && clientRoom.getMyMembership() === "join";
|
||||
const isGuest = client.isGuest();
|
||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
let previewButton;
|
||||
let joinOrViewButton;
|
||||
|
||||
if (!this.state.publicRooms) return [];
|
||||
|
||||
const rooms = this.state.publicRooms;
|
||||
const rows = [];
|
||||
const self = this;
|
||||
let guestRead; let guestJoin; let perms;
|
||||
for (let i = 0; i < rooms.length; i++) {
|
||||
guestRead = null;
|
||||
guestJoin = null;
|
||||
|
||||
if (rooms[i].world_readable) {
|
||||
guestRead = (
|
||||
<div className="mx_RoomDirectory_perm">{ _t('World readable') }</div>
|
||||
);
|
||||
}
|
||||
if (rooms[i].guest_can_join) {
|
||||
guestJoin = (
|
||||
<div className="mx_RoomDirectory_perm">{ _t('Guests can join') }</div>
|
||||
);
|
||||
}
|
||||
|
||||
perms = null;
|
||||
if (guestRead || guestJoin) {
|
||||
perms = <div className="mx_RoomDirectory_perms">{guestRead}{guestJoin}</div>;
|
||||
}
|
||||
|
||||
let name = rooms[i].name || get_display_alias_for_room(rooms[i]) || _t('Unnamed room');
|
||||
if (name.length > MAX_NAME_LENGTH) {
|
||||
name = `${name.substring(0, MAX_NAME_LENGTH)}...`;
|
||||
}
|
||||
|
||||
let topic = rooms[i].topic || '';
|
||||
if (topic.length > MAX_TOPIC_LENGTH) {
|
||||
topic = `${topic.substring(0, MAX_TOPIC_LENGTH)}...`;
|
||||
}
|
||||
topic = linkifyAndSanitizeHtml(topic);
|
||||
|
||||
rows.push(
|
||||
<tr key={ rooms[i].room_id }
|
||||
onClick={self.onRoomClicked.bind(self, rooms[i])}
|
||||
// cancel onMouseDown otherwise shift-clicking highlights text
|
||||
onMouseDown={(ev) => {ev.preventDefault();}}
|
||||
>
|
||||
<td className="mx_RoomDirectory_roomAvatar">
|
||||
<BaseAvatar width={24} height={24} resizeMethod='crop'
|
||||
name={ name } idName={ name }
|
||||
url={ ContentRepo.getHttpUriForMxc(
|
||||
MatrixClientPeg.get().getHomeserverUrl(),
|
||||
rooms[i].avatar_url, 24, 24, "crop") } />
|
||||
</td>
|
||||
<td className="mx_RoomDirectory_roomDescription">
|
||||
<div className="mx_RoomDirectory_name">{ name }</div>
|
||||
{ perms }
|
||||
<div className="mx_RoomDirectory_topic"
|
||||
onClick={ function(e) { e.stopPropagation(); } }
|
||||
dangerouslySetInnerHTML={{ __html: topic }} />
|
||||
<div className="mx_RoomDirectory_alias">{ get_display_alias_for_room(rooms[i]) }</div>
|
||||
</td>
|
||||
<td className="mx_RoomDirectory_roomMemberCount">
|
||||
{ rooms[i].num_joined_members }
|
||||
</td>
|
||||
</tr>,
|
||||
if (room.world_readable && !hasJoinedRoom) {
|
||||
previewButton = (
|
||||
<AccessibleButton kind="secondary" onClick={() => this.onPreviewClick(room)}>{_t("Preview")}</AccessibleButton>
|
||||
);
|
||||
}
|
||||
return rows;
|
||||
if (hasJoinedRoom) {
|
||||
joinOrViewButton = (
|
||||
<AccessibleButton kind="secondary" onClick={() => this.onViewClick(room)}>{_t("View")}</AccessibleButton>
|
||||
);
|
||||
} else if (!isGuest || room.guest_can_join) {
|
||||
joinOrViewButton = (
|
||||
<AccessibleButton kind="primary" onClick={() => this.onJoinClick(room)}>{_t("Join")}</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
||||
let name = room.name || get_display_alias_for_room(room) || _t('Unnamed room');
|
||||
if (name.length > MAX_NAME_LENGTH) {
|
||||
name = `${name.substring(0, MAX_NAME_LENGTH)}...`;
|
||||
}
|
||||
|
||||
let topic = room.topic || '';
|
||||
if (topic.length > MAX_TOPIC_LENGTH) {
|
||||
topic = `${topic.substring(0, MAX_TOPIC_LENGTH)}...`;
|
||||
}
|
||||
topic = linkifyAndSanitizeHtml(topic);
|
||||
const avatarUrl = ContentRepo.getHttpUriForMxc(
|
||||
MatrixClientPeg.get().getHomeserverUrl(),
|
||||
room.avatar_url, 32, 32, "crop",
|
||||
);
|
||||
return (
|
||||
<tr key={ room.room_id }
|
||||
onClick={() => this.onRoomClicked(room)}
|
||||
// cancel onMouseDown otherwise shift-clicking highlights text
|
||||
onMouseDown={(ev) => {ev.preventDefault();}}
|
||||
>
|
||||
<td className="mx_RoomDirectory_roomAvatar">
|
||||
<BaseAvatar width={32} height={32} resizeMethod='crop'
|
||||
name={ name } idName={ name }
|
||||
url={ avatarUrl } />
|
||||
</td>
|
||||
<td className="mx_RoomDirectory_roomDescription">
|
||||
<div className="mx_RoomDirectory_name">{ name }</div>
|
||||
<div className="mx_RoomDirectory_topic"
|
||||
onClick={ (ev) => { ev.stopPropagation(); } }
|
||||
dangerouslySetInnerHTML={{ __html: topic }} />
|
||||
<div className="mx_RoomDirectory_alias">{ get_display_alias_for_room(room) }</div>
|
||||
</td>
|
||||
<td className="mx_RoomDirectory_roomMemberCount">
|
||||
{ room.num_joined_members }
|
||||
</td>
|
||||
<td className="mx_RoomDirectory_preview">{previewButton}</td>
|
||||
<td className="mx_RoomDirectory_join">{joinOrViewButton}</td>
|
||||
</tr>
|
||||
);
|
||||
},
|
||||
|
||||
collectScrollPanel: function(element) {
|
||||
|
@ -531,20 +559,26 @@ module.exports = React.createClass({
|
|||
let content;
|
||||
if (this.state.error) {
|
||||
content = this.state.error;
|
||||
} else if (this.state.protocolsLoading || this.state.loading) {
|
||||
} else if (this.state.protocolsLoading) {
|
||||
content = <Loader />;
|
||||
} else {
|
||||
const rows = this.getRows();
|
||||
const rows = (this.state.publicRooms || []).map(room => this.getRow(room));
|
||||
// we still show the scrollpanel, at least for now, because
|
||||
// otherwise we don't fetch more because we don't get a fill
|
||||
// request from the scrollpanel because there isn't one
|
||||
|
||||
let spinner;
|
||||
if (this.state.loading) {
|
||||
spinner = <Loader />;
|
||||
}
|
||||
|
||||
let scrollpanel_content;
|
||||
if (rows.length == 0) {
|
||||
if (rows.length === 0 && !this.state.loading) {
|
||||
scrollpanel_content = <i>{ _t('No rooms to show') }</i>;
|
||||
} else {
|
||||
scrollpanel_content = <table ref="directory_table" className="mx_RoomDirectory_table">
|
||||
<tbody>
|
||||
{ this.getRows() }
|
||||
{ rows }
|
||||
</tbody>
|
||||
</table>;
|
||||
}
|
||||
|
@ -556,6 +590,7 @@ module.exports = React.createClass({
|
|||
startAtBottom={false}
|
||||
>
|
||||
{ scrollpanel_content }
|
||||
{ spinner }
|
||||
</ScrollPanel>;
|
||||
}
|
||||
|
||||
|
@ -577,10 +612,9 @@ module.exports = React.createClass({
|
|||
instance_expected_field_type = this.protocols[protocolName].field_types[last_field];
|
||||
}
|
||||
|
||||
|
||||
let placeholder = _t('Search for a room');
|
||||
let placeholder = _t('Find a room…');
|
||||
if (!this.state.instanceId) {
|
||||
placeholder = _t('Search for a room like #example') + ':' + this.state.roomServer;
|
||||
placeholder = _t("Find a room… (e.g. %(exampleRoom)s)", {exampleRoom: "#example:" + this.state.roomServer});
|
||||
} else if (instance_expected_field_type) {
|
||||
placeholder = instance_expected_field_type.placeholder;
|
||||
}
|
||||
|
@ -596,27 +630,31 @@ module.exports = React.createClass({
|
|||
listHeader = <div className="mx_RoomDirectory_listheader">
|
||||
<DirectorySearchBox
|
||||
className="mx_RoomDirectory_searchbox"
|
||||
onChange={this.onFilterChange} onClear={this.onFilterClear} onJoinClick={this.onJoinClick}
|
||||
onChange={this.onFilterChange} onClear={this.onFilterClear} onJoinClick={this.onJoinFromSearchClick}
|
||||
placeholder={placeholder} showJoinButton={showJoinButton}
|
||||
/>
|
||||
<NetworkDropdown config={this.props.config} protocols={this.protocols} onOptionChange={this.onOptionChange} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
const createRoomButton = (<AccessibleButton
|
||||
onClick={this.onCreateRoomClicked}
|
||||
className="mx_RoomDirectory_createRoom"
|
||||
>{_t("Create new room")}</AccessibleButton>);
|
||||
const explanation =
|
||||
_t("If you can't find the room you're looking for, ask for an invite or <a>Create a new room</a>.", null,
|
||||
{a: sub => {
|
||||
return (<AccessibleButton
|
||||
kind="secondary"
|
||||
onClick={this.onCreateRoomClick}
|
||||
>{sub}</AccessibleButton>);
|
||||
}},
|
||||
);
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
className={'mx_RoomDirectory_dialog'}
|
||||
hasCancel={true}
|
||||
onFinished={this.props.onFinished}
|
||||
headerButton={createRoomButton}
|
||||
title={_t("Room directory")}
|
||||
title={_t("Explore rooms")}
|
||||
>
|
||||
<div className="mx_RoomDirectory">
|
||||
<p>{explanation}</p>
|
||||
<div className="mx_RoomDirectory_list">
|
||||
{listHeader}
|
||||
{content}
|
||||
|
|
|
@ -16,13 +16,12 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import Matrix from 'matrix-js-sdk';
|
||||
import { _t, _td } from '../../languageHandler';
|
||||
import sdk from '../../index';
|
||||
import WhoIsTyping from '../../WhoIsTyping';
|
||||
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||
import MemberAvatar from '../views/avatars/MemberAvatar';
|
||||
import Resend from '../../Resend';
|
||||
import * as cryptodevices from '../../cryptodevices';
|
||||
import dis from '../../dispatcher';
|
||||
|
@ -39,7 +38,7 @@ function getUnsentMessages(room) {
|
|||
});
|
||||
}
|
||||
|
||||
module.exports = React.createClass({
|
||||
module.exports = createReactClass({
|
||||
displayName: 'RoomStatusBar',
|
||||
|
||||
propTypes: {
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import classNames from 'classnames';
|
||||
import sdk from '../../index';
|
||||
import dis from '../../dispatcher';
|
||||
|
@ -34,7 +35,7 @@ import {_t} from "../../languageHandler";
|
|||
// turn this on for drop & drag console debugging galore
|
||||
const debug = false;
|
||||
|
||||
const RoomSubList = React.createClass({
|
||||
const RoomSubList = createReactClass({
|
||||
displayName: 'RoomSubList',
|
||||
|
||||
debug: debug,
|
||||
|
|
|
@ -24,6 +24,7 @@ limitations under the License.
|
|||
import shouldHideEvent from '../../shouldHideEvent';
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import ReactDOM from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import Promise from 'bluebird';
|
||||
|
@ -70,7 +71,7 @@ const RoomContext = PropTypes.shape({
|
|||
room: PropTypes.instanceOf(Room),
|
||||
});
|
||||
|
||||
module.exports = React.createClass({
|
||||
module.exports = createReactClass({
|
||||
displayName: 'RoomView',
|
||||
propTypes: {
|
||||
ConferenceHandler: PropTypes.any,
|
||||
|
@ -582,7 +583,7 @@ module.exports = React.createClass({
|
|||
payload.data.description || payload.data.name);
|
||||
break;
|
||||
case 'picture_snapshot':
|
||||
return ContentMessages.sharedInstance().sendContentListToRoom(
|
||||
ContentMessages.sharedInstance().sendContentListToRoom(
|
||||
[payload.file], this.state.room.roomId, MatrixClientPeg.get(),
|
||||
);
|
||||
break;
|
||||
|
@ -623,6 +624,11 @@ module.exports = React.createClass({
|
|||
showApps: payload.show,
|
||||
});
|
||||
break;
|
||||
case 'reply_to_event':
|
||||
if (this.state.searchResults && payload.event.getRoomId() === this.state.roomId && !this.unmounted) {
|
||||
this.onCancelSearchClick();
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1550,7 +1556,6 @@ module.exports = React.createClass({
|
|||
|
||||
render: function() {
|
||||
const RoomHeader = sdk.getComponent('rooms.RoomHeader');
|
||||
const MessageComposer = sdk.getComponent('rooms.MessageComposer');
|
||||
const ForwardMessage = sdk.getComponent("rooms.ForwardMessage");
|
||||
const AuxPanel = sdk.getComponent("rooms.AuxPanel");
|
||||
const SearchBar = sdk.getComponent("rooms.SearchBar");
|
||||
|
@ -1778,15 +1783,29 @@ module.exports = React.createClass({
|
|||
myMembership === 'join' && !this.state.searchResults
|
||||
);
|
||||
if (canSpeak) {
|
||||
messageComposer =
|
||||
<MessageComposer
|
||||
room={this.state.room}
|
||||
callState={this.state.callState}
|
||||
disabled={this.props.disabled}
|
||||
showApps={this.state.showApps}
|
||||
e2eStatus={this.state.e2eStatus}
|
||||
permalinkCreator={this._getPermalinkCreatorForRoom(this.state.room)}
|
||||
/>;
|
||||
if (SettingsStore.isFeatureEnabled("feature_cider_composer")) {
|
||||
const MessageComposer = sdk.getComponent('rooms.MessageComposer');
|
||||
messageComposer =
|
||||
<MessageComposer
|
||||
room={this.state.room}
|
||||
callState={this.state.callState}
|
||||
disabled={this.props.disabled}
|
||||
showApps={this.state.showApps}
|
||||
e2eStatus={this.state.e2eStatus}
|
||||
permalinkCreator={this._getPermalinkCreatorForRoom(this.state.room)}
|
||||
/>;
|
||||
} else {
|
||||
const SlateMessageComposer = sdk.getComponent('rooms.SlateMessageComposer');
|
||||
messageComposer =
|
||||
<SlateMessageComposer
|
||||
room={this.state.room}
|
||||
callState={this.state.callState}
|
||||
disabled={this.props.disabled}
|
||||
showApps={this.state.showApps}
|
||||
e2eStatus={this.state.e2eStatus}
|
||||
permalinkCreator={this._getPermalinkCreatorForRoom(this.state.room)}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Why aren't we storing the term/scope/count in this format
|
||||
|
|
|
@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const React = require("react");
|
||||
import React from "react";
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import Promise from 'bluebird';
|
||||
import { KeyCode } from '../../Keyboard';
|
||||
|
@ -84,7 +85,7 @@ if (DEBUG_SCROLL) {
|
|||
* offset as normal.
|
||||
*/
|
||||
|
||||
module.exports = React.createClass({
|
||||
module.exports = createReactClass({
|
||||
displayName: 'ScrollPanel',
|
||||
|
||||
propTypes: {
|
||||
|
|
|
@ -16,13 +16,15 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import { KeyCode } from '../../Keyboard';
|
||||
import dis from '../../dispatcher';
|
||||
import { throttle } from 'lodash';
|
||||
import AccessibleButton from '../../components/views/elements/AccessibleButton';
|
||||
import classNames from 'classnames';
|
||||
|
||||
module.exports = React.createClass({
|
||||
module.exports = createReactClass({
|
||||
displayName: 'SearchBox',
|
||||
|
||||
propTypes: {
|
||||
|
@ -46,6 +48,7 @@ module.exports = React.createClass({
|
|||
getInitialState: function() {
|
||||
return {
|
||||
searchTerm: "",
|
||||
blurred: true,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -93,7 +96,18 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
_onFocus: function(ev) {
|
||||
this.setState({blurred: false});
|
||||
ev.target.select();
|
||||
if (this.props.onFocus) {
|
||||
this.props.onFocus(ev);
|
||||
}
|
||||
},
|
||||
|
||||
_onBlur: function(ev) {
|
||||
this.setState({blurred: true});
|
||||
if (this.props.onBlur) {
|
||||
this.props.onBlur(ev);
|
||||
}
|
||||
},
|
||||
|
||||
_clearSearch: function(source) {
|
||||
|
@ -112,15 +126,21 @@ module.exports = React.createClass({
|
|||
if (this.props.collapsed) {
|
||||
return null;
|
||||
}
|
||||
const clearButton = this.state.searchTerm.length > 0 ?
|
||||
const clearButton = !this.state.blurred ?
|
||||
(<AccessibleButton key="button"
|
||||
className="mx_SearchBox_closeButton"
|
||||
onClick={ () => {this._clearSearch("button"); } }>
|
||||
</AccessibleButton>) : undefined;
|
||||
|
||||
// show a shorter placeholder when blurred, if requested
|
||||
// this is used for the room filter field that has
|
||||
// the explore button next to it when blurred
|
||||
const placeholder = this.state.blurred ?
|
||||
(this.props.blurredPlaceholder || this.props.placeholder) :
|
||||
this.props.placeholder;
|
||||
const className = this.props.className || "";
|
||||
return (
|
||||
<div className="mx_SearchBox mx_textinput">
|
||||
<div className={classNames("mx_SearchBox", "mx_textinput", {"mx_SearchBox_blurred": this.state.blurred})}>
|
||||
<input
|
||||
key="searchfield"
|
||||
type="text"
|
||||
|
@ -130,7 +150,8 @@ module.exports = React.createClass({
|
|||
onFocus={ this._onFocus }
|
||||
onChange={ this.onChange }
|
||||
onKeyDown={ this._onKeyDown }
|
||||
placeholder={ this.props.placeholder }
|
||||
onBlur={this._onBlur}
|
||||
placeholder={ placeholder }
|
||||
/>
|
||||
{ clearButton }
|
||||
</div>
|
||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import TagOrderStore from '../../stores/TagOrderStore';
|
||||
|
@ -28,7 +29,7 @@ import { _t } from '../../languageHandler';
|
|||
import { Droppable } from 'react-beautiful-dnd';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const TagPanel = React.createClass({
|
||||
const TagPanel = createReactClass({
|
||||
displayName: 'TagPanel',
|
||||
|
||||
contextTypes: {
|
||||
|
|
|
@ -15,12 +15,13 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import sdk from '../../index';
|
||||
import dis from '../../dispatcher';
|
||||
import Modal from '../../Modal';
|
||||
import { _t } from '../../languageHandler';
|
||||
|
||||
const TagPanelButtons = React.createClass({
|
||||
const TagPanelButtons = createReactClass({
|
||||
displayName: 'TagPanelButtons',
|
||||
|
||||
|
||||
|
|
|
@ -19,8 +19,9 @@ limitations under the License.
|
|||
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
|
||||
const React = require('react');
|
||||
const ReactDOM = require("react-dom");
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import ReactDOM from "react-dom";
|
||||
import PropTypes from 'prop-types';
|
||||
import Promise from 'bluebird';
|
||||
|
||||
|
@ -58,7 +59,7 @@ if (DEBUG) {
|
|||
*
|
||||
* Also responsible for handling and sending read receipts.
|
||||
*/
|
||||
const TimelinePanel = React.createClass({
|
||||
const TimelinePanel = createReactClass({
|
||||
displayName: 'TimelinePanel',
|
||||
|
||||
propTypes: {
|
||||
|
@ -684,20 +685,26 @@ const TimelinePanel = React.createClass({
|
|||
}
|
||||
this.lastRMSentEventId = this.state.readMarkerEventId;
|
||||
|
||||
const roomId = this.props.timelineSet.room.roomId;
|
||||
const hiddenRR = !SettingsStore.getValue("sendReadReceipts", roomId);
|
||||
|
||||
debuglog('TimelinePanel: Sending Read Markers for ',
|
||||
this.props.timelineSet.room.roomId,
|
||||
'rm', this.state.readMarkerEventId,
|
||||
lastReadEvent ? 'rr ' + lastReadEvent.getId() : '',
|
||||
' hidden:' + hiddenRR,
|
||||
);
|
||||
MatrixClientPeg.get().setRoomReadMarkers(
|
||||
this.props.timelineSet.room.roomId,
|
||||
this.state.readMarkerEventId,
|
||||
lastReadEvent, // Could be null, in which case no RR is sent
|
||||
{hidden: hiddenRR},
|
||||
).catch((e) => {
|
||||
// /read_markers API is not implemented on this HS, fallback to just RR
|
||||
if (e.errcode === 'M_UNRECOGNIZED' && lastReadEvent) {
|
||||
return MatrixClientPeg.get().sendReadReceipt(
|
||||
lastReadEvent,
|
||||
{hidden: hiddenRR},
|
||||
).catch((e) => {
|
||||
console.error(e);
|
||||
this.lastRRSentEventId = undefined;
|
||||
|
|
|
@ -14,14 +14,16 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
const React = require('react');
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import ContentMessages from '../../ContentMessages';
|
||||
const dis = require('../../dispatcher');
|
||||
const filesize = require('filesize');
|
||||
import { _t } from '../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({displayName: 'UploadBar',
|
||||
module.exports = createReactClass({
|
||||
displayName: 'UploadBar',
|
||||
propTypes: {
|
||||
room: PropTypes.object,
|
||||
},
|
||||
|
|
|
@ -16,13 +16,14 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import SyntaxHighlight from '../views/elements/SyntaxHighlight';
|
||||
import {_t} from "../../languageHandler";
|
||||
import sdk from "../../index";
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
module.exports = createReactClass({
|
||||
displayName: 'ViewSource',
|
||||
|
||||
propTypes: {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017, 2018, 2019 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -16,6 +17,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import sdk from '../../../index';
|
||||
|
@ -37,7 +39,7 @@ const PHASE_EMAIL_SENT = 3;
|
|||
// User has clicked the link in email and completed reset
|
||||
const PHASE_DONE = 4;
|
||||
|
||||
module.exports = React.createClass({
|
||||
module.exports = createReactClass({
|
||||
displayName: 'ForgotPassword',
|
||||
|
||||
propTypes: {
|
||||
|
@ -62,10 +64,12 @@ module.exports = React.createClass({
|
|||
serverIsAlive: true,
|
||||
serverErrorIsFatal: false,
|
||||
serverDeadError: "",
|
||||
serverRequiresIdServer: null,
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.reset = null;
|
||||
this._checkServerLiveliness(this.props.serverConfig);
|
||||
},
|
||||
|
||||
|
@ -83,7 +87,14 @@ module.exports = React.createClass({
|
|||
serverConfig.hsUrl,
|
||||
serverConfig.isUrl,
|
||||
);
|
||||
this.setState({serverIsAlive: true});
|
||||
|
||||
const pwReset = new PasswordReset(serverConfig.hsUrl, serverConfig.isUrl);
|
||||
const serverRequiresIdServer = await pwReset.doesServerRequireIdServerParam();
|
||||
|
||||
this.setState({
|
||||
serverIsAlive: true,
|
||||
serverRequiresIdServer,
|
||||
});
|
||||
} catch (e) {
|
||||
this.setState(AutoDiscoveryUtils.authComponentStateForError(e, "forgot_password"));
|
||||
}
|
||||
|
@ -199,6 +210,7 @@ module.exports = React.createClass({
|
|||
serverConfig={this.props.serverConfig}
|
||||
onServerConfigChange={this.props.onServerConfigChange}
|
||||
delayTimeMs={0}
|
||||
showIdentityServerIfRequiredByHomeserver={true}
|
||||
onAfterSubmit={this.onServerDetailsNextPhaseClick}
|
||||
submitText={_t("Next")}
|
||||
submitClass="mx_Login_submit"
|
||||
|
@ -256,7 +268,7 @@ module.exports = React.createClass({
|
|||
</a>;
|
||||
}
|
||||
|
||||
if (!this.props.serverConfig.isUrl) {
|
||||
if (!this.props.serverConfig.isUrl && this.state.serverRequiresIdServer) {
|
||||
return <div>
|
||||
<h3>
|
||||
{yourMatrixAccountText}
|
||||
|
|
|
@ -16,9 +16,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import {_t, _td} from '../../../languageHandler';
|
||||
import sdk from '../../../index';
|
||||
|
@ -54,7 +53,7 @@ _td("General failure");
|
|||
/**
|
||||
* A wire component which glues together login UI components and Login logic
|
||||
*/
|
||||
module.exports = React.createClass({
|
||||
module.exports = createReactClass({
|
||||
displayName: 'Login',
|
||||
|
||||
propTypes: {
|
||||
|
@ -94,7 +93,7 @@ module.exports = React.createClass({
|
|||
// Phase of the overall login dialog.
|
||||
phase: PHASE_LOGIN,
|
||||
// The current login flow, such as password, SSO, etc.
|
||||
currentFlow: "m.login.password",
|
||||
currentFlow: null, // we need to load the flows from the server
|
||||
|
||||
// We perform liveliness checks later, but for now suppress the errors.
|
||||
// We also track the server dead errors independently of the regular errors so
|
||||
|
@ -373,6 +372,7 @@ module.exports = React.createClass({
|
|||
|
||||
this.setState({
|
||||
busy: true,
|
||||
currentFlow: null, // reset flow
|
||||
loginIncorrect: false,
|
||||
});
|
||||
|
||||
|
@ -566,6 +566,13 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
_renderSsoStep: function(url) {
|
||||
const SignInToText = sdk.getComponent('views.auth.SignInToText');
|
||||
|
||||
let onEditServerDetailsClick = null;
|
||||
// If custom URLs are allowed, wire up the server details edit link.
|
||||
if (PHASES_ENABLED && !SdkConfig.get()['disable_custom_urls']) {
|
||||
onEditServerDetailsClick = this.onEditServerDetailsClick;
|
||||
}
|
||||
// XXX: This link does *not* have a target="_blank" because single sign-on relies on
|
||||
// redirecting the user back to a URI once they're logged in. On the web, this means
|
||||
// we use the same window and redirect back to riot. On electron, this actually
|
||||
|
@ -575,7 +582,12 @@ module.exports = React.createClass({
|
|||
// user's browser, let them log into their SSO provider, then redirect their browser
|
||||
// to vector://vector which, of course, will not work.
|
||||
return (
|
||||
<a href={url} className="mx_Login_sso_link mx_Login_submit">{ _t('Sign in with single sign-on') }</a>
|
||||
<div>
|
||||
<SignInToText serverConfig={this.props.serverConfig}
|
||||
onEditServerDetailsClick={onEditServerDetailsClick} />
|
||||
|
||||
<a href={url} className="mx_Login_sso_link mx_Login_submit">{ _t('Sign in with single sign-on') }</a>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
|
|
|
@ -14,15 +14,14 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import sdk from '../../../index';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
module.exports = createReactClass({
|
||||
displayName: 'PostRegistration',
|
||||
|
||||
propTypes: {
|
||||
|
|
|
@ -20,6 +20,7 @@ limitations under the License.
|
|||
import Matrix from 'matrix-js-sdk';
|
||||
import Promise from 'bluebird';
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import sdk from '../../../index';
|
||||
import { _t, _td } from '../../../languageHandler';
|
||||
|
@ -40,7 +41,7 @@ const PHASE_REGISTRATION = 1;
|
|||
// Enable phases for registration
|
||||
const PHASES_ENABLED = true;
|
||||
|
||||
module.exports = React.createClass({
|
||||
module.exports = createReactClass({
|
||||
displayName: 'Registration',
|
||||
|
||||
propTypes: {
|
||||
|
@ -98,6 +99,9 @@ module.exports = React.createClass({
|
|||
// component without it.
|
||||
matrixClient: null,
|
||||
|
||||
// whether the HS requires an ID server to register with a threepid
|
||||
serverRequiresIdServer: null,
|
||||
|
||||
// The user ID we've just registered
|
||||
registeredUsername: null,
|
||||
|
||||
|
@ -204,13 +208,23 @@ module.exports = React.createClass({
|
|||
}
|
||||
|
||||
const {hsUrl, isUrl} = serverConfig;
|
||||
this.setState({
|
||||
matrixClient: Matrix.createClient({
|
||||
baseUrl: hsUrl,
|
||||
idBaseUrl: isUrl,
|
||||
}),
|
||||
const cli = Matrix.createClient({
|
||||
baseUrl: hsUrl,
|
||||
idBaseUrl: isUrl,
|
||||
});
|
||||
|
||||
let serverRequiresIdServer = true;
|
||||
try {
|
||||
serverRequiresIdServer = await cli.doesServerRequireIdServerParam();
|
||||
} catch (e) {
|
||||
console.log("Unable to determine is server needs id_server param", e);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
matrixClient: cli,
|
||||
serverRequiresIdServer,
|
||||
busy: false,
|
||||
});
|
||||
this.setState({busy: false});
|
||||
try {
|
||||
await this._makeRegisterRequest({});
|
||||
// This should never succeed since we specified an empty
|
||||
|
@ -403,14 +417,9 @@ module.exports = React.createClass({
|
|||
// clicking the email link.
|
||||
let inhibitLogin = Boolean(this.state.formVals.email);
|
||||
|
||||
// Only send the bind params if we're sending username / pw params
|
||||
// Only send inhibitLogin if we're sending username / pw params
|
||||
// (Since we need to send no params at all to use the ones saved in the
|
||||
// session).
|
||||
const bindThreepids = this.state.formVals.password ? {
|
||||
email: true,
|
||||
msisdn: true,
|
||||
} : {};
|
||||
// Likewise inhibitLogin
|
||||
if (!this.state.formVals.password) inhibitLogin = null;
|
||||
|
||||
return this.state.matrixClient.register(
|
||||
|
@ -418,7 +427,7 @@ module.exports = React.createClass({
|
|||
this.state.formVals.password,
|
||||
undefined, // session id: included in the auth dict already
|
||||
auth,
|
||||
bindThreepids,
|
||||
null,
|
||||
null,
|
||||
inhibitLogin,
|
||||
);
|
||||
|
@ -491,6 +500,7 @@ module.exports = React.createClass({
|
|||
serverConfig={this.props.serverConfig}
|
||||
onServerConfigChange={this.props.onServerConfigChange}
|
||||
delayTimeMs={250}
|
||||
showIdentityServerIfRequiredByHomeserver={true}
|
||||
{...serverDetailsProps}
|
||||
/>;
|
||||
break;
|
||||
|
@ -555,6 +565,7 @@ module.exports = React.createClass({
|
|||
flows={this.state.flows}
|
||||
serverConfig={this.props.serverConfig}
|
||||
canSubmit={!this.state.serverErrorIsFatal}
|
||||
serverRequiresIdServer={this.state.serverRequiresIdServer}
|
||||
/>;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -15,12 +15,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
|
||||
module.exports = React.createClass({
|
||||
module.exports = createReactClass({
|
||||
displayName: 'AuthFooter',
|
||||
|
||||
render: function() {
|
||||
|
|
|
@ -15,12 +15,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const React = require('react');
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import sdk from '../../../index';
|
||||
|
||||
module.exports = React.createClass({
|
||||
module.exports = createReactClass({
|
||||
displayName: 'AuthHeader',
|
||||
|
||||
render: function() {
|
||||
|
|
|
@ -15,12 +15,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const React = require('react');
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import sdk from '../../../index';
|
||||
|
||||
module.exports = React.createClass({
|
||||
module.exports = createReactClass({
|
||||
displayName: 'AuthPage',
|
||||
|
||||
render: function() {
|
||||
|
|
|
@ -14,9 +14,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
|
@ -25,7 +24,7 @@ const DIV_ID = 'mx_recaptcha';
|
|||
/**
|
||||
* A pure UI component which displays a captcha form.
|
||||
*/
|
||||
module.exports = React.createClass({
|
||||
module.exports = createReactClass({
|
||||
displayName: 'CaptchaForm',
|
||||
|
||||
propTypes: {
|
||||
|
|
|
@ -15,9 +15,10 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
module.exports = React.createClass({
|
||||
module.exports = createReactClass({
|
||||
displayName: 'CustomServerDialog',
|
||||
|
||||
render: function() {
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import url from 'url';
|
||||
import classnames from 'classnames';
|
||||
|
@ -63,7 +64,7 @@ import SettingsStore from "../../../settings/SettingsStore";
|
|||
* focus: set the input focus appropriately in the form.
|
||||
*/
|
||||
|
||||
export const PasswordAuthEntry = React.createClass({
|
||||
export const PasswordAuthEntry = createReactClass({
|
||||
displayName: 'PasswordAuthEntry',
|
||||
|
||||
statics: {
|
||||
|
@ -162,7 +163,7 @@ export const PasswordAuthEntry = React.createClass({
|
|||
},
|
||||
});
|
||||
|
||||
export const RecaptchaAuthEntry = React.createClass({
|
||||
export const RecaptchaAuthEntry = createReactClass({
|
||||
displayName: 'RecaptchaAuthEntry',
|
||||
|
||||
statics: {
|
||||
|
@ -212,7 +213,7 @@ export const RecaptchaAuthEntry = React.createClass({
|
|||
},
|
||||
});
|
||||
|
||||
export const TermsAuthEntry = React.createClass({
|
||||
export const TermsAuthEntry = createReactClass({
|
||||
displayName: 'TermsAuthEntry',
|
||||
|
||||
statics: {
|
||||
|
@ -351,7 +352,7 @@ export const TermsAuthEntry = React.createClass({
|
|||
},
|
||||
});
|
||||
|
||||
export const EmailIdentityAuthEntry = React.createClass({
|
||||
export const EmailIdentityAuthEntry = createReactClass({
|
||||
displayName: 'EmailIdentityAuthEntry',
|
||||
|
||||
statics: {
|
||||
|
@ -393,7 +394,7 @@ export const EmailIdentityAuthEntry = React.createClass({
|
|||
},
|
||||
});
|
||||
|
||||
export const MsisdnAuthEntry = React.createClass({
|
||||
export const MsisdnAuthEntry = createReactClass({
|
||||
displayName: 'MsisdnAuthEntry',
|
||||
|
||||
statics: {
|
||||
|
@ -540,7 +541,7 @@ export const MsisdnAuthEntry = React.createClass({
|
|||
},
|
||||
});
|
||||
|
||||
export const FallbackAuthEntry = React.createClass({
|
||||
export const FallbackAuthEntry = createReactClass({
|
||||
displayName: 'FallbackAuthEntry',
|
||||
|
||||
propTypes: {
|
||||
|
|
|
@ -15,13 +15,13 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils";
|
||||
import * as ServerType from '../../views/auth/ServerTypeSelector';
|
||||
import ServerConfig from "./ServerConfig";
|
||||
|
||||
const MODULAR_URL = 'https://modular.im/?utm_source=riot-web&utm_medium=web&utm_campaign=riot-web-authentication';
|
||||
|
||||
|
@ -33,49 +33,8 @@ const MODULAR_URL = 'https://modular.im/?utm_source=riot-web&utm_medium=web&utm_
|
|||
* This is a variant of ServerConfig with only the HS field and different body
|
||||
* text that is specific to the Modular case.
|
||||
*/
|
||||
export default class ModularServerConfig extends React.PureComponent {
|
||||
static propTypes = {
|
||||
onServerConfigChange: PropTypes.func,
|
||||
|
||||
// The current configuration that the user is expecting to change.
|
||||
serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired,
|
||||
|
||||
delayTimeMs: PropTypes.number, // time to wait before invoking onChanged
|
||||
|
||||
// Called after the component calls onServerConfigChange
|
||||
onAfterSubmit: PropTypes.func,
|
||||
|
||||
// Optional text for the submit button. If falsey, no button will be shown.
|
||||
submitText: PropTypes.string,
|
||||
|
||||
// Optional class for the submit button. Only applies if the submit button
|
||||
// is to be rendered.
|
||||
submitClass: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onServerConfigChange: function() {},
|
||||
customHsUrl: "",
|
||||
delayTimeMs: 0,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
busy: false,
|
||||
errorText: "",
|
||||
hsUrl: props.serverConfig.hsUrl,
|
||||
isUrl: props.serverConfig.isUrl,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
if (newProps.serverConfig.hsUrl === this.state.hsUrl &&
|
||||
newProps.serverConfig.isUrl === this.state.isUrl) return;
|
||||
|
||||
this.validateAndApplyServer(newProps.serverConfig.hsUrl, newProps.serverConfig.isUrl);
|
||||
}
|
||||
export default class ModularServerConfig extends ServerConfig {
|
||||
static propTypes = ServerConfig.propTypes;
|
||||
|
||||
async validateAndApplyServer(hsUrl, isUrl) {
|
||||
// Always try and use the defaults first
|
||||
|
@ -120,35 +79,6 @@ export default class ModularServerConfig extends React.PureComponent {
|
|||
return this.validateAndApplyServer(this.state.hsUrl, ServerType.TYPES.PREMIUM.identityServerUrl);
|
||||
}
|
||||
|
||||
onHomeserverBlur = (ev) => {
|
||||
this._hsTimeoutId = this._waitThenInvoke(this._hsTimeoutId, () => {
|
||||
this.validateServer();
|
||||
});
|
||||
};
|
||||
|
||||
onHomeserverChange = (ev) => {
|
||||
const hsUrl = ev.target.value;
|
||||
this.setState({ hsUrl });
|
||||
};
|
||||
|
||||
onSubmit = async (ev) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
const result = await this.validateServer();
|
||||
if (!result) return; // Do not continue.
|
||||
|
||||
if (this.props.onAfterSubmit) {
|
||||
this.props.onAfterSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
_waitThenInvoke(existingTimeoutId, fn) {
|
||||
if (existingTimeoutId) {
|
||||
clearTimeout(existingTimeoutId);
|
||||
}
|
||||
return setTimeout(fn.bind(this), this.props.delayTimeMs);
|
||||
}
|
||||
|
||||
render() {
|
||||
const Field = sdk.getComponent('elements.Field');
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
|
|
|
@ -31,6 +31,7 @@ export default class PasswordLogin extends React.Component {
|
|||
static propTypes = {
|
||||
onSubmit: PropTypes.func.isRequired, // fn(username, password)
|
||||
onError: PropTypes.func,
|
||||
onEditServerDetailsClick: PropTypes.func,
|
||||
onForgotPasswordClick: PropTypes.func, // fn()
|
||||
initialUsername: PropTypes.string,
|
||||
initialPhoneCountry: PropTypes.string,
|
||||
|
@ -257,6 +258,7 @@ export default class PasswordLogin extends React.Component {
|
|||
|
||||
render() {
|
||||
const Field = sdk.getComponent('elements.Field');
|
||||
const SignInToText = sdk.getComponent('views.auth.SignInToText');
|
||||
|
||||
let forgotPasswordJsx;
|
||||
|
||||
|
@ -273,33 +275,6 @@ export default class PasswordLogin extends React.Component {
|
|||
</span>;
|
||||
}
|
||||
|
||||
let signInToText = _t('Sign in to your Matrix account on %(serverName)s', {
|
||||
serverName: this.props.serverConfig.hsName,
|
||||
});
|
||||
if (this.props.serverConfig.hsNameIsDifferent) {
|
||||
const TextWithTooltip = sdk.getComponent("elements.TextWithTooltip");
|
||||
|
||||
signInToText = _t('Sign in to your Matrix account on <underlinedServerName />', {}, {
|
||||
'underlinedServerName': () => {
|
||||
return <TextWithTooltip
|
||||
class="mx_Login_underlinedServerName"
|
||||
tooltip={this.props.serverConfig.hsUrl}
|
||||
>
|
||||
{this.props.serverConfig.hsName}
|
||||
</TextWithTooltip>;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let editLink = null;
|
||||
if (this.props.onEditServerDetailsClick) {
|
||||
editLink = <a className="mx_AuthBody_editServerDetails"
|
||||
href="#" onClick={this.props.onEditServerDetailsClick}
|
||||
>
|
||||
{_t('Change')}
|
||||
</a>;
|
||||
}
|
||||
|
||||
const pwFieldClass = classNames({
|
||||
error: this.props.loginIncorrect && !this.isLoginEmpty(), // only error password if error isn't top field
|
||||
});
|
||||
|
@ -342,10 +317,8 @@ export default class PasswordLogin extends React.Component {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<h3>
|
||||
{signInToText}
|
||||
{editLink}
|
||||
</h3>
|
||||
<SignInToText serverConfig={this.props.serverConfig}
|
||||
onEditServerDetailsClick={this.props.onEditServerDetailsClick} />
|
||||
<form onSubmit={this.onSubmitForm}>
|
||||
{loginType}
|
||||
{loginField}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2018, 2019 New Vector Ltd
|
||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -17,6 +18,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import sdk from '../../../index';
|
||||
import Email from '../../../email';
|
||||
|
@ -39,7 +41,7 @@ const PASSWORD_MIN_SCORE = 3; // safely unguessable: moderate protection from of
|
|||
/**
|
||||
* A pure UI component which displays a registration form.
|
||||
*/
|
||||
module.exports = React.createClass({
|
||||
module.exports = createReactClass({
|
||||
displayName: 'RegistrationForm',
|
||||
|
||||
propTypes: {
|
||||
|
@ -54,6 +56,7 @@ module.exports = React.createClass({
|
|||
flows: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired,
|
||||
canSubmit: PropTypes.bool,
|
||||
serverRequiresIdServer: PropTypes.bool,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
|
@ -69,10 +72,10 @@ module.exports = React.createClass({
|
|||
fieldValid: {},
|
||||
// The ISO2 country code selected in the phone number entry
|
||||
phoneCountry: this.props.defaultPhoneCountry,
|
||||
username: "",
|
||||
email: "",
|
||||
phoneNumber: "",
|
||||
password: "",
|
||||
username: this.props.defaultUsername || "",
|
||||
email: this.props.defaultEmail || "",
|
||||
phoneNumber: this.props.defaultPhoneNumber || "",
|
||||
password: this.props.defaultPassword || "",
|
||||
passwordConfirm: "",
|
||||
passwordComplexity: null,
|
||||
passwordSafe: false,
|
||||
|
@ -90,7 +93,7 @@ module.exports = React.createClass({
|
|||
}
|
||||
|
||||
const self = this;
|
||||
if (this.state.email == '') {
|
||||
if (this.state.email === '') {
|
||||
const haveIs = Boolean(this.props.serverConfig.isUrl);
|
||||
|
||||
let desc;
|
||||
|
@ -436,7 +439,17 @@ module.exports = React.createClass({
|
|||
|
||||
_showEmail() {
|
||||
const haveIs = Boolean(this.props.serverConfig.isUrl);
|
||||
if (!haveIs || !this._authStepIsUsed('m.login.email.identity')) {
|
||||
if ((this.props.serverRequiresIdServer && !haveIs) || !this._authStepIsUsed('m.login.email.identity')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
_showPhoneNumber() {
|
||||
const threePidLogin = !SdkConfig.get().disable_3pid_login;
|
||||
const haveIs = Boolean(this.props.serverConfig.isUrl);
|
||||
const haveRequiredIs = this.props.serverRequiresIdServer && !haveIs;
|
||||
if (!threePidLogin || haveRequiredIs || !this._authStepIsUsed('m.login.msisdn')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -455,7 +468,6 @@ module.exports = React.createClass({
|
|||
ref={field => this[FIELD_EMAIL] = field}
|
||||
type="text"
|
||||
label={emailPlaceholder}
|
||||
defaultValue={this.props.defaultEmail}
|
||||
value={this.state.email}
|
||||
onChange={this.onEmailChange}
|
||||
onValidate={this.onEmailValidate}
|
||||
|
@ -469,7 +481,6 @@ module.exports = React.createClass({
|
|||
ref={field => this[FIELD_PASSWORD] = field}
|
||||
type="password"
|
||||
label={_t("Password")}
|
||||
defaultValue={this.props.defaultPassword}
|
||||
value={this.state.password}
|
||||
onChange={this.onPasswordChange}
|
||||
onValidate={this.onPasswordValidate}
|
||||
|
@ -483,7 +494,6 @@ module.exports = React.createClass({
|
|||
ref={field => this[FIELD_PASSWORD_CONFIRM] = field}
|
||||
type="password"
|
||||
label={_t("Confirm")}
|
||||
defaultValue={this.props.defaultPassword}
|
||||
value={this.state.passwordConfirm}
|
||||
onChange={this.onPasswordConfirmChange}
|
||||
onValidate={this.onPasswordConfirmValidate}
|
||||
|
@ -491,9 +501,7 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
renderPhoneNumber() {
|
||||
const threePidLogin = !SdkConfig.get().disable_3pid_login;
|
||||
const haveIs = Boolean(this.props.serverConfig.isUrl);
|
||||
if (!threePidLogin || !haveIs || !this._authStepIsUsed('m.login.msisdn')) {
|
||||
if (!this._showPhoneNumber()) {
|
||||
return null;
|
||||
}
|
||||
const CountryDropdown = sdk.getComponent('views.auth.CountryDropdown');
|
||||
|
@ -512,7 +520,6 @@ module.exports = React.createClass({
|
|||
ref={field => this[FIELD_PHONE_NUMBER] = field}
|
||||
type="text"
|
||||
label={phoneLabel}
|
||||
defaultValue={this.props.defaultPhoneNumber}
|
||||
value={this.state.phoneNumber}
|
||||
prefix={phoneCountry}
|
||||
onChange={this.onPhoneNumberChange}
|
||||
|
@ -528,7 +535,6 @@ module.exports = React.createClass({
|
|||
type="text"
|
||||
autoFocus={true}
|
||||
label={_t("Username")}
|
||||
defaultValue={this.props.defaultUsername}
|
||||
value={this.state.username}
|
||||
onChange={this.onUsernameChange}
|
||||
onValidate={this.onUsernameValidate}
|
||||
|
@ -567,11 +573,24 @@ module.exports = React.createClass({
|
|||
<input className="mx_Login_submit" type="submit" value={_t("Register")} disabled={!this.props.canSubmit} />
|
||||
);
|
||||
|
||||
const emailHelperText = this._showEmail() ? <div>
|
||||
{_t("Use an email address to recover your account.") + " "}
|
||||
{_t("Other users can invite you to rooms using your contact details.")}
|
||||
</div> : null;
|
||||
|
||||
let emailHelperText = null;
|
||||
if (this._showEmail()) {
|
||||
if (this._showPhoneNumber()) {
|
||||
emailHelperText = <div>
|
||||
{_t(
|
||||
"Set an email for account recovery. " +
|
||||
"Use email or phone to optionally be discoverable by existing contacts.",
|
||||
)}
|
||||
</div>;
|
||||
} else {
|
||||
emailHelperText = <div>
|
||||
{_t(
|
||||
"Set an email for account recovery. " +
|
||||
"Use email to optionally be discoverable by existing contacts.",
|
||||
)}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
const haveIs = Boolean(this.props.serverConfig.isUrl);
|
||||
const noIsText = haveIs ? null : <div>
|
||||
{_t(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2019 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -23,6 +24,8 @@ import { _t } from '../../../languageHandler';
|
|||
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
||||
import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import { createClient } from 'matrix-js-sdk/lib/matrix';
|
||||
import classNames from 'classnames';
|
||||
|
||||
/*
|
||||
* A pure UI component which displays the HS and IS to use.
|
||||
|
@ -46,6 +49,10 @@ export default class ServerConfig extends React.PureComponent {
|
|||
// Optional class for the submit button. Only applies if the submit button
|
||||
// is to be rendered.
|
||||
submitClass: PropTypes.string,
|
||||
|
||||
// Whether the flow this component is embedded in requires an identity
|
||||
// server when the homeserver says it will need one. Default false.
|
||||
showIdentityServerIfRequiredByHomeserver: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -61,6 +68,7 @@ export default class ServerConfig extends React.PureComponent {
|
|||
errorText: "",
|
||||
hsUrl: props.serverConfig.hsUrl,
|
||||
isUrl: props.serverConfig.isUrl,
|
||||
showIdentityServer: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -75,14 +83,41 @@ export default class ServerConfig extends React.PureComponent {
|
|||
// TODO: Do we want to support .well-known lookups here?
|
||||
// If for some reason someone enters "matrix.org" for a URL, we could do a lookup to
|
||||
// find their homeserver without demanding they use "https://matrix.org"
|
||||
return this.validateAndApplyServer(this.state.hsUrl, this.state.isUrl);
|
||||
const result = this.validateAndApplyServer(this.state.hsUrl, this.state.isUrl);
|
||||
if (!result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// If the UI flow this component is embedded in requires an identity
|
||||
// server when the homeserver says it will need one, check first and
|
||||
// reveal this field if not already shown.
|
||||
// XXX: This a backward compatibility path for homeservers that require
|
||||
// an identity server to be passed during certain flows.
|
||||
// See also https://github.com/matrix-org/synapse/pull/5868.
|
||||
if (
|
||||
this.props.showIdentityServerIfRequiredByHomeserver &&
|
||||
!this.state.showIdentityServer &&
|
||||
await this.isIdentityServerRequiredByHomeserver()
|
||||
) {
|
||||
this.setState({
|
||||
showIdentityServer: true,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async validateAndApplyServer(hsUrl, isUrl) {
|
||||
// Always try and use the defaults first
|
||||
const defaultConfig: ValidatedServerConfig = SdkConfig.get()["validated_server_config"];
|
||||
if (defaultConfig.hsUrl === hsUrl && defaultConfig.isUrl === isUrl) {
|
||||
this.setState({busy: false, errorText: ""});
|
||||
this.setState({
|
||||
hsUrl: defaultConfig.hsUrl,
|
||||
isUrl: defaultConfig.isUrl,
|
||||
busy: false,
|
||||
errorText: "",
|
||||
});
|
||||
this.props.onServerConfigChange(defaultConfig);
|
||||
return defaultConfig;
|
||||
}
|
||||
|
@ -126,6 +161,15 @@ export default class ServerConfig extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
async isIdentityServerRequiredByHomeserver() {
|
||||
// XXX: We shouldn't have to create a whole new MatrixClient just to
|
||||
// check if the homeserver requires an identity server... Should it be
|
||||
// extracted to a static utils function...?
|
||||
return createClient({
|
||||
baseUrl: this.state.hsUrl,
|
||||
}).doesServerRequireIdServerParam();
|
||||
}
|
||||
|
||||
onHomeserverBlur = (ev) => {
|
||||
this._hsTimeoutId = this._waitThenInvoke(this._hsTimeoutId, () => {
|
||||
this.validateServer();
|
||||
|
@ -171,8 +215,49 @@ export default class ServerConfig extends React.PureComponent {
|
|||
Modal.createTrackedDialog('Custom Server Dialog', '', CustomServerDialog);
|
||||
};
|
||||
|
||||
render() {
|
||||
_renderHomeserverSection() {
|
||||
const Field = sdk.getComponent('elements.Field');
|
||||
return <div>
|
||||
{_t("Enter your custom homeserver URL <a>What does this mean?</a>", {}, {
|
||||
a: sub => <a className="mx_ServerConfig_help" href="#" onClick={this.showHelpPopup}>
|
||||
{sub}
|
||||
</a>,
|
||||
})}
|
||||
<Field id="mx_ServerConfig_hsUrl"
|
||||
label={_t("Homeserver URL")}
|
||||
placeholder={this.props.serverConfig.hsUrl}
|
||||
value={this.state.hsUrl}
|
||||
onBlur={this.onHomeserverBlur}
|
||||
onChange={this.onHomeserverChange}
|
||||
disabled={this.state.busy}
|
||||
/>
|
||||
</div>;
|
||||
}
|
||||
|
||||
_renderIdentityServerSection() {
|
||||
const Field = sdk.getComponent('elements.Field');
|
||||
const classes = classNames({
|
||||
"mx_ServerConfig_identityServer": true,
|
||||
"mx_ServerConfig_identityServer_shown": this.state.showIdentityServer,
|
||||
});
|
||||
return <div className={classes}>
|
||||
{_t("Enter your custom identity server URL <a>What does this mean?</a>", {}, {
|
||||
a: sub => <a className="mx_ServerConfig_help" href="#" onClick={this.showHelpPopup}>
|
||||
{sub}
|
||||
</a>,
|
||||
})}
|
||||
<Field id="mx_ServerConfig_isUrl"
|
||||
label={_t("Identity Server URL")}
|
||||
placeholder={this.props.serverConfig.isUrl}
|
||||
value={this.state.isUrl || ''}
|
||||
onBlur={this.onIdentityServerBlur}
|
||||
onChange={this.onIdentityServerChange}
|
||||
disabled={this.state.busy}
|
||||
/>
|
||||
</div>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
|
||||
const errorText = this.state.errorText
|
||||
|
@ -191,31 +276,10 @@ export default class ServerConfig extends React.PureComponent {
|
|||
return (
|
||||
<div className="mx_ServerConfig">
|
||||
<h3>{_t("Other servers")}</h3>
|
||||
{_t("Enter custom server URLs <a>What does this mean?</a>", {}, {
|
||||
a: sub => <a className="mx_ServerConfig_help" href="#" onClick={this.showHelpPopup}>
|
||||
{ sub }
|
||||
</a>,
|
||||
})}
|
||||
{errorText}
|
||||
{this._renderHomeserverSection()}
|
||||
{this._renderIdentityServerSection()}
|
||||
<form onSubmit={this.onSubmit} autoComplete={false} action={null}>
|
||||
<div className="mx_ServerConfig_fields">
|
||||
<Field id="mx_ServerConfig_hsUrl"
|
||||
label={_t("Homeserver URL")}
|
||||
placeholder={this.props.serverConfig.hsUrl}
|
||||
value={this.state.hsUrl}
|
||||
onBlur={this.onHomeserverBlur}
|
||||
onChange={this.onHomeserverChange}
|
||||
disabled={this.state.busy}
|
||||
/>
|
||||
<Field id="mx_ServerConfig_isUrl"
|
||||
label={_t("Identity Server URL")}
|
||||
placeholder={this.props.serverConfig.isUrl}
|
||||
value={this.state.isUrl || ''}
|
||||
onBlur={this.onIdentityServerBlur}
|
||||
onChange={this.onIdentityServerChange}
|
||||
disabled={this.state.busy}
|
||||
/>
|
||||
</div>
|
||||
{submitButton}
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 {_t} from "../../../languageHandler";
|
||||
import sdk from "../../../index";
|
||||
import PropTypes from "prop-types";
|
||||
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
||||
|
||||
export default class SignInToText extends React.PureComponent {
|
||||
static propTypes = {
|
||||
serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired,
|
||||
onEditServerDetailsClick: PropTypes.func,
|
||||
};
|
||||
|
||||
render() {
|
||||
let signInToText = _t('Sign in to your Matrix account on %(serverName)s', {
|
||||
serverName: this.props.serverConfig.hsName,
|
||||
});
|
||||
if (this.props.serverConfig.hsNameIsDifferent) {
|
||||
const TextWithTooltip = sdk.getComponent("elements.TextWithTooltip");
|
||||
|
||||
signInToText = _t('Sign in to your Matrix account on <underlinedServerName />', {}, {
|
||||
'underlinedServerName': () => {
|
||||
return <TextWithTooltip
|
||||
class="mx_Login_underlinedServerName"
|
||||
tooltip={this.props.serverConfig.hsUrl}
|
||||
>
|
||||
{this.props.serverConfig.hsName}
|
||||
</TextWithTooltip>;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let editLink = null;
|
||||
if (this.props.onEditServerDetailsClick) {
|
||||
editLink = <a className="mx_AuthBody_editServerDetails"
|
||||
href="#" onClick={this.props.onEditServerDetailsClick}
|
||||
>
|
||||
{_t('Change')}
|
||||
</a>;
|
||||
}
|
||||
|
||||
return <h3>
|
||||
{signInToText}
|
||||
{editLink}
|
||||
</h3>;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue