Merge branches 'develop' and 't3chguy/fix_react_complaining' of github.com:matrix-org/matrix-react-sdk into t3chguy/fix_react_complaining
Conflicts: src/components/views/elements/Pill.js
This commit is contained in:
commit
bd0a96f2b2
130 changed files with 2039 additions and 1346 deletions
|
@ -4,7 +4,7 @@ matrix-react-sdk
|
||||||
This is a react-based SDK for inserting a Matrix chat/voip client into a web page.
|
This is a react-based SDK for inserting a Matrix chat/voip client into a web page.
|
||||||
|
|
||||||
This package provides the React components needed to build a Matrix web client
|
This package provides the React components needed to build a Matrix web client
|
||||||
using React. It is not useable in isolation, and instead must must be used from
|
using React. It is not useable in isolation, and instead must be used from
|
||||||
a 'skin'. A skin provides:
|
a 'skin'. A skin provides:
|
||||||
* Customised implementations of presentation components.
|
* Customised implementations of presentation components.
|
||||||
* Custom CSS
|
* Custom CSS
|
||||||
|
@ -82,7 +82,7 @@ practices that anyone working with the SDK needs to be be aware of and uphold:
|
||||||
'Stealing' styling information from other components (including parents)
|
'Stealing' styling information from other components (including parents)
|
||||||
is not cool, as it breaks the independence of the components.
|
is not cool, as it breaks the independence of the components.
|
||||||
|
|
||||||
* CSS classes are named with an app-specific namespacing prefix to try to avoid
|
* CSS classes are named with an app-specific name-spacing prefix to try to avoid
|
||||||
CSS collisions. The base skin shipped by Matrix.org with the matrix-react-sdk
|
CSS collisions. The base skin shipped by Matrix.org with the matrix-react-sdk
|
||||||
uses the naming prefix "mx_". A company called Yoyodyne Inc might use a
|
uses the naming prefix "mx_". A company called Yoyodyne Inc might use a
|
||||||
prefix like "yy_" for its app-specific classes.
|
prefix like "yy_" for its app-specific classes.
|
||||||
|
@ -107,7 +107,7 @@ practices that anyone working with the SDK needs to be be aware of and uphold:
|
||||||
.mx_RoomTile {} in RoomList.css - only RoomTile.css is allowed to define its
|
.mx_RoomTile {} in RoomList.css - only RoomTile.css is allowed to define its
|
||||||
own CSS. Instead, say .mx_RoomList .mx_RoomTile {} to scope the override
|
own CSS. Instead, say .mx_RoomList .mx_RoomTile {} to scope the override
|
||||||
only to the context of RoomList views. N.B. overrides should be relatively
|
only to the context of RoomList views. N.B. overrides should be relatively
|
||||||
rare as in general CSS inheritence should be enough.
|
rare as in general CSS inheritance should be enough.
|
||||||
|
|
||||||
* Components should render only within the bounding box of their outermost DOM
|
* Components should render only within the bounding box of their outermost DOM
|
||||||
element. Page-absolute positioning and negative CSS margins and similar are
|
element. Page-absolute positioning and negative CSS margins and similar are
|
||||||
|
|
|
@ -43,7 +43,6 @@
|
||||||
"diff-i18n": "cp src/i18n/strings/en_EN.json src/i18n/strings/en_EN_orig.json && ./scripts/gen-i18n.js && node scripts/compare-file.js src/i18n/strings/en_EN_orig.json src/i18n/strings/en_EN.json",
|
"diff-i18n": "cp src/i18n/strings/en_EN.json src/i18n/strings/en_EN_orig.json && ./scripts/gen-i18n.js && node scripts/compare-file.js src/i18n/strings/en_EN_orig.json src/i18n/strings/en_EN.json",
|
||||||
"build": "yarn reskindex && yarn start:init",
|
"build": "yarn reskindex && yarn start:init",
|
||||||
"build:watch": "babel src -w --skip-initial-build -d lib --source-maps --copy-files",
|
"build:watch": "babel src -w --skip-initial-build -d lib --source-maps --copy-files",
|
||||||
"emoji-data-strip": "node scripts/emoji-data-strip.js",
|
|
||||||
"start": "yarn start:init && yarn start:all",
|
"start": "yarn start:init && yarn start:all",
|
||||||
"start:all": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n build,reskindex \"yarn build:watch\" \"yarn reskindex:watch\"",
|
"start:all": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n build,reskindex \"yarn build:watch\" \"yarn reskindex:watch\"",
|
||||||
"start:init": "babel src -d lib --source-maps --copy-files",
|
"start:init": "babel src -d lib --source-maps --copy-files",
|
||||||
|
@ -75,7 +74,6 @@
|
||||||
"file-saver": "^1.3.3",
|
"file-saver": "^1.3.3",
|
||||||
"filesize": "3.5.6",
|
"filesize": "3.5.6",
|
||||||
"flux": "2.1.1",
|
"flux": "2.1.1",
|
||||||
"react-focus-lock": "^2.2.1",
|
|
||||||
"focus-visible": "^5.0.2",
|
"focus-visible": "^5.0.2",
|
||||||
"fuse.js": "^2.2.0",
|
"fuse.js": "^2.2.0",
|
||||||
"gemini-scrollbar": "github:matrix-org/gemini-scrollbar#91e1e566",
|
"gemini-scrollbar": "github:matrix-org/gemini-scrollbar#91e1e566",
|
||||||
|
@ -83,6 +81,7 @@
|
||||||
"glob": "^5.0.14",
|
"glob": "^5.0.14",
|
||||||
"glob-to-regexp": "^0.4.1",
|
"glob-to-regexp": "^0.4.1",
|
||||||
"highlight.js": "^9.15.8",
|
"highlight.js": "^9.15.8",
|
||||||
|
"humanize": "^0.0.9",
|
||||||
"is-ip": "^2.0.0",
|
"is-ip": "^2.0.0",
|
||||||
"isomorphic-fetch": "^2.2.1",
|
"isomorphic-fetch": "^2.2.1",
|
||||||
"linkifyjs": "^2.1.6",
|
"linkifyjs": "^2.1.6",
|
||||||
|
@ -100,6 +99,7 @@
|
||||||
"react-addons-css-transition-group": "15.6.2",
|
"react-addons-css-transition-group": "15.6.2",
|
||||||
"react-beautiful-dnd": "^4.0.1",
|
"react-beautiful-dnd": "^4.0.1",
|
||||||
"react-dom": "^16.9.0",
|
"react-dom": "^16.9.0",
|
||||||
|
"react-focus-lock": "^2.2.1",
|
||||||
"react-gemini-scrollbar": "github:matrix-org/react-gemini-scrollbar#9cf17f63b7c0b0ec5f31df27da0f82f7238dc594",
|
"react-gemini-scrollbar": "github:matrix-org/react-gemini-scrollbar#9cf17f63b7c0b0ec5f31df27da0f82f7238dc594",
|
||||||
"resize-observer-polyfill": "^1.5.0",
|
"resize-observer-polyfill": "^1.5.0",
|
||||||
"sanitize-html": "^1.18.4",
|
"sanitize-html": "^1.18.4",
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
@import "./structures/_SearchBox.scss";
|
@import "./structures/_SearchBox.scss";
|
||||||
@import "./structures/_TabbedView.scss";
|
@import "./structures/_TabbedView.scss";
|
||||||
@import "./structures/_TagPanel.scss";
|
@import "./structures/_TagPanel.scss";
|
||||||
@import "./structures/_TagPanelButtons.scss";
|
|
||||||
@import "./structures/_ToastContainer.scss";
|
@import "./structures/_ToastContainer.scss";
|
||||||
@import "./structures/_TopLeftMenuButton.scss";
|
@import "./structures/_TopLeftMenuButton.scss";
|
||||||
@import "./structures/_UploadBar.scss";
|
@import "./structures/_UploadBar.scss";
|
||||||
|
@ -57,6 +56,7 @@
|
||||||
@import "./views/dialogs/_ConfirmUserActionDialog.scss";
|
@import "./views/dialogs/_ConfirmUserActionDialog.scss";
|
||||||
@import "./views/dialogs/_CreateGroupDialog.scss";
|
@import "./views/dialogs/_CreateGroupDialog.scss";
|
||||||
@import "./views/dialogs/_CreateRoomDialog.scss";
|
@import "./views/dialogs/_CreateRoomDialog.scss";
|
||||||
|
@import "./views/dialogs/_DMInviteDialog.scss";
|
||||||
@import "./views/dialogs/_DeactivateAccountDialog.scss";
|
@import "./views/dialogs/_DeactivateAccountDialog.scss";
|
||||||
@import "./views/dialogs/_DeviceVerifyDialog.scss";
|
@import "./views/dialogs/_DeviceVerifyDialog.scss";
|
||||||
@import "./views/dialogs/_DevtoolsDialog.scss";
|
@import "./views/dialogs/_DevtoolsDialog.scss";
|
||||||
|
@ -174,6 +174,7 @@
|
||||||
@import "./views/rooms/_SendMessageComposer.scss";
|
@import "./views/rooms/_SendMessageComposer.scss";
|
||||||
@import "./views/rooms/_Stickers.scss";
|
@import "./views/rooms/_Stickers.scss";
|
||||||
@import "./views/rooms/_TopUnreadMessagesBar.scss";
|
@import "./views/rooms/_TopUnreadMessagesBar.scss";
|
||||||
|
@import "./views/rooms/_UserOnlineDot.scss";
|
||||||
@import "./views/rooms/_WhoIsTypingTile.scss";
|
@import "./views/rooms/_WhoIsTypingTile.scss";
|
||||||
@import "./views/settings/_AvatarSetting.scss";
|
@import "./views/settings/_AvatarSetting.scss";
|
||||||
@import "./views/settings/_CrossSigningPanel.scss";
|
@import "./views/settings/_CrossSigningPanel.scss";
|
||||||
|
|
|
@ -26,11 +26,16 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_CustomRoomTagPanel_scroller {
|
.mx_CustomRoomTagPanel_scroller {
|
||||||
max-height: inherit;
|
max-height: inherit;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_CustomRoomTagPanel .mx_AccessibleButton {
|
.mx_CustomRoomTagPanel .mx_AccessibleButton {
|
||||||
margin: 9px auto;
|
margin: 0 auto;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
|
padding: 10px 0 9px 0;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_CustomRoomTagPanel .mx_BaseAvatar_image {
|
.mx_CustomRoomTagPanel .mx_BaseAvatar_image {
|
||||||
|
@ -39,7 +44,13 @@ limitations under the License.
|
||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_CustomRoomTagPanel .mx_AccessibleButton.CustomRoomTagPanel_tileSelected .mx_BaseAvatar_image {
|
.mx_CustomRoomTagPanel .mx_AccessibleButton.CustomRoomTagPanel_tileSelected::before {
|
||||||
border: 3px solid $warning-color;
|
content: '';
|
||||||
border-radius: 40px;
|
height: 56px;
|
||||||
|
background-color: $accent-color-alt;
|
||||||
|
width: 5px;
|
||||||
|
position: absolute;
|
||||||
|
left: -15px;
|
||||||
|
border-radius: 0 3px 3px 0;
|
||||||
|
top: 2px; // 10 [padding-top] - (56 - 40)/2
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
.mx_TagPanel .mx_TagPanel_tagTileContainer > div {
|
.mx_TagPanel .mx_TagPanel_tagTileContainer > div {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
padding: 5px 0 4px 0;
|
padding: 10px 0 9px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_TagPanel .mx_TagTile {
|
.mx_TagPanel .mx_TagTile {
|
||||||
|
@ -82,21 +82,39 @@ limitations under the License.
|
||||||
// opacity: 1;
|
// opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_TagPanel .mx_TagTile.mx_TagTile_selected .mx_TagTile_avatar .mx_BaseAvatar {
|
.mx_TagPanel .mx_TagTile_plus {
|
||||||
background-color: $accent-color;
|
margin-bottom: 12px;
|
||||||
border-radius: 40px;
|
|
||||||
|
|
||||||
/* In case this is a "initial" avatar */
|
|
||||||
display: block;
|
|
||||||
height: 40px;
|
height: 40px;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background-color: $roomheader-addroom-bg-color;
|
||||||
|
position: relative;
|
||||||
|
/* overwrite mx_RoleButton inline-block */
|
||||||
|
display: block !important;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: $roomheader-addroom-fg-color;
|
||||||
|
mask-image: url('$(res)/img/feather-customised/plus.svg');
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_TagPanel .mx_TagTile_selected .mx_BaseAvatar_image {
|
.mx_TagPanel .mx_TagTile.mx_TagTile_selected::before {
|
||||||
border: 3px solid $accent-color;
|
content: '';
|
||||||
height: 40px;
|
height: 56px;
|
||||||
width: 40px;
|
background-color: $accent-color;
|
||||||
box-sizing: border-box;
|
width: 5px;
|
||||||
|
position: absolute;
|
||||||
|
left: -15px;
|
||||||
|
border-radius: 0 3px 3px 0;
|
||||||
|
top: -8px; // (56 - 40)/2
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_TagPanel .mx_TagTile.mx_AccessibleButton:focus {
|
.mx_TagPanel .mx_TagTile.mx_AccessibleButton:focus {
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2019 New Vector Ltd.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.mx_TagPanelButtons {
|
|
||||||
background-color: $tagpanel-bg-color;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 17px 0 3px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_TagPanelButtons > .mx_GroupsButton::before {
|
|
||||||
mask: url('$(res)/img/feather-customised/users.svg');
|
|
||||||
mask-position: center 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_TagPanelButtons > .mx_TagPanelButtons_report::before {
|
|
||||||
mask: url('$(res)/img/feather-customised/life-buoy.svg');
|
|
||||||
mask-position: center 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_TagPanelButtons > .mx_AccessibleButton {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
height: 40px;
|
|
||||||
width: 40px;
|
|
||||||
border-radius: 20px;
|
|
||||||
background-color: $tagpanel-button-color;
|
|
||||||
position: relative;
|
|
||||||
/* overwrite mx_RoleButton inline-block */
|
|
||||||
display: block !important;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
background-color: $tagpanel-bg-color;
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -53,6 +53,10 @@ limitations under the License.
|
||||||
mask-image: url('$(res)/img/feather-customised/home.svg');
|
mask-image: url('$(res)/img/feather-customised/home.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_TopLeftMenu_icon_help::after {
|
||||||
|
mask-image: url('$(res)/img/feather-customised/life-buoy.svg');
|
||||||
|
}
|
||||||
|
|
||||||
.mx_TopLeftMenu_icon_settings::after {
|
.mx_TopLeftMenu_icon_settings::after {
|
||||||
mask-image: url('$(res)/img/feather-customised/settings.svg');
|
mask-image: url('$(res)/img/feather-customised/settings.svg');
|
||||||
}
|
}
|
||||||
|
|
81
res/css/views/dialogs/_DMInviteDialog.scss
Normal file
81
res/css/views/dialogs/_DMInviteDialog.scss
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019, 2020 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_DMInviteDialog_addressBar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
.mx_DMInviteDialog_editor {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%; // Needed to make the Field inside grow
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Field {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DMInviteDialog_goButton {
|
||||||
|
width: 48px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DMInviteDialog_section {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $muted-fg-color;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DMInviteDialog_roomTile {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 5px 10px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $user-tile-hover-bg-color;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DMInviteDialog_roomTile_name {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
margin-left: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DMInviteDialog_roomTile_userId {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $muted-fg-color;
|
||||||
|
margin-left: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_DMInviteDialog_roomTile_time {
|
||||||
|
text-align: right;
|
||||||
|
font-size: 12px;
|
||||||
|
color: $muted-fg-color;
|
||||||
|
float: right;
|
||||||
|
line-height: 36px; // Height of the avatar to keep the time vertically aligned
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -29,6 +29,11 @@ limitations under the License.
|
||||||
mask-image: url('$(res)/img/feather-customised/users-sm.svg');
|
mask-image: url('$(res)/img/feather-customised/users-sm.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_RoomSettingsDialog_bridgesIcon::before {
|
||||||
|
// This icon is pants, please improve :)
|
||||||
|
mask-image: url('$(res)/img/feather-customised/bridge.svg');
|
||||||
|
}
|
||||||
|
|
||||||
.mx_RoomSettingsDialog_warningIcon::before {
|
.mx_RoomSettingsDialog_warningIcon::before {
|
||||||
mask-image: url('$(res)/img/feather-customised/warning-triangle.svg');
|
mask-image: url('$(res)/img/feather-customised/warning-triangle.svg');
|
||||||
}
|
}
|
||||||
|
@ -50,3 +55,17 @@ limitations under the License.
|
||||||
mask-size: 36px;
|
mask-size: 36px;
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_RoomSettingsDialog_BridgeList {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomSettingsDialog_BridgeList li {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
border-width: 1px 0px;
|
||||||
|
border-color: #dee1f3;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -353,7 +354,6 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody {
|
||||||
left: 46px;
|
left: 46px;
|
||||||
width: 15px;
|
width: 15px;
|
||||||
height: 15px;
|
height: 15px;
|
||||||
cursor: pointer;
|
|
||||||
display: block;
|
display: block;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
|
|
@ -40,4 +40,5 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_RoomRecoveryReminder_secondary {
|
.mx_RoomRecoveryReminder_secondary {
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|
23
res/css/views/rooms/_UserOnlineDot.scss
Normal file
23
res/css/views/rooms/_UserOnlineDot.scss
Normal file
|
@ -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_UserOnlineDot {
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: $accent-color;
|
||||||
|
height: 5px;
|
||||||
|
width: 5px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
50
res/img/feather-customised/bridge.svg
Normal file
50
res/img/feather-customised/bridge.svg
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<?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"
|
||||||
|
id="svg8"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 5.487504 5.7341776"
|
||||||
|
height="5.7341776mm"
|
||||||
|
width="5.487504mm">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<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>
|
||||||
|
<g
|
||||||
|
transform="translate(14.166523,-96.032669)"
|
||||||
|
id="layer1">
|
||||||
|
<rect
|
||||||
|
y="99.461258"
|
||||||
|
x="-10.861272"
|
||||||
|
height="2.0555882"
|
||||||
|
width="1.9322528"
|
||||||
|
id="rect831-6"
|
||||||
|
style="fill:none;stroke:#b8bec9;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
|
||||||
|
<path
|
||||||
|
id="path883"
|
||||||
|
d="m -11.98427,98.338257 1.122998,1.122998"
|
||||||
|
style="fill:#b8bec9;fill-opacity:1;stroke:#b8bec9;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||||
|
<rect
|
||||||
|
transform="scale(-1)"
|
||||||
|
y="-98.338257"
|
||||||
|
x="11.98427"
|
||||||
|
height="2.0555882"
|
||||||
|
width="1.9322529"
|
||||||
|
id="rect831-6-7"
|
||||||
|
style="fill:none;stroke:#b8bec9;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
4
res/img/feather-customised/plus.svg
Normal file
4
res/img/feather-customised/plus.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 5V19" stroke="#2E2F32" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M5 12H19" stroke="#2E2F32" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 311 B |
|
@ -16,6 +16,7 @@ $room-highlight-color: #343a46;
|
||||||
// typical text (dark-on-white in light skin)
|
// typical text (dark-on-white in light skin)
|
||||||
$primary-fg-color: $text-primary-color;
|
$primary-fg-color: $text-primary-color;
|
||||||
$primary-bg-color: $bg-color;
|
$primary-bg-color: $bg-color;
|
||||||
|
$muted-fg-color: $header-panel-text-primary-color;
|
||||||
|
|
||||||
// used for dialog box text
|
// used for dialog box text
|
||||||
$light-fg-color: $header-panel-text-secondary-color;
|
$light-fg-color: $header-panel-text-secondary-color;
|
||||||
|
@ -172,6 +173,8 @@ $interactive-tooltip-fg-color: #ffffff;
|
||||||
|
|
||||||
$breadcrumb-placeholder-bg-color: #272c35;
|
$breadcrumb-placeholder-bg-color: #272c35;
|
||||||
|
|
||||||
|
$user-tile-hover-bg-color: $header-panel-bg-color;
|
||||||
|
|
||||||
// ***** Mixins! *****
|
// ***** Mixins! *****
|
||||||
|
|
||||||
@define-mixin mx_DialogButton {
|
@define-mixin mx_DialogButton {
|
||||||
|
@ -243,3 +246,13 @@ $breadcrumb-placeholder-bg-color: #272c35;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// diff highlight colors
|
||||||
|
// intentionally swapped to avoid inversion
|
||||||
|
.hljs-addition {
|
||||||
|
background: #fdd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-deletion {
|
||||||
|
background: #dfd;
|
||||||
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ $header-panel-bg-color: #f3f8fd;
|
||||||
// typical text (dark-on-white in light skin)
|
// typical text (dark-on-white in light skin)
|
||||||
$primary-fg-color: #2e2f32;
|
$primary-fg-color: #2e2f32;
|
||||||
$primary-bg-color: #ffffff;
|
$primary-bg-color: #ffffff;
|
||||||
|
$muted-fg-color: #61708b; // Commonly used in headings and relevant alt text
|
||||||
|
|
||||||
// used for dialog box text
|
// used for dialog box text
|
||||||
$light-fg-color: #747474;
|
$light-fg-color: #747474;
|
||||||
|
@ -293,6 +294,8 @@ $interactive-tooltip-fg-color: #ffffff;
|
||||||
|
|
||||||
$breadcrumb-placeholder-bg-color: #e8eef5;
|
$breadcrumb-placeholder-bg-color: #e8eef5;
|
||||||
|
|
||||||
|
$user-tile-hover-bg-color: $header-panel-bg-color;
|
||||||
|
|
||||||
// ***** Mixins! *****
|
// ***** Mixins! *****
|
||||||
|
|
||||||
@define-mixin mx_DialogButton {
|
@define-mixin mx_DialogButton {
|
||||||
|
@ -338,3 +341,12 @@ $breadcrumb-placeholder-bg-color: #e8eef5;
|
||||||
color: $accent-color;
|
color: $accent-color;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// diff highlight colors
|
||||||
|
.hljs-addition {
|
||||||
|
background: #dfd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-deletion {
|
||||||
|
background: #fdd;
|
||||||
|
}
|
||||||
|
|
|
@ -36,7 +36,8 @@ echo "--- Install synapse & other dependencies"
|
||||||
./install.sh
|
./install.sh
|
||||||
# install static webserver to server symlinked local copy of riot
|
# install static webserver to server symlinked local copy of riot
|
||||||
./riot/install-webserver.sh
|
./riot/install-webserver.sh
|
||||||
mkdir logs || rm -r logs/*
|
rm -r logs || true
|
||||||
|
mkdir logs
|
||||||
echo "+++ Running end-to-end tests"
|
echo "+++ Running end-to-end tests"
|
||||||
TESTS_STARTED=1
|
TESTS_STARTED=1
|
||||||
./run.sh --no-sandbox --log-directory logs/
|
./run.sh --no-sandbox --log-directory logs/
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
// This generates src/stripped-emoji.json as used by the EmojiProvider autocomplete
|
|
||||||
// provider.
|
|
||||||
|
|
||||||
const EMOJIBASE = require('emojibase-data/en/compact.json');
|
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
const output = EMOJIBASE.map(
|
|
||||||
(datum) => {
|
|
||||||
const newDatum = {
|
|
||||||
name: datum.annotation,
|
|
||||||
shortname: `:${datum.shortcodes[0]}:`,
|
|
||||||
category: datum.group,
|
|
||||||
emoji_order: datum.order,
|
|
||||||
};
|
|
||||||
if (datum.shortcodes.length > 1) {
|
|
||||||
newDatum.aliases = datum.shortcodes.slice(1).map(s => `:${s}:`);
|
|
||||||
}
|
|
||||||
if (datum.emoticon) {
|
|
||||||
newDatum.aliases_ascii = [ datum.emoticon ];
|
|
||||||
}
|
|
||||||
return newDatum;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Write to a file in src. Changes should be checked into git. This file is copied by
|
|
||||||
// babel using --copy-files
|
|
||||||
fs.writeFileSync('./src/stripped-emoji.json', JSON.stringify(output));
|
|
|
@ -422,6 +422,9 @@ export default class ContentMessages {
|
||||||
|
|
||||||
const UploadConfirmDialog = sdk.getComponent("dialogs.UploadConfirmDialog");
|
const UploadConfirmDialog = sdk.getComponent("dialogs.UploadConfirmDialog");
|
||||||
let uploadAll = false;
|
let uploadAll = false;
|
||||||
|
// Promise to complete before sending next file into room, used for synchronisation of file-sending
|
||||||
|
// to match the order the files were specified in
|
||||||
|
let promBefore = Promise.resolve();
|
||||||
for (let i = 0; i < okFiles.length; ++i) {
|
for (let i = 0; i < okFiles.length; ++i) {
|
||||||
const file = okFiles[i];
|
const file = okFiles[i];
|
||||||
if (!uploadAll) {
|
if (!uploadAll) {
|
||||||
|
@ -440,11 +443,11 @@ export default class ContentMessages {
|
||||||
});
|
});
|
||||||
if (!shouldContinue) break;
|
if (!shouldContinue) break;
|
||||||
}
|
}
|
||||||
this._sendContentToRoom(file, roomId, matrixClient);
|
promBefore = this._sendContentToRoom(file, roomId, matrixClient, promBefore);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_sendContentToRoom(file, roomId, matrixClient) {
|
_sendContentToRoom(file, roomId, matrixClient, promBefore) {
|
||||||
const content = {
|
const content = {
|
||||||
body: file.name || 'Attachment',
|
body: file.name || 'Attachment',
|
||||||
info: {
|
info: {
|
||||||
|
@ -517,7 +520,10 @@ export default class ContentMessages {
|
||||||
content.file = result.file;
|
content.file = result.file;
|
||||||
content.url = result.url;
|
content.url = result.url;
|
||||||
});
|
});
|
||||||
}).then(function(url) {
|
}).then((url) => {
|
||||||
|
// Await previous message being sent into the room
|
||||||
|
return promBefore;
|
||||||
|
}).then(function() {
|
||||||
return matrixClient.sendMessage(roomId, content);
|
return matrixClient.sendMessage(roomId, content);
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
error = err;
|
error = err;
|
||||||
|
|
|
@ -97,7 +97,7 @@ export const crossSigningCallbacks = {
|
||||||
*
|
*
|
||||||
* Additionally, the secret storage keys are cached during the scope of this function
|
* Additionally, the secret storage keys are cached during the scope of this function
|
||||||
* to ensure the user is prompted only once for their secret storage
|
* to ensure the user is prompted only once for their secret storage
|
||||||
* passphrase. The cache is then
|
* passphrase. The cache is then cleared once the provided function completes.
|
||||||
*
|
*
|
||||||
* @param {Function} [func] An operation to perform once secret storage has been
|
* @param {Function} [func] An operation to perform once secret storage has been
|
||||||
* bootstrapped. Optional.
|
* bootstrapped. Optional.
|
||||||
|
|
|
@ -32,9 +32,9 @@ import classNames from 'classnames';
|
||||||
import MatrixClientPeg from './MatrixClientPeg';
|
import MatrixClientPeg from './MatrixClientPeg';
|
||||||
import url from 'url';
|
import url from 'url';
|
||||||
|
|
||||||
import EMOJIBASE from 'emojibase-data/en/compact.json';
|
|
||||||
import EMOJIBASE_REGEX from 'emojibase-regex';
|
import EMOJIBASE_REGEX from 'emojibase-regex';
|
||||||
import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks";
|
import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks";
|
||||||
|
import {SHORTCODE_TO_EMOJI, getEmojiFromUnicode} from "./emoji";
|
||||||
|
|
||||||
linkifyMatrix(linkify);
|
linkifyMatrix(linkify);
|
||||||
|
|
||||||
|
@ -58,8 +58,6 @@ const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
|
||||||
|
|
||||||
const PERMITTED_URL_SCHEMES = ['http', 'https', 'ftp', 'mailto', 'magnet'];
|
const PERMITTED_URL_SCHEMES = ['http', 'https', 'ftp', 'mailto', 'magnet'];
|
||||||
|
|
||||||
const VARIATION_SELECTOR = String.fromCharCode(0xFE0F);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Return true if the given string contains emoji
|
* Return true if the given string contains emoji
|
||||||
* Uses a much, much simpler regex than emojibase's so will give false
|
* Uses a much, much simpler regex than emojibase's so will give false
|
||||||
|
@ -71,21 +69,6 @@ function mightContainEmoji(str) {
|
||||||
return SURROGATE_PAIR_PATTERN.test(str) || SYMBOL_PATTERN.test(str);
|
return SURROGATE_PAIR_PATTERN.test(str) || SYMBOL_PATTERN.test(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Find emoji data in emojibase by character.
|
|
||||||
*
|
|
||||||
* @param {String} char The emoji character
|
|
||||||
* @return {Object} The emoji data
|
|
||||||
*/
|
|
||||||
export function findEmojiData(char) {
|
|
||||||
// Check against both the char and the char with an empty variation selector
|
|
||||||
// appended because that's how emojibase stores its base emojis which have
|
|
||||||
// variations.
|
|
||||||
// See also https://github.com/vector-im/riot-web/issues/9785.
|
|
||||||
const emptyVariation = char + VARIATION_SELECTOR;
|
|
||||||
return EMOJIBASE.find(e => e.unicode === char || e.unicode === emptyVariation);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the shortcode for an emoji character.
|
* Returns the shortcode for an emoji character.
|
||||||
*
|
*
|
||||||
|
@ -93,7 +76,7 @@ export function findEmojiData(char) {
|
||||||
* @return {String} The shortcode (such as :thumbup:)
|
* @return {String} The shortcode (such as :thumbup:)
|
||||||
*/
|
*/
|
||||||
export function unicodeToShortcode(char) {
|
export function unicodeToShortcode(char) {
|
||||||
const data = findEmojiData(char);
|
const data = getEmojiFromUnicode(char);
|
||||||
return (data && data.shortcodes ? `:${data.shortcodes[0]}:` : '');
|
return (data && data.shortcodes ? `:${data.shortcodes[0]}:` : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +88,7 @@ export function unicodeToShortcode(char) {
|
||||||
*/
|
*/
|
||||||
export function shortcodeToUnicode(shortcode) {
|
export function shortcodeToUnicode(shortcode) {
|
||||||
shortcode = shortcode.slice(1, shortcode.length - 1);
|
shortcode = shortcode.slice(1, shortcode.length - 1);
|
||||||
const data = EMOJIBASE.find(e => e.shortcodes && e.shortcodes.includes(shortcode));
|
const data = SHORTCODE_TO_EMOJI.get(shortcode);
|
||||||
return data ? data.unicode : null;
|
return data ? data.unicode : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -111,6 +111,12 @@ export default class KeyRequestHandler {
|
||||||
this._currentUser = null;
|
this._currentUser = null;
|
||||||
this._currentDevice = null;
|
this._currentDevice = null;
|
||||||
|
|
||||||
|
if (!this._pendingKeyRequests[userId] || !this._pendingKeyRequests[userId][deviceId]) {
|
||||||
|
// request was removed in the time the dialog was displayed
|
||||||
|
this._processNextRequest();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (r) {
|
if (r) {
|
||||||
for (const req of this._pendingKeyRequests[userId][deviceId]) {
|
for (const req of this._pendingKeyRequests[userId][deviceId]) {
|
||||||
req.share();
|
req.share();
|
||||||
|
|
|
@ -25,6 +25,7 @@ import sdk from './';
|
||||||
import dis from './dispatcher';
|
import dis from './dispatcher';
|
||||||
import DMRoomMap from './utils/DMRoomMap';
|
import DMRoomMap from './utils/DMRoomMap';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invites multiple addresses to a room
|
* Invites multiple addresses to a room
|
||||||
|
@ -41,6 +42,18 @@ function inviteMultipleToRoom(roomId, addrs) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showStartChatInviteDialog() {
|
export function showStartChatInviteDialog() {
|
||||||
|
if (SettingsStore.isFeatureEnabled("feature_ftue_dms")) {
|
||||||
|
const DMInviteDialog = sdk.getComponent("dialogs.DMInviteDialog");
|
||||||
|
Modal.createTrackedDialog('Start DM', '', DMInviteDialog, {
|
||||||
|
onFinished: (inviteIds) => {
|
||||||
|
// TODO: Replace _onStartDmFinished with less hacks
|
||||||
|
if (inviteIds.length > 0) _onStartDmFinished(true, inviteIds.map(i => ({address: i})));
|
||||||
|
// else ignore and just do nothing
|
||||||
|
},
|
||||||
|
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
|
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
|
||||||
|
|
||||||
Modal.createTrackedDialog('Start a chat', '', AddressPickerDialog, {
|
Modal.createTrackedDialog('Start a chat', '', AddressPickerDialog, {
|
||||||
|
@ -99,7 +112,7 @@ export function isValid3pidInvite(event) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Immutable DMs replaces this
|
// TODO: Canonical DMs replaces this
|
||||||
function _onStartDmFinished(shouldInvite, addrs) {
|
function _onStartDmFinished(shouldInvite, addrs) {
|
||||||
if (!shouldInvite) return;
|
if (!shouldInvite) return;
|
||||||
|
|
||||||
|
|
|
@ -780,54 +780,52 @@ export const CommandMap = {
|
||||||
const deviceId = matches[2];
|
const deviceId = matches[2];
|
||||||
const fingerprint = matches[3];
|
const fingerprint = matches[3];
|
||||||
|
|
||||||
return success(
|
return success((async () => {
|
||||||
// Promise.resolve to handle transition from static result to promise; can be removed
|
const device = await cli.getStoredDevice(userId, deviceId);
|
||||||
// in future
|
if (!device) {
|
||||||
Promise.resolve(cli.getStoredDevice(userId, deviceId)).then((device) => {
|
throw new Error(_t('Unknown (user, device) pair:') + ` (${userId}, ${deviceId})`);
|
||||||
if (!device) {
|
}
|
||||||
throw new Error(_t('Unknown (user, device) pair:') + ` (${userId}, ${deviceId})`);
|
const deviceTrust = await cli.checkDeviceTrust(userId, deviceId);
|
||||||
}
|
|
||||||
|
|
||||||
if (device.isVerified()) {
|
if (deviceTrust.isVerified()) {
|
||||||
if (device.getFingerprint() === fingerprint) {
|
if (device.getFingerprint() === fingerprint) {
|
||||||
throw new Error(_t('Device already verified!'));
|
throw new Error(_t('Device already verified!'));
|
||||||
} else {
|
} else {
|
||||||
throw new Error(_t('WARNING: Device already verified, but keys do NOT MATCH!'));
|
throw new Error(_t('WARNING: Device already verified, but keys do NOT MATCH!'));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (device.getFingerprint() !== fingerprint) {
|
if (device.getFingerprint() !== fingerprint) {
|
||||||
const fprint = device.getFingerprint();
|
const fprint = device.getFingerprint();
|
||||||
throw new Error(
|
throw new Error(
|
||||||
_t('WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device' +
|
_t('WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device' +
|
||||||
' %(deviceId)s is "%(fprint)s" which does not match the provided key ' +
|
' %(deviceId)s is "%(fprint)s" which does not match the provided key ' +
|
||||||
'"%(fingerprint)s". This could mean your communications are being intercepted!',
|
'"%(fingerprint)s". This could mean your communications are being intercepted!',
|
||||||
{
|
{
|
||||||
fprint,
|
fprint,
|
||||||
userId,
|
userId,
|
||||||
deviceId,
|
deviceId,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return cli.setDeviceVerified(userId, deviceId, true);
|
await cli.setDeviceVerified(userId, deviceId, true);
|
||||||
}).then(() => {
|
|
||||||
// Tell the user we verified everything
|
// Tell the user we verified everything
|
||||||
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
|
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
|
||||||
Modal.createTrackedDialog('Slash Commands', 'Verified key', InfoDialog, {
|
Modal.createTrackedDialog('Slash Commands', 'Verified key', InfoDialog, {
|
||||||
title: _t('Verified key'),
|
title: _t('Verified key'),
|
||||||
description: <div>
|
description: <div>
|
||||||
<p>
|
<p>
|
||||||
{
|
{
|
||||||
_t('The signing key you provided matches the signing key you received ' +
|
_t('The signing key you provided matches the signing key you received ' +
|
||||||
'from %(userId)s\'s device %(deviceId)s. Device marked as verified.',
|
'from %(userId)s\'s device %(deviceId)s. Device marked as verified.',
|
||||||
{userId, deviceId})
|
{userId, deviceId})
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
</div>,
|
</div>,
|
||||||
});
|
});
|
||||||
}),
|
})());
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
|
|
|
@ -473,7 +473,7 @@ function textForPowerEvent(event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function textForPinnedEvent(event) {
|
function textForPinnedEvent(event) {
|
||||||
const senderName = event.getSender();
|
const senderName = event.sender ? event.sender.name : event.getSender();
|
||||||
return _t("%(senderName)s changed the pinned messages for the room.", {senderName});
|
return _t("%(senderName)s changed the pinned messages for the room.", {senderName});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,9 @@ import { _t } from '../../../languageHandler';
|
||||||
const sdk = require('../../../index');
|
const sdk = require('../../../index');
|
||||||
const MatrixClientPeg = require("../../../MatrixClientPeg");
|
const MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||||
|
|
||||||
|
// XXX: This component is not cross-signing aware.
|
||||||
|
// https://github.com/vector-im/riot-web/issues/11752 tracks either updating this
|
||||||
|
// component or taking it out to pasture.
|
||||||
module.exports = createReactClass({
|
module.exports = createReactClass({
|
||||||
displayName: 'EncryptedEventDialog',
|
displayName: 'EncryptedEventDialog',
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2018, 2019 New Vector Ltd
|
Copyright 2018, 2019 New Vector Ltd
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -17,11 +17,14 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import FileSaver from 'file-saver';
|
import FileSaver from 'file-saver';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import sdk from '../../../../index';
|
import sdk from '../../../../index';
|
||||||
import MatrixClientPeg from '../../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../../MatrixClientPeg';
|
||||||
import { scorePassword } from '../../../../utils/PasswordScorer';
|
import { scorePassword } from '../../../../utils/PasswordScorer';
|
||||||
import { _t } from '../../../../languageHandler';
|
import { _t } from '../../../../languageHandler';
|
||||||
|
import { accessSecretStorage } from '../../../../CrossSigningManager';
|
||||||
|
import SettingsStore from '../../../../../lib/settings/SettingsStore';
|
||||||
|
|
||||||
const PHASE_PASSPHRASE = 0;
|
const PHASE_PASSPHRASE = 0;
|
||||||
const PHASE_PASSPHRASE_CONFIRM = 1;
|
const PHASE_PASSPHRASE_CONFIRM = 1;
|
||||||
|
@ -49,10 +52,20 @@ function selectText(target) {
|
||||||
* on the server.
|
* on the server.
|
||||||
*/
|
*/
|
||||||
export default class CreateKeyBackupDialog extends React.PureComponent {
|
export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
secureSecretStorage: PropTypes.bool,
|
||||||
|
onFinished: PropTypes.func.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
this._recoveryKeyNode = null;
|
||||||
|
this._keyBackupInfo = null;
|
||||||
|
this._setZxcvbnResultTimeout = null;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
secureSecretStorage: props.secureSecretStorage,
|
||||||
phase: PHASE_PASSPHRASE,
|
phase: PHASE_PASSPHRASE,
|
||||||
passPhrase: '',
|
passPhrase: '',
|
||||||
passPhraseConfirm: '',
|
passPhraseConfirm: '',
|
||||||
|
@ -61,12 +74,25 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
zxcvbnResult: null,
|
zxcvbnResult: null,
|
||||||
setPassPhrase: false,
|
setPassPhrase: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (this.state.secureSecretStorage === undefined) {
|
||||||
|
this.state.secureSecretStorage =
|
||||||
|
SettingsStore.isFeatureEnabled("feature_cross_signing");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're using secret storage, skip ahead to the backing up step, as
|
||||||
|
// `accessSecretStorage` will handle passphrases as needed.
|
||||||
|
if (this.state.secureSecretStorage) {
|
||||||
|
this.state.phase = PHASE_BACKINGUP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentDidMount() {
|
||||||
this._recoveryKeyNode = null;
|
// If we're using secret storage, skip ahead to the backing up step, as
|
||||||
this._keyBackupInfo = null;
|
// `accessSecretStorage` will handle passphrases as needed.
|
||||||
this._setZxcvbnResultTimeout = null;
|
if (this.state.secureSecretStorage) {
|
||||||
|
this._createBackup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -103,15 +129,26 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
_createBackup = async () => {
|
_createBackup = async () => {
|
||||||
|
const { secureSecretStorage } = this.state;
|
||||||
this.setState({
|
this.setState({
|
||||||
phase: PHASE_BACKINGUP,
|
phase: PHASE_BACKINGUP,
|
||||||
error: null,
|
error: null,
|
||||||
});
|
});
|
||||||
let info;
|
let info;
|
||||||
try {
|
try {
|
||||||
info = await MatrixClientPeg.get().createKeyBackupVersion(
|
if (secureSecretStorage) {
|
||||||
this._keyBackupInfo,
|
await accessSecretStorage(async () => {
|
||||||
);
|
info = await MatrixClientPeg.get().prepareKeyBackupVersion(
|
||||||
|
null /* random key */,
|
||||||
|
{ secureSecretStorage: true },
|
||||||
|
);
|
||||||
|
info = await MatrixClientPeg.get().createKeyBackupVersion(info);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
info = await MatrixClientPeg.get().createKeyBackupVersion(
|
||||||
|
this._keyBackupInfo,
|
||||||
|
);
|
||||||
|
}
|
||||||
await MatrixClientPeg.get().scheduleAllGroupSessionsForBackup();
|
await MatrixClientPeg.get().scheduleAllGroupSessionsForBackup();
|
||||||
this.setState({
|
this.setState({
|
||||||
phase: PHASE_DONE,
|
phase: PHASE_DONE,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2018-2019 New Vector Ltd
|
Copyright 2018, 2019 New Vector Ltd
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -40,9 +41,11 @@ export default class NewRecoveryMethodDialog extends React.PureComponent {
|
||||||
|
|
||||||
onSetupClick = async () => {
|
onSetupClick = async () => {
|
||||||
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
|
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
|
||||||
Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, {
|
Modal.createTrackedDialog(
|
||||||
onFinished: this.props.onFinished,
|
'Restore Backup', '', RestoreKeyBackupDialog, {
|
||||||
});
|
onFinished: this.props.onFinished,
|
||||||
|
}, null, /* priority = */ false, /* static = */ true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019 New Vector Ltd
|
Copyright 2019 New Vector Ltd
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -35,6 +36,7 @@ export default class RecoveryMethodRemovedDialog extends React.PureComponent {
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
|
Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
|
||||||
import("./CreateKeyBackupDialog"),
|
import("./CreateKeyBackupDialog"),
|
||||||
|
null, null, /* priority = */ false, /* static = */ true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,13 +23,15 @@ import FileSaver from 'file-saver';
|
||||||
import { _t } from '../../../../languageHandler';
|
import { _t } from '../../../../languageHandler';
|
||||||
import Modal from '../../../../Modal';
|
import Modal from '../../../../Modal';
|
||||||
|
|
||||||
const PHASE_PASSPHRASE = 0;
|
const PHASE_LOADING = 0;
|
||||||
const PHASE_PASSPHRASE_CONFIRM = 1;
|
const PHASE_MIGRATE = 1;
|
||||||
const PHASE_SHOWKEY = 2;
|
const PHASE_PASSPHRASE = 2;
|
||||||
const PHASE_KEEPITSAFE = 3;
|
const PHASE_PASSPHRASE_CONFIRM = 3;
|
||||||
const PHASE_STORING = 4;
|
const PHASE_SHOWKEY = 4;
|
||||||
const PHASE_DONE = 5;
|
const PHASE_KEEPITSAFE = 5;
|
||||||
const PHASE_OPTOUT_CONFIRM = 6;
|
const PHASE_STORING = 6;
|
||||||
|
const PHASE_DONE = 7;
|
||||||
|
const PHASE_OPTOUT_CONFIRM = 8;
|
||||||
|
|
||||||
const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc.
|
const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc.
|
||||||
const PASSPHRASE_FEEDBACK_DELAY = 500; // How long after keystroke to offer passphrase feedback, ms.
|
const PASSPHRASE_FEEDBACK_DELAY = 500; // How long after keystroke to offer passphrase feedback, ms.
|
||||||
|
@ -58,7 +60,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
this._setZxcvbnResultTimeout = null;
|
this._setZxcvbnResultTimeout = null;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
phase: PHASE_PASSPHRASE,
|
phase: PHASE_LOADING,
|
||||||
passPhrase: '',
|
passPhrase: '',
|
||||||
passPhraseConfirm: '',
|
passPhraseConfirm: '',
|
||||||
copied: false,
|
copied: false,
|
||||||
|
@ -66,6 +68,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
zxcvbnResult: null,
|
zxcvbnResult: null,
|
||||||
setPassPhrase: false,
|
setPassPhrase: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this._fetchBackupInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -74,10 +78,23 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _fetchBackupInfo() {
|
||||||
|
const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
phase: backupInfo ? PHASE_MIGRATE: PHASE_PASSPHRASE,
|
||||||
|
backupInfo,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_collectRecoveryKeyNode = (n) => {
|
_collectRecoveryKeyNode = (n) => {
|
||||||
this._recoveryKeyNode = n;
|
this._recoveryKeyNode = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onMigrateNextClick = () => {
|
||||||
|
this._bootstrapSecretStorage();
|
||||||
|
}
|
||||||
|
|
||||||
_onCopyClick = () => {
|
_onCopyClick = () => {
|
||||||
selectText(this._recoveryKeyNode);
|
selectText(this._recoveryKeyNode);
|
||||||
const successful = document.execCommand('copy');
|
const successful = document.execCommand('copy');
|
||||||
|
@ -125,6 +142,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
createSecretStorageKey: async () => this._keyInfo,
|
createSecretStorageKey: async () => this._keyInfo,
|
||||||
|
keyBackupInfo: this.state.backupInfo,
|
||||||
});
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
phase: PHASE_DONE,
|
phase: PHASE_DONE,
|
||||||
|
@ -250,6 +268,27 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
return this.state.zxcvbnResult && this.state.zxcvbnResult.score >= PASSWORD_MIN_SCORE;
|
return this.state.zxcvbnResult && this.state.zxcvbnResult.score >= PASSWORD_MIN_SCORE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_renderPhaseMigrate() {
|
||||||
|
// TODO: This is a temporary screen so people who have the labs flag turned on and
|
||||||
|
// click the button are aware they're making a change to their account.
|
||||||
|
// Once we're confident enough in this (and it's supported enough) we can do
|
||||||
|
// it automatically.
|
||||||
|
// https://github.com/vector-im/riot-web/issues/11696
|
||||||
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
|
return <div>
|
||||||
|
<p>{_t(
|
||||||
|
"Secret Storage will be set up using your existing key backup details." +
|
||||||
|
"Your secret storage passphrase and recovery key will be the same as " +
|
||||||
|
" they were for your key backup",
|
||||||
|
)}</p>
|
||||||
|
<DialogButtons primaryButton={_t('Next')}
|
||||||
|
onPrimaryButtonClick={this._onMigrateNextClick}
|
||||||
|
hasCancel={true}
|
||||||
|
onCancel={this._onCancel}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
_renderPhasePassPhrase() {
|
_renderPhasePassPhrase() {
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
|
|
||||||
|
@ -449,7 +488,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderBusyPhase(text) {
|
_renderBusyPhase() {
|
||||||
const Spinner = sdk.getComponent('views.elements.Spinner');
|
const Spinner = sdk.getComponent('views.elements.Spinner');
|
||||||
return <div>
|
return <div>
|
||||||
<Spinner />
|
<Spinner />
|
||||||
|
@ -488,6 +527,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
|
|
||||||
_titleForPhase(phase) {
|
_titleForPhase(phase) {
|
||||||
switch (phase) {
|
switch (phase) {
|
||||||
|
case PHASE_MIGRATE:
|
||||||
|
return _t('Migrate from Key Backup');
|
||||||
case PHASE_PASSPHRASE:
|
case PHASE_PASSPHRASE:
|
||||||
return _t('Secure your encrypted messages with a passphrase');
|
return _t('Secure your encrypted messages with a passphrase');
|
||||||
case PHASE_PASSPHRASE_CONFIRM:
|
case PHASE_PASSPHRASE_CONFIRM:
|
||||||
|
@ -525,6 +566,12 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
</div>;
|
</div>;
|
||||||
} else {
|
} else {
|
||||||
switch (this.state.phase) {
|
switch (this.state.phase) {
|
||||||
|
case PHASE_LOADING:
|
||||||
|
content = this._renderBusyPhase();
|
||||||
|
break;
|
||||||
|
case PHASE_MIGRATE:
|
||||||
|
content = this._renderPhaseMigrate();
|
||||||
|
break;
|
||||||
case PHASE_PASSPHRASE:
|
case PHASE_PASSPHRASE:
|
||||||
content = this._renderPhasePassPhrase();
|
content = this._renderPhasePassPhrase();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
Copyright 2016 Aviral Dasgupta
|
Copyright 2016 Aviral Dasgupta
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
Copyright 2017, 2018 New Vector 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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -28,7 +29,7 @@ import SettingsStore from "../settings/SettingsStore";
|
||||||
import { shortcodeToUnicode } from '../HtmlUtils';
|
import { shortcodeToUnicode } from '../HtmlUtils';
|
||||||
|
|
||||||
import EMOTICON_REGEX from 'emojibase-regex/emoticon';
|
import EMOTICON_REGEX from 'emojibase-regex/emoticon';
|
||||||
import EmojiData from '../stripped-emoji.json';
|
import EMOJIBASE from 'emojibase-data/en/compact.json';
|
||||||
|
|
||||||
const LIMIT = 20;
|
const LIMIT = 20;
|
||||||
|
|
||||||
|
@ -38,19 +39,15 @@ const EMOJI_REGEX = new RegExp('(' + EMOTICON_REGEX.source + '|:[+-\\w]*:?)$', '
|
||||||
// XXX: it's very unclear why we bother with this generated emojidata file.
|
// XXX: it's very unclear why we bother with this generated emojidata file.
|
||||||
// all it means is that we end up bloating the bundle with precomputed stuff
|
// all it means is that we end up bloating the bundle with precomputed stuff
|
||||||
// which would be trivial to calculate and cache on demand.
|
// which would be trivial to calculate and cache on demand.
|
||||||
const EMOJI_SHORTNAMES = Object.keys(EmojiData).map((key) => EmojiData[key]).sort(
|
const EMOJI_SHORTNAMES = EMOJIBASE.sort((a, b) => {
|
||||||
(a, b) => {
|
if (a.group === b.group) {
|
||||||
if (a.category === b.category) {
|
return a.order - b.order;
|
||||||
return a.emoji_order - b.emoji_order;
|
}
|
||||||
}
|
return a.group - b.group;
|
||||||
return a.category - b.category;
|
}).map((emoji, index) => {
|
||||||
},
|
|
||||||
).map((a, index) => {
|
|
||||||
return {
|
return {
|
||||||
name: a.name,
|
emoji,
|
||||||
shortname: a.shortname,
|
shortname: `:${emoji.shortcodes[0]}:`,
|
||||||
aliases: a.aliases ? a.aliases.join(' ') : '',
|
|
||||||
aliases_ascii: a.aliases_ascii ? a.aliases_ascii.join(' ') : '',
|
|
||||||
// Include the index so that we can preserve the original order
|
// Include the index so that we can preserve the original order
|
||||||
_orderBy: index,
|
_orderBy: index,
|
||||||
};
|
};
|
||||||
|
@ -69,12 +66,15 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(EMOJI_REGEX);
|
super(EMOJI_REGEX);
|
||||||
this.matcher = new QueryMatcher(EMOJI_SHORTNAMES, {
|
this.matcher = new QueryMatcher(EMOJI_SHORTNAMES, {
|
||||||
keys: ['aliases_ascii', 'shortname', 'aliases'],
|
keys: ['emoji.emoticon', 'shortname'],
|
||||||
|
funcs: [
|
||||||
|
(o) => o.emoji.shortcodes.length > 1 ? o.emoji.shortcodes.slice(1).map(s => `:${s}:`).join(" ") : "", // aliases
|
||||||
|
],
|
||||||
// For matching against ascii equivalents
|
// For matching against ascii equivalents
|
||||||
shouldMatchWordsOnly: false,
|
shouldMatchWordsOnly: false,
|
||||||
});
|
});
|
||||||
this.nameMatcher = new QueryMatcher(EMOJI_SHORTNAMES, {
|
this.nameMatcher = new QueryMatcher(EMOJI_SHORTNAMES, {
|
||||||
keys: ['name'],
|
keys: ['emoji.annotation'],
|
||||||
// For removing punctuation
|
// For removing punctuation
|
||||||
shouldMatchWordsOnly: true,
|
shouldMatchWordsOnly: true,
|
||||||
});
|
});
|
||||||
|
@ -96,7 +96,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
|
|
||||||
const sorters = [];
|
const sorters = [];
|
||||||
// make sure that emoticons come first
|
// make sure that emoticons come first
|
||||||
sorters.push((c) => score(matchedString, c.aliases_ascii));
|
sorters.push((c) => score(matchedString, c.emoji.emoticon || ""));
|
||||||
|
|
||||||
// then sort by score (Infinity if matchedString not in shortname)
|
// then sort by score (Infinity if matchedString not in shortname)
|
||||||
sorters.push((c) => score(matchedString, c.shortname));
|
sorters.push((c) => score(matchedString, c.shortname));
|
||||||
|
@ -110,8 +110,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
sorters.push((c) => c._orderBy);
|
sorters.push((c) => c._orderBy);
|
||||||
completions = _sortBy(_uniq(completions), sorters);
|
completions = _sortBy(_uniq(completions), sorters);
|
||||||
|
|
||||||
completions = completions.map((result) => {
|
completions = completions.map(({shortname}) => {
|
||||||
const { shortname } = result;
|
|
||||||
const unicode = shortcodeToUnicode(shortname);
|
const unicode = shortcodeToUnicode(shortname);
|
||||||
return {
|
return {
|
||||||
completion: unicode,
|
completion: unicode,
|
||||||
|
|
|
@ -71,6 +71,7 @@ export default class QueryMatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const keyValue of keyValues) {
|
for (const keyValue of keyValues) {
|
||||||
|
if (!keyValue) continue; // skip falsy keyValues
|
||||||
const key = stripDiacritics(keyValue).toLowerCase();
|
const key = stripDiacritics(keyValue).toLowerCase();
|
||||||
if (!this._items.has(key)) {
|
if (!this._items.has(key)) {
|
||||||
this._items.set(key, []);
|
this._items.set(key, []);
|
||||||
|
|
|
@ -71,12 +71,12 @@ export class ContextMenu extends React.Component {
|
||||||
// on resize callback
|
// on resize callback
|
||||||
windowResize: PropTypes.func,
|
windowResize: PropTypes.func,
|
||||||
|
|
||||||
catchTab: PropTypes.bool, // whether to close the ContextMenu on TAB (default=true)
|
managed: PropTypes.bool, // whether this context menu should be focus managed. If false it must handle itself
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
hasBackground: true,
|
hasBackground: true,
|
||||||
catchTab: true,
|
managed: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -186,15 +186,19 @@ export class ContextMenu extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
_onKeyDown = (ev) => {
|
_onKeyDown = (ev) => {
|
||||||
|
if (!this.props.managed) {
|
||||||
|
if (ev.key === Key.ESCAPE) {
|
||||||
|
this.props.onFinished();
|
||||||
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let handled = true;
|
let handled = true;
|
||||||
|
|
||||||
switch (ev.key) {
|
switch (ev.key) {
|
||||||
case Key.TAB:
|
case Key.TAB:
|
||||||
if (!this.props.catchTab) {
|
|
||||||
handled = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// fallthrough
|
|
||||||
case Key.ESCAPE:
|
case Key.ESCAPE:
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
break;
|
break;
|
||||||
|
@ -321,7 +325,7 @@ export class ContextMenu extends React.Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_ContextualMenu_wrapper" style={{...position, ...wrapperStyle}} onKeyDown={this._onKeyDown}>
|
<div className="mx_ContextualMenu_wrapper" style={{...position, ...wrapperStyle}} onKeyDown={this._onKeyDown}>
|
||||||
<div className={menuClasses} style={menuStyle} ref={this.collectContextMenuRect} role="menu">
|
<div className={menuClasses} style={menuStyle} ref={this.collectContextMenuRect} role={this.props.managed ? "menu" : undefined}>
|
||||||
{ chevron }
|
{ chevron }
|
||||||
{ props.children }
|
{ props.children }
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -61,30 +61,13 @@ class CustomRoomTagPanel extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
class CustomRoomTagTile extends React.Component {
|
class CustomRoomTagTile extends React.Component {
|
||||||
constructor(props) {
|
onClick = () => {
|
||||||
super(props);
|
|
||||||
this.state = {hover: false};
|
|
||||||
this.onClick = this.onClick.bind(this);
|
|
||||||
this.onMouseOut = this.onMouseOut.bind(this);
|
|
||||||
this.onMouseOver = this.onMouseOver.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMouseOver() {
|
|
||||||
this.setState({hover: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
onMouseOut() {
|
|
||||||
this.setState({hover: false});
|
|
||||||
}
|
|
||||||
|
|
||||||
onClick() {
|
|
||||||
dis.dispatch({action: 'select_custom_room_tag', tag: this.props.tag.name});
|
dis.dispatch({action: 'select_custom_room_tag', tag: this.props.tag.name});
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
const AccessibleTooltipButton = sdk.getComponent('elements.AccessibleTooltipButton');
|
||||||
const Tooltip = sdk.getComponent('elements.Tooltip');
|
|
||||||
|
|
||||||
const tag = this.props.tag;
|
const tag = this.props.tag;
|
||||||
const avatarHeight = 40;
|
const avatarHeight = 40;
|
||||||
|
@ -102,12 +85,9 @@ class CustomRoomTagTile extends React.Component {
|
||||||
badgeElement = (<div className={badgeClasses}>{FormattingUtils.formatCount(badge.count)}</div>);
|
badgeElement = (<div className={badgeClasses}>{FormattingUtils.formatCount(badge.count)}</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tip = (this.state.hover ?
|
|
||||||
<Tooltip className="mx_TagTile_tooltip" label={name} /> :
|
|
||||||
<div />);
|
|
||||||
return (
|
return (
|
||||||
<AccessibleButton className={className} onClick={this.onClick}>
|
<AccessibleTooltipButton className={className} onClick={this.onClick} title={name}>
|
||||||
<div className="mx_TagTile_avatar" onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut}>
|
<div className="mx_TagTile_avatar">
|
||||||
<BaseAvatar
|
<BaseAvatar
|
||||||
name={tag.avatarLetter}
|
name={tag.avatarLetter}
|
||||||
idName={name}
|
idName={name}
|
||||||
|
@ -115,9 +95,8 @@ class CustomRoomTagTile extends React.Component {
|
||||||
height={avatarHeight}
|
height={avatarHeight}
|
||||||
/>
|
/>
|
||||||
{ badgeElement }
|
{ badgeElement }
|
||||||
{ tip }
|
|
||||||
</div>
|
</div>
|
||||||
</AccessibleButton>
|
</AccessibleTooltipButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,8 @@ import sanitizeHtml from 'sanitize-html';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher';
|
||||||
import MatrixClientPeg from '../../MatrixClientPeg';
|
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
export default class EmbeddedPage extends React.PureComponent {
|
export default class EmbeddedPage extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -39,9 +39,7 @@ export default class EmbeddedPage extends React.PureComponent {
|
||||||
scrollbar: PropTypes.bool,
|
scrollbar: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
static contextTypes = {
|
static contextType = MatrixClientContext;
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -104,7 +102,7 @@ export default class EmbeddedPage extends React.PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
// HACK: Workaround for the context's MatrixClient not updating.
|
// HACK: Workaround for the context's MatrixClient not updating.
|
||||||
const client = this.context.matrixClient || MatrixClientPeg.get();
|
const client = this.context || MatrixClientPeg.get();
|
||||||
const isGuest = client ? client.isGuest() : true;
|
const isGuest = client ? client.isGuest() : true;
|
||||||
const className = this.props.className;
|
const className = this.props.className;
|
||||||
const classes = classnames({
|
const classes = classnames({
|
||||||
|
|
|
@ -19,12 +19,10 @@ import React from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
|
||||||
import { Key } from '../../Keyboard';
|
import { Key } from '../../Keyboard';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher';
|
||||||
import VectorConferenceHandler from '../../VectorConferenceHandler';
|
import VectorConferenceHandler from '../../VectorConferenceHandler';
|
||||||
import TagPanelButtons from './TagPanelButtons';
|
|
||||||
import SettingsStore from '../../settings/SettingsStore';
|
import SettingsStore from '../../settings/SettingsStore';
|
||||||
import {_t} from "../../languageHandler";
|
import {_t} from "../../languageHandler";
|
||||||
import Analytics from "../../Analytics";
|
import Analytics from "../../Analytics";
|
||||||
|
@ -39,10 +37,6 @@ const LeftPanel = createReactClass({
|
||||||
collapsed: PropTypes.bool.isRequired,
|
collapsed: PropTypes.bool.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
contextTypes: {
|
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
searchFilter: '',
|
searchFilter: '',
|
||||||
|
@ -243,7 +237,6 @@ const LeftPanel = createReactClass({
|
||||||
tagPanelContainer = (<div className="mx_LeftPanel_tagPanelContainer">
|
tagPanelContainer = (<div className="mx_LeftPanel_tagPanelContainer">
|
||||||
<TagPanel />
|
<TagPanel />
|
||||||
{ isCustomTagsEnabled ? <CustomRoomTagPanel /> : undefined }
|
{ isCustomTagsEnabled ? <CustomRoomTagPanel /> : undefined }
|
||||||
<TagPanelButtons />
|
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ import TagOrderActions from '../../actions/TagOrderActions';
|
||||||
import RoomListActions from '../../actions/RoomListActions';
|
import RoomListActions from '../../actions/RoomListActions';
|
||||||
import ResizeHandle from '../views/elements/ResizeHandle';
|
import ResizeHandle from '../views/elements/ResizeHandle';
|
||||||
import {Resizer, CollapseDistributor} from '../../resizer';
|
import {Resizer, CollapseDistributor} from '../../resizer';
|
||||||
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
// We need to fetch each pinned message individually (if we don't already have it)
|
// We need to fetch each pinned message individually (if we don't already have it)
|
||||||
// so each pinned message may trigger a request. Limit the number per room for sanity.
|
// so each pinned message may trigger a request. Limit the number per room for sanity.
|
||||||
// NB. this is just for server notices rather than pinned messages in general.
|
// NB. this is just for server notices rather than pinned messages in general.
|
||||||
|
@ -77,21 +78,6 @@ const LoggedInView = createReactClass({
|
||||||
// and lots and lots of other stuff.
|
// and lots and lots of other stuff.
|
||||||
},
|
},
|
||||||
|
|
||||||
childContextTypes: {
|
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
|
||||||
authCache: PropTypes.object,
|
|
||||||
},
|
|
||||||
|
|
||||||
getChildContext: function() {
|
|
||||||
return {
|
|
||||||
matrixClient: this._matrixClient,
|
|
||||||
authCache: {
|
|
||||||
auth: {},
|
|
||||||
lastUpdate: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
// use compact timeline view
|
// use compact timeline view
|
||||||
|
@ -631,21 +617,30 @@ const LoggedInView = createReactClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onPaste={this._onPaste} onKeyDown={this._onReactKeyDown} className='mx_MatrixChat_wrapper' aria-hidden={this.props.hideToSRUsers} onMouseDown={this._onMouseDown} onMouseUp={this._onMouseUp}>
|
<MatrixClientContext.Provider value={this._matrixClient}>
|
||||||
{ topBar }
|
<div
|
||||||
<ToastContainer />
|
onPaste={this._onPaste}
|
||||||
<DragDropContext onDragEnd={this._onDragEnd}>
|
onKeyDown={this._onReactKeyDown}
|
||||||
<div ref={this._setResizeContainerRef} className={bodyClasses}>
|
className='mx_MatrixChat_wrapper'
|
||||||
<LeftPanel
|
aria-hidden={this.props.hideToSRUsers}
|
||||||
resizeNotifier={this.props.resizeNotifier}
|
onMouseDown={this._onMouseDown}
|
||||||
collapsed={this.props.collapseLhs || false}
|
onMouseUp={this._onMouseUp}
|
||||||
disabled={this.props.leftDisabled}
|
>
|
||||||
/>
|
{ topBar }
|
||||||
<ResizeHandle />
|
<ToastContainer />
|
||||||
{ pageElement }
|
<DragDropContext onDragEnd={this._onDragEnd}>
|
||||||
</div>
|
<div ref={this._setResizeContainerRef} className={bodyClasses}>
|
||||||
</DragDropContext>
|
<LeftPanel
|
||||||
</div>
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
|
collapsed={this.props.collapseLhs || false}
|
||||||
|
disabled={this.props.leftDisabled}
|
||||||
|
/>
|
||||||
|
<ResizeHandle />
|
||||||
|
{ pageElement }
|
||||||
|
</div>
|
||||||
|
</DragDropContext>
|
||||||
|
</div>
|
||||||
|
</MatrixClientContext.Provider>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -74,6 +74,21 @@ export default class MainSplit extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
const wasPanelSet = this.props.panel && !prevProps.panel;
|
||||||
|
const wasPanelCleared = !this.props.panel && prevProps.panel;
|
||||||
|
|
||||||
|
if (this.resizeContainer && wasPanelSet) {
|
||||||
|
// The resizer can only be created when **both** expanded and the panel is
|
||||||
|
// set. Once both are true, the container ref will mount, which is required
|
||||||
|
// for the resizer to work.
|
||||||
|
this._createResizer();
|
||||||
|
} else if (this.resizer && wasPanelCleared) {
|
||||||
|
this.resizer.detach();
|
||||||
|
this.resizer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const bodyView = React.Children.only(this.props.children);
|
const bodyView = React.Children.only(this.props.children);
|
||||||
const panelView = this.props.panel;
|
const panelView = this.props.panel;
|
||||||
|
|
|
@ -150,16 +150,6 @@ export default createReactClass({
|
||||||
makeRegistrationUrl: PropTypes.func.isRequired,
|
makeRegistrationUrl: PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
childContextTypes: {
|
|
||||||
appConfig: PropTypes.object,
|
|
||||||
},
|
|
||||||
|
|
||||||
getChildContext: function() {
|
|
||||||
return {
|
|
||||||
appConfig: this.props.config,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
const s = {
|
const s = {
|
||||||
// the master view we are showing.
|
// the master view we are showing.
|
||||||
|
@ -1466,7 +1456,7 @@ export default createReactClass({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (SettingsStore.isFeatureEnabled("feature_dm_verification")) {
|
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||||
cli.on("crypto.verification.request", request => {
|
cli.on("crypto.verification.request", request => {
|
||||||
let requestObserver;
|
let requestObserver;
|
||||||
if (request.event.getRoomId()) {
|
if (request.event.getRoomId()) {
|
||||||
|
@ -1579,9 +1569,17 @@ export default createReactClass({
|
||||||
action: 'start_post_registration',
|
action: 'start_post_registration',
|
||||||
});
|
});
|
||||||
} else if (screen.indexOf('room/') == 0) {
|
} else if (screen.indexOf('room/') == 0) {
|
||||||
const segments = screen.substring(5).split('/');
|
// Rooms can have the following formats:
|
||||||
const roomString = segments[0];
|
// #room_alias:domain or !opaque_id:domain
|
||||||
let eventId = segments.splice(1).join("/"); // empty string if no event id given
|
const room = screen.substring(5);
|
||||||
|
const domainOffset = room.indexOf(':') + 1; // 0 in case room does not contain a :
|
||||||
|
let eventOffset = room.length;
|
||||||
|
// room aliases can contain slashes only look for slash after domain
|
||||||
|
if (room.substring(domainOffset).indexOf('/') > -1) {
|
||||||
|
eventOffset = domainOffset + room.substring(domainOffset).indexOf('/');
|
||||||
|
}
|
||||||
|
const roomString = room.substring(0, eventOffset);
|
||||||
|
let eventId = room.substring(eventOffset + 1); // empty string if no event id given
|
||||||
|
|
||||||
// Previously we pulled the eventID from the segments in such a way
|
// Previously we pulled the eventID from the segments in such a way
|
||||||
// where if there was no eventId then we'd get undefined. However, we
|
// where if there was no eventId then we'd get undefined. However, we
|
||||||
|
|
|
@ -17,12 +17,11 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher';
|
||||||
import AccessibleButton from '../views/elements/AccessibleButton';
|
import AccessibleButton from '../views/elements/AccessibleButton';
|
||||||
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
export default createReactClass({
|
export default createReactClass({
|
||||||
displayName: 'MyGroups',
|
displayName: 'MyGroups',
|
||||||
|
@ -34,8 +33,8 @@ export default createReactClass({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
contextTypes: {
|
statics: {
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
contextType: MatrixClientContext,
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
|
@ -47,7 +46,7 @@ export default createReactClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_fetch: function() {
|
_fetch: function() {
|
||||||
this.context.matrixClient.getJoinedGroups().then((result) => {
|
this.context.getJoinedGroups().then((result) => {
|
||||||
this.setState({groups: result.groups, error: null});
|
this.setState({groups: result.groups, error: null});
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
if (err.errcode === 'M_GUEST_ACCESS_FORBIDDEN') {
|
if (err.errcode === 'M_GUEST_ACCESS_FORBIDDEN') {
|
||||||
|
|
|
@ -23,13 +23,13 @@ import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher';
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
|
||||||
import RateLimitedFunc from '../../ratelimitedfunc';
|
import RateLimitedFunc from '../../ratelimitedfunc';
|
||||||
import { showGroupInviteDialog, showGroupAddRoomDialog } from '../../GroupAddressPicker';
|
import { showGroupInviteDialog, showGroupAddRoomDialog } from '../../GroupAddressPicker';
|
||||||
import GroupStore from '../../stores/GroupStore';
|
import GroupStore from '../../stores/GroupStore';
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
import {RIGHT_PANEL_PHASES, RIGHT_PANEL_PHASES_NO_ARGS} from "../../stores/RightPanelStorePhases";
|
import {RIGHT_PANEL_PHASES, RIGHT_PANEL_PHASES_NO_ARGS} from "../../stores/RightPanelStorePhases";
|
||||||
import RightPanelStore from "../../stores/RightPanelStore";
|
import RightPanelStore from "../../stores/RightPanelStore";
|
||||||
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
export default class RightPanel extends React.Component {
|
export default class RightPanel extends React.Component {
|
||||||
static get propTypes() {
|
static get propTypes() {
|
||||||
|
@ -40,14 +40,10 @@ export default class RightPanel extends React.Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static get contextTypes() {
|
static contextType = MatrixClientContext;
|
||||||
return {
|
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
phase: this._getPhaseFromProps(),
|
phase: this._getPhaseFromProps(),
|
||||||
isUserPrivilegedInGroup: null,
|
isUserPrivilegedInGroup: null,
|
||||||
|
@ -93,15 +89,15 @@ export default class RightPanel extends React.Component {
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
const cli = this.context.matrixClient;
|
const cli = this.context;
|
||||||
cli.on("RoomState.members", this.onRoomStateMember);
|
cli.on("RoomState.members", this.onRoomStateMember);
|
||||||
this._initGroupStore(this.props.groupId);
|
this._initGroupStore(this.props.groupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
if (this.context.matrixClient) {
|
if (this.context) {
|
||||||
this.context.matrixClient.removeListener("RoomState.members", this.onRoomStateMember);
|
this.context.removeListener("RoomState.members", this.onRoomStateMember);
|
||||||
}
|
}
|
||||||
this._unregisterGroupStore(this.props.groupId);
|
this._unregisterGroupStore(this.props.groupId);
|
||||||
}
|
}
|
||||||
|
@ -190,7 +186,7 @@ export default class RightPanel extends React.Component {
|
||||||
} else if (this.state.phase === RIGHT_PANEL_PHASES.GroupRoomList) {
|
} else if (this.state.phase === RIGHT_PANEL_PHASES.GroupRoomList) {
|
||||||
panel = <GroupRoomList groupId={this.props.groupId} key={this.props.groupId} />;
|
panel = <GroupRoomList groupId={this.props.groupId} key={this.props.groupId} />;
|
||||||
} else if (this.state.phase === RIGHT_PANEL_PHASES.RoomMemberInfo) {
|
} else if (this.state.phase === RIGHT_PANEL_PHASES.RoomMemberInfo) {
|
||||||
if (SettingsStore.isFeatureEnabled("feature_dm_verification")) {
|
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "view_user",
|
action: "view_user",
|
||||||
|
@ -209,7 +205,7 @@ export default class RightPanel extends React.Component {
|
||||||
} else if (this.state.phase === RIGHT_PANEL_PHASES.Room3pidMemberInfo) {
|
} else if (this.state.phase === RIGHT_PANEL_PHASES.Room3pidMemberInfo) {
|
||||||
panel = <ThirdPartyMemberInfo event={this.state.event} key={this.props.roomId} />;
|
panel = <ThirdPartyMemberInfo event={this.state.event} key={this.props.roomId} />;
|
||||||
} else if (this.state.phase === RIGHT_PANEL_PHASES.GroupMemberInfo) {
|
} else if (this.state.phase === RIGHT_PANEL_PHASES.GroupMemberInfo) {
|
||||||
if (SettingsStore.isFeatureEnabled("feature_dm_verification")) {
|
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "view_user",
|
action: "view_user",
|
||||||
|
|
|
@ -30,6 +30,7 @@ import PropTypes from 'prop-types';
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils';
|
import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils';
|
||||||
import Analytics from '../../Analytics';
|
import Analytics from '../../Analytics';
|
||||||
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
const MAX_NAME_LENGTH = 80;
|
const MAX_NAME_LENGTH = 80;
|
||||||
const MAX_TOPIC_LENGTH = 160;
|
const MAX_TOPIC_LENGTH = 160;
|
||||||
|
@ -65,16 +66,6 @@ module.exports = createReactClass({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
childContextTypes: {
|
|
||||||
matrixClient: PropTypes.object,
|
|
||||||
},
|
|
||||||
|
|
||||||
getChildContext: function() {
|
|
||||||
return {
|
|
||||||
matrixClient: MatrixClientPeg.get(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
this._unmounted = false;
|
this._unmounted = false;
|
||||||
this.nextBatch = null;
|
this.nextBatch = null;
|
||||||
|
|
|
@ -25,7 +25,7 @@ import MatrixClientPeg from '../../MatrixClientPeg';
|
||||||
import Resend from '../../Resend';
|
import Resend from '../../Resend';
|
||||||
import * as cryptodevices from '../../cryptodevices';
|
import * as cryptodevices from '../../cryptodevices';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher';
|
||||||
import { messageForResourceLimitError } from '../../utils/ErrorUtils';
|
import {messageForResourceLimitError, messageForSendError} from '../../utils/ErrorUtils';
|
||||||
|
|
||||||
const STATUS_BAR_HIDDEN = 0;
|
const STATUS_BAR_HIDDEN = 0;
|
||||||
const STATUS_BAR_EXPANDED = 1;
|
const STATUS_BAR_EXPANDED = 1;
|
||||||
|
@ -272,7 +272,7 @@ module.exports = createReactClass({
|
||||||
unsentMessages[0].error.data &&
|
unsentMessages[0].error.data &&
|
||||||
unsentMessages[0].error.data.error
|
unsentMessages[0].error.data.error
|
||||||
) {
|
) {
|
||||||
title = unsentMessages[0].error.data.error;
|
title = messageForSendError(unsentMessages[0].error.data) || unsentMessages[0].error.data.error;
|
||||||
} else {
|
} else {
|
||||||
title = _t('%(count)s of your messages have not been sent.', { count: unsentMessages.length });
|
title = _t('%(count)s of your messages have not been sent.', { count: unsentMessages.length });
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,10 +25,8 @@ import shouldHideEvent from '../../shouldHideEvent';
|
||||||
|
|
||||||
import React, {createRef} from 'react';
|
import React, {createRef} from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {Room} from "matrix-js-sdk";
|
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import {RoomPermalinkCreator} from '../../utils/permalinks/Permalinks';
|
import {RoomPermalinkCreator} from '../../utils/permalinks/Permalinks';
|
||||||
|
|
||||||
|
@ -55,6 +53,7 @@ import SettingsStore, {SettingLevel} from "../../settings/SettingsStore";
|
||||||
import WidgetUtils from '../../utils/WidgetUtils';
|
import WidgetUtils from '../../utils/WidgetUtils';
|
||||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||||
import RightPanelStore from "../../stores/RightPanelStore";
|
import RightPanelStore from "../../stores/RightPanelStore";
|
||||||
|
import RoomContext from "../../contexts/RoomContext";
|
||||||
|
|
||||||
const DEBUG = false;
|
const DEBUG = false;
|
||||||
let debuglog = function() {};
|
let debuglog = function() {};
|
||||||
|
@ -66,12 +65,6 @@ if (DEBUG) {
|
||||||
debuglog = console.log.bind(console);
|
debuglog = console.log.bind(console);
|
||||||
}
|
}
|
||||||
|
|
||||||
const RoomContext = PropTypes.shape({
|
|
||||||
canReact: PropTypes.bool.isRequired,
|
|
||||||
canReply: PropTypes.bool.isRequired,
|
|
||||||
room: PropTypes.instanceOf(Room),
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = createReactClass({
|
module.exports = createReactClass({
|
||||||
displayName: 'RoomView',
|
displayName: 'RoomView',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
|
@ -169,21 +162,6 @@ module.exports = createReactClass({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
childContextTypes: {
|
|
||||||
room: RoomContext,
|
|
||||||
},
|
|
||||||
|
|
||||||
getChildContext: function() {
|
|
||||||
const {canReact, canReply, room} = this.state;
|
|
||||||
return {
|
|
||||||
room: {
|
|
||||||
canReact,
|
|
||||||
canReply,
|
|
||||||
room,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
MatrixClientPeg.get().on("Room", this.onRoom);
|
MatrixClientPeg.get().on("Room", this.onRoom);
|
||||||
|
@ -461,7 +439,7 @@ module.exports = createReactClass({
|
||||||
|
|
||||||
componentDidUpdate: function() {
|
componentDidUpdate: function() {
|
||||||
if (this._roomView.current) {
|
if (this._roomView.current) {
|
||||||
const roomView = ReactDOM.findDOMNode(this._roomView.current);
|
const roomView = this._roomView.current;
|
||||||
if (!roomView.ondrop) {
|
if (!roomView.ondrop) {
|
||||||
roomView.addEventListener('drop', this.onDrop);
|
roomView.addEventListener('drop', this.onDrop);
|
||||||
roomView.addEventListener('dragover', this.onDragOver);
|
roomView.addEventListener('dragover', this.onDragOver);
|
||||||
|
@ -505,7 +483,7 @@ module.exports = createReactClass({
|
||||||
// is really just for hygiene - we're going to be
|
// is really just for hygiene - we're going to be
|
||||||
// deleted anyway, so it doesn't matter if the event listeners
|
// deleted anyway, so it doesn't matter if the event listeners
|
||||||
// don't get cleaned up.
|
// don't get cleaned up.
|
||||||
const roomView = ReactDOM.findDOMNode(this._roomView.current);
|
const roomView = this._roomView.current;
|
||||||
roomView.removeEventListener('drop', this.onDrop);
|
roomView.removeEventListener('drop', this.onDrop);
|
||||||
roomView.removeEventListener('dragover', this.onDragOver);
|
roomView.removeEventListener('dragover', this.onDragOver);
|
||||||
roomView.removeEventListener('dragleave', this.onDragLeaveOrEnd);
|
roomView.removeEventListener('dragleave', this.onDragLeaveOrEnd);
|
||||||
|
@ -1953,7 +1931,8 @@ module.exports = createReactClass({
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
let topUnreadMessagesBar = null;
|
let topUnreadMessagesBar = null;
|
||||||
if (this.state.showTopUnreadMessagesBar) {
|
// Do not show TopUnreadMessagesBar if we have search results showing, it makes no sense
|
||||||
|
if (this.state.showTopUnreadMessagesBar && !this.state.searchResults) {
|
||||||
const TopUnreadMessagesBar = sdk.getComponent('rooms.TopUnreadMessagesBar');
|
const TopUnreadMessagesBar = sdk.getComponent('rooms.TopUnreadMessagesBar');
|
||||||
topUnreadMessagesBar = (<TopUnreadMessagesBar
|
topUnreadMessagesBar = (<TopUnreadMessagesBar
|
||||||
onScrollUpClick={this.jumpToReadMarker}
|
onScrollUpClick={this.jumpToReadMarker}
|
||||||
|
@ -1961,7 +1940,8 @@ module.exports = createReactClass({
|
||||||
/>);
|
/>);
|
||||||
}
|
}
|
||||||
let jumpToBottom;
|
let jumpToBottom;
|
||||||
if (!this.state.atEndOfLiveTimeline) {
|
// Do not show JumpToBottomButton if we have search results showing, it makes no sense
|
||||||
|
if (!this.state.atEndOfLiveTimeline && !this.state.searchResults) {
|
||||||
const JumpToBottomButton = sdk.getComponent('rooms.JumpToBottomButton');
|
const JumpToBottomButton = sdk.getComponent('rooms.JumpToBottomButton');
|
||||||
jumpToBottom = (<JumpToBottomButton
|
jumpToBottom = (<JumpToBottomButton
|
||||||
numUnreadMessages={this.state.numUnreadMessages}
|
numUnreadMessages={this.state.numUnreadMessages}
|
||||||
|
@ -1989,45 +1969,47 @@ module.exports = createReactClass({
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className={"mx_RoomView" + (inCall ? " mx_RoomView_inCall" : "")} ref={this._roomView}>
|
<RoomContext.Provider value={this.state}>
|
||||||
<ErrorBoundary>
|
<main className={"mx_RoomView" + (inCall ? " mx_RoomView_inCall" : "")} ref={this._roomView}>
|
||||||
<RoomHeader
|
<ErrorBoundary>
|
||||||
room={this.state.room}
|
<RoomHeader
|
||||||
searchInfo={searchInfo}
|
room={this.state.room}
|
||||||
oobData={this.props.oobData}
|
searchInfo={searchInfo}
|
||||||
inRoom={myMembership === 'join'}
|
oobData={this.props.oobData}
|
||||||
onSearchClick={this.onSearchClick}
|
inRoom={myMembership === 'join'}
|
||||||
onSettingsClick={this.onSettingsClick}
|
onSearchClick={this.onSearchClick}
|
||||||
onPinnedClick={this.onPinnedClick}
|
onSettingsClick={this.onSettingsClick}
|
||||||
onCancelClick={(aux && !hideCancel) ? this.onCancelClick : null}
|
onPinnedClick={this.onPinnedClick}
|
||||||
onForgetClick={(myMembership === "leave") ? this.onForgetClick : null}
|
onCancelClick={(aux && !hideCancel) ? this.onCancelClick : null}
|
||||||
onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null}
|
onForgetClick={(myMembership === "leave") ? this.onForgetClick : null}
|
||||||
e2eStatus={this.state.e2eStatus}
|
onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null}
|
||||||
/>
|
e2eStatus={this.state.e2eStatus}
|
||||||
<MainSplit
|
/>
|
||||||
panel={rightPanel}
|
<MainSplit
|
||||||
resizeNotifier={this.props.resizeNotifier}
|
panel={rightPanel}
|
||||||
>
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
<div className={fadableSectionClasses}>
|
>
|
||||||
{auxPanel}
|
<div className={fadableSectionClasses}>
|
||||||
<div className="mx_RoomView_timeline">
|
{auxPanel}
|
||||||
{topUnreadMessagesBar}
|
<div className="mx_RoomView_timeline">
|
||||||
{jumpToBottom}
|
{topUnreadMessagesBar}
|
||||||
{messagePanel}
|
{jumpToBottom}
|
||||||
{searchResultsPanel}
|
{messagePanel}
|
||||||
</div>
|
{searchResultsPanel}
|
||||||
<div className={statusBarAreaClass}>
|
|
||||||
<div className="mx_RoomView_statusAreaBox">
|
|
||||||
<div className="mx_RoomView_statusAreaBox_line"></div>
|
|
||||||
{statusBar}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className={statusBarAreaClass}>
|
||||||
|
<div className="mx_RoomView_statusAreaBox">
|
||||||
|
<div className="mx_RoomView_statusAreaBox_line" />
|
||||||
|
{statusBar}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{previewBar}
|
||||||
|
{messageComposer}
|
||||||
</div>
|
</div>
|
||||||
{previewBar}
|
</MainSplit>
|
||||||
{messageComposer}
|
</ErrorBoundary>
|
||||||
</div>
|
</main>
|
||||||
</MainSplit>
|
</RoomContext.Provider>
|
||||||
</ErrorBoundary>
|
|
||||||
</main>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,8 +16,6 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
|
||||||
import TagOrderStore from '../../stores/TagOrderStore';
|
import TagOrderStore from '../../stores/TagOrderStore';
|
||||||
|
|
||||||
import GroupActions from '../../actions/GroupActions';
|
import GroupActions from '../../actions/GroupActions';
|
||||||
|
@ -28,12 +26,13 @@ import { _t } from '../../languageHandler';
|
||||||
|
|
||||||
import { Droppable } from 'react-beautiful-dnd';
|
import { Droppable } from 'react-beautiful-dnd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
const TagPanel = createReactClass({
|
const TagPanel = createReactClass({
|
||||||
displayName: 'TagPanel',
|
displayName: 'TagPanel',
|
||||||
|
|
||||||
contextTypes: {
|
statics: {
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
contextType: MatrixClientContext,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
|
@ -45,8 +44,8 @@ const TagPanel = createReactClass({
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
this.unmounted = false;
|
this.unmounted = false;
|
||||||
this.context.matrixClient.on("Group.myMembership", this._onGroupMyMembership);
|
this.context.on("Group.myMembership", this._onGroupMyMembership);
|
||||||
this.context.matrixClient.on("sync", this._onClientSync);
|
this.context.on("sync", this._onClientSync);
|
||||||
|
|
||||||
this._tagOrderStoreToken = TagOrderStore.addListener(() => {
|
this._tagOrderStoreToken = TagOrderStore.addListener(() => {
|
||||||
if (this.unmounted) {
|
if (this.unmounted) {
|
||||||
|
@ -58,13 +57,13 @@ const TagPanel = createReactClass({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// This could be done by anything with a matrix client
|
// This could be done by anything with a matrix client
|
||||||
dis.dispatch(GroupActions.fetchJoinedGroups(this.context.matrixClient));
|
dis.dispatch(GroupActions.fetchJoinedGroups(this.context));
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.unmounted = true;
|
this.unmounted = true;
|
||||||
this.context.matrixClient.removeListener("Group.myMembership", this._onGroupMyMembership);
|
this.context.removeListener("Group.myMembership", this._onGroupMyMembership);
|
||||||
this.context.matrixClient.removeListener("sync", this._onClientSync);
|
this.context.removeListener("sync", this._onClientSync);
|
||||||
if (this._filterStoreToken) {
|
if (this._filterStoreToken) {
|
||||||
this._filterStoreToken.remove();
|
this._filterStoreToken.remove();
|
||||||
}
|
}
|
||||||
|
@ -72,7 +71,7 @@ const TagPanel = createReactClass({
|
||||||
|
|
||||||
_onGroupMyMembership() {
|
_onGroupMyMembership() {
|
||||||
if (this.unmounted) return;
|
if (this.unmounted) return;
|
||||||
dis.dispatch(GroupActions.fetchJoinedGroups(this.context.matrixClient));
|
dis.dispatch(GroupActions.fetchJoinedGroups(this.context));
|
||||||
},
|
},
|
||||||
|
|
||||||
_onClientSync(syncState, prevState) {
|
_onClientSync(syncState, prevState) {
|
||||||
|
@ -81,7 +80,7 @@ const TagPanel = createReactClass({
|
||||||
const reconnected = syncState !== "ERROR" && prevState !== syncState;
|
const reconnected = syncState !== "ERROR" && prevState !== syncState;
|
||||||
if (reconnected) {
|
if (reconnected) {
|
||||||
// Load joined groups
|
// Load joined groups
|
||||||
dis.dispatch(GroupActions.fetchJoinedGroups(this.context.matrixClient));
|
dis.dispatch(GroupActions.fetchJoinedGroups(this.context));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -104,6 +103,7 @@ const TagPanel = createReactClass({
|
||||||
render() {
|
render() {
|
||||||
const DNDTagTile = sdk.getComponent('elements.DNDTagTile');
|
const DNDTagTile = sdk.getComponent('elements.DNDTagTile');
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
|
const ActionButton = sdk.getComponent('elements.ActionButton');
|
||||||
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
||||||
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
||||||
|
|
||||||
|
@ -154,6 +154,13 @@ const TagPanel = createReactClass({
|
||||||
ref={provided.innerRef}
|
ref={provided.innerRef}
|
||||||
>
|
>
|
||||||
{ tags }
|
{ tags }
|
||||||
|
<div>
|
||||||
|
<ActionButton
|
||||||
|
tooltip
|
||||||
|
label={_t("Communities")}
|
||||||
|
action="toggle_my_groups"
|
||||||
|
className="mx_TagTile mx_TagTile_plus" />
|
||||||
|
</div>
|
||||||
{ provided.placeholder }
|
{ provided.placeholder }
|
||||||
</div>
|
</div>
|
||||||
) }
|
) }
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2019 New Vector Ltd.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import createReactClass from 'create-react-class';
|
|
||||||
import sdk from '../../index';
|
|
||||||
import dis from '../../dispatcher';
|
|
||||||
import Modal from '../../Modal';
|
|
||||||
import { _t } from '../../languageHandler';
|
|
||||||
|
|
||||||
const TagPanelButtons = createReactClass({
|
|
||||||
displayName: 'TagPanelButtons',
|
|
||||||
|
|
||||||
|
|
||||||
componentDidMount: function() {
|
|
||||||
this._dispatcherRef = dis.register(this._onAction);
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
if (this._dispatcherRef) {
|
|
||||||
dis.unregister(this._dispatcherRef);
|
|
||||||
this._dispatcherRef = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_onAction(payload) {
|
|
||||||
if (payload.action === "show_redesign_feedback_dialog") {
|
|
||||||
const RedesignFeedbackDialog =
|
|
||||||
sdk.getComponent("views.dialogs.RedesignFeedbackDialog");
|
|
||||||
Modal.createTrackedDialog('Report bugs & give feedback', '', RedesignFeedbackDialog);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const GroupsButton = sdk.getComponent('elements.GroupsButton');
|
|
||||||
const ActionButton = sdk.getComponent("elements.ActionButton");
|
|
||||||
|
|
||||||
return (<div className="mx_TagPanelButtons">
|
|
||||||
<GroupsButton />
|
|
||||||
<ActionButton
|
|
||||||
className="mx_TagPanelButtons_report" action="show_redesign_feedback_dialog"
|
|
||||||
label={_t("Report bugs & give feedback")} tooltip={true} />
|
|
||||||
</div>);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
export default TagPanelButtons;
|
|
|
@ -19,10 +19,10 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
|
||||||
import AvatarLogic from '../../../Avatar';
|
import AvatarLogic from '../../../Avatar';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
module.exports = createReactClass({
|
module.exports = createReactClass({
|
||||||
displayName: 'BaseAvatar',
|
displayName: 'BaseAvatar',
|
||||||
|
@ -38,10 +38,16 @@ module.exports = createReactClass({
|
||||||
// XXX resizeMethod not actually used.
|
// XXX resizeMethod not actually used.
|
||||||
resizeMethod: PropTypes.string,
|
resizeMethod: PropTypes.string,
|
||||||
defaultToInitialLetter: PropTypes.bool, // true to add default url
|
defaultToInitialLetter: PropTypes.bool, // true to add default url
|
||||||
|
inputRef: PropTypes.oneOfType([
|
||||||
|
// Either a function
|
||||||
|
PropTypes.func,
|
||||||
|
// Or the instance of a DOM native element
|
||||||
|
PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
|
|
||||||
contextTypes: {
|
statics: {
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
contextType: MatrixClientContext,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -59,12 +65,12 @@ module.exports = createReactClass({
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.unmounted = false;
|
this.unmounted = false;
|
||||||
this.context.matrixClient.on('sync', this.onClientSync);
|
this.context.on('sync', this.onClientSync);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.unmounted = true;
|
this.unmounted = true;
|
||||||
this.context.matrixClient.removeListener('sync', this.onClientSync);
|
this.context.removeListener('sync', this.onClientSync);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillReceiveProps: function(nextProps) {
|
componentWillReceiveProps: function(nextProps) {
|
||||||
|
@ -148,7 +154,7 @@ module.exports = createReactClass({
|
||||||
|
|
||||||
const {
|
const {
|
||||||
name, idName, title, url, urls, width, height, resizeMethod,
|
name, idName, title, url, urls, width, height, resizeMethod,
|
||||||
defaultToInitialLetter, onClick,
|
defaultToInitialLetter, onClick, inputRef,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
@ -171,7 +177,7 @@ module.exports = createReactClass({
|
||||||
if (onClick != null) {
|
if (onClick != null) {
|
||||||
return (
|
return (
|
||||||
<AccessibleButton element='span' className="mx_BaseAvatar"
|
<AccessibleButton element='span' className="mx_BaseAvatar"
|
||||||
onClick={onClick} {...otherProps}
|
onClick={onClick} inputRef={inputRef} {...otherProps}
|
||||||
>
|
>
|
||||||
{ textNode }
|
{ textNode }
|
||||||
{ imgNode }
|
{ imgNode }
|
||||||
|
@ -179,7 +185,7 @@ module.exports = createReactClass({
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<span className="mx_BaseAvatar" {...otherProps}>
|
<span className="mx_BaseAvatar" ref={inputRef} {...otherProps}>
|
||||||
{ textNode }
|
{ textNode }
|
||||||
{ imgNode }
|
{ imgNode }
|
||||||
</span>
|
</span>
|
||||||
|
@ -188,21 +194,26 @@ module.exports = createReactClass({
|
||||||
}
|
}
|
||||||
if (onClick != null) {
|
if (onClick != null) {
|
||||||
return (
|
return (
|
||||||
<AccessibleButton className="mx_BaseAvatar mx_BaseAvatar_image"
|
<AccessibleButton
|
||||||
|
className="mx_BaseAvatar mx_BaseAvatar_image"
|
||||||
element='img'
|
element='img'
|
||||||
src={imageUrl}
|
src={imageUrl}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onError={this.onError}
|
onError={this.onError}
|
||||||
width={width} height={height}
|
width={width} height={height}
|
||||||
title={title} alt=""
|
title={title} alt=""
|
||||||
|
inputRef={inputRef}
|
||||||
{...otherProps} />
|
{...otherProps} />
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<img className="mx_BaseAvatar mx_BaseAvatar_image" src={imageUrl}
|
<img
|
||||||
|
className="mx_BaseAvatar mx_BaseAvatar_image"
|
||||||
|
src={imageUrl}
|
||||||
onError={this.onError}
|
onError={this.onError}
|
||||||
width={width} height={height}
|
width={width} height={height}
|
||||||
title={title} alt=""
|
title={title} alt=""
|
||||||
|
ref={inputRef}
|
||||||
{...otherProps} />
|
{...otherProps} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,8 +38,8 @@ export default class MemberStatusMessageAvatar extends React.Component {
|
||||||
resizeMethod: 'crop',
|
resizeMethod: 'crop',
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
hasStatus: this.hasStatus,
|
hasStatus: this.hasStatus,
|
||||||
|
|
|
@ -31,8 +31,8 @@ export default class GroupInviteTileContextMenu extends React.Component {
|
||||||
onFinished: PropTypes.func,
|
onFinished: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context);
|
super(props);
|
||||||
|
|
||||||
this._onClickReject = this._onClickReject.bind(this);
|
this._onClickReject = this._onClickReject.bind(this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,8 @@ export default class StatusMessageContextMenu extends React.Component {
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
message: this.comittedStatusMessage,
|
message: this.comittedStatusMessage,
|
||||||
|
|
|
@ -17,12 +17,12 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import TagOrderActions from '../../../actions/TagOrderActions';
|
import TagOrderActions from '../../../actions/TagOrderActions';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import {MenuItem} from "../../structures/ContextMenu";
|
import {MenuItem} from "../../structures/ContextMenu";
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
export default class TagTileContextMenu extends React.Component {
|
export default class TagTileContextMenu extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -31,9 +31,7 @@ export default class TagTileContextMenu extends React.Component {
|
||||||
onFinished: PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
static contextTypes = {
|
static contextType = MatrixClientContext;
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
@ -51,7 +49,7 @@ export default class TagTileContextMenu extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onRemoveClick() {
|
_onRemoveClick() {
|
||||||
dis.dispatch(TagOrderActions.removeTag(this.context.matrixClient, this.props.tag));
|
dis.dispatch(TagOrderActions.removeTag(this.context, this.props.tag));
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import SdkConfig from '../../../SdkConfig';
|
||||||
import { getHostingLink } from '../../../utils/HostingLink';
|
import { getHostingLink } from '../../../utils/HostingLink';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import {MenuItem} from "../../structures/ContextMenu";
|
import {MenuItem} from "../../structures/ContextMenu";
|
||||||
|
import sdk from "../../../index";
|
||||||
|
|
||||||
export class TopLeftMenu extends React.Component {
|
export class TopLeftMenu extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -100,6 +101,12 @@ export class TopLeftMenu extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const helpItem = (
|
||||||
|
<MenuItem className="mx_TopLeftMenu_icon_help" onClick={this.openHelp}>
|
||||||
|
{_t("Help")}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
|
||||||
const settingsItem = (
|
const settingsItem = (
|
||||||
<MenuItem className="mx_TopLeftMenu_icon_settings" onClick={this.openSettings}>
|
<MenuItem className="mx_TopLeftMenu_icon_settings" onClick={this.openSettings}>
|
||||||
{_t("Settings")}
|
{_t("Settings")}
|
||||||
|
@ -115,11 +122,18 @@ export class TopLeftMenu extends React.Component {
|
||||||
<ul className="mx_TopLeftMenu_section_withIcon" role="none">
|
<ul className="mx_TopLeftMenu_section_withIcon" role="none">
|
||||||
{homePageItem}
|
{homePageItem}
|
||||||
{settingsItem}
|
{settingsItem}
|
||||||
|
{helpItem}
|
||||||
{signInOutItem}
|
{signInOutItem}
|
||||||
</ul>
|
</ul>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openHelp = () => {
|
||||||
|
this.closeMenu();
|
||||||
|
const RedesignFeedbackDialog = sdk.getComponent("views.dialogs.RedesignFeedbackDialog");
|
||||||
|
Modal.createTrackedDialog('Report bugs & give feedback', '', RedesignFeedbackDialog);
|
||||||
|
};
|
||||||
|
|
||||||
viewHomePage() {
|
viewHomePage() {
|
||||||
dis.dispatch({action: 'view_home_page'});
|
dis.dispatch({action: 'view_home_page'});
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
|
|
|
@ -22,12 +22,11 @@ import FocusLock from 'react-focus-lock';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
|
||||||
|
|
||||||
import { Key } from '../../../Keyboard';
|
import { Key } from '../../../Keyboard';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic container for modal dialogs.
|
* Basic container for modal dialogs.
|
||||||
|
@ -84,16 +83,6 @@ export default createReactClass({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
childContextTypes: {
|
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
|
||||||
},
|
|
||||||
|
|
||||||
getChildContext: function() {
|
|
||||||
return {
|
|
||||||
matrixClient: this._matrixClient,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this._matrixClient = MatrixClientPeg.get();
|
this._matrixClient = MatrixClientPeg.get();
|
||||||
},
|
},
|
||||||
|
@ -122,36 +111,38 @@ export default createReactClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FocusLock
|
<MatrixClientContext.Provider value={this._matrixClient}>
|
||||||
returnFocus={true}
|
<FocusLock
|
||||||
lockProps={{
|
returnFocus={true}
|
||||||
onKeyDown: this._onKeyDown,
|
lockProps={{
|
||||||
role: "dialog",
|
onKeyDown: this._onKeyDown,
|
||||||
["aria-labelledby"]: "mx_BaseDialog_title",
|
role: "dialog",
|
||||||
// This should point to a node describing the dialog.
|
["aria-labelledby"]: "mx_BaseDialog_title",
|
||||||
// If we were about to completely follow this recommendation we'd need to
|
// This should point to a node describing the dialog.
|
||||||
// make all the components relying on BaseDialog to be aware of it.
|
// If we were about to completely follow this recommendation we'd need to
|
||||||
// So instead we will use the whole content as the description.
|
// make all the components relying on BaseDialog to be aware of it.
|
||||||
// Description comes first and if the content contains more text,
|
// So instead we will use the whole content as the description.
|
||||||
// AT users can skip its presentation.
|
// Description comes first and if the content contains more text,
|
||||||
["aria-describedby"]: this.props.contentId,
|
// AT users can skip its presentation.
|
||||||
}}
|
["aria-describedby"]: this.props.contentId,
|
||||||
className={classNames({
|
}}
|
||||||
[this.props.className]: true,
|
className={classNames({
|
||||||
'mx_Dialog_fixedWidth': this.props.fixedWidth,
|
[this.props.className]: true,
|
||||||
})}
|
'mx_Dialog_fixedWidth': this.props.fixedWidth,
|
||||||
>
|
})}
|
||||||
<div className={classNames('mx_Dialog_header', {
|
>
|
||||||
'mx_Dialog_headerWithButton': !!this.props.headerButton,
|
<div className={classNames('mx_Dialog_header', {
|
||||||
})}>
|
'mx_Dialog_headerWithButton': !!this.props.headerButton,
|
||||||
<div className={classNames('mx_Dialog_title', this.props.titleClass)} id='mx_BaseDialog_title'>
|
})}>
|
||||||
{ this.props.title }
|
<div className={classNames('mx_Dialog_title', this.props.titleClass)} id='mx_BaseDialog_title'>
|
||||||
|
{ this.props.title }
|
||||||
|
</div>
|
||||||
|
{ this.props.headerButton }
|
||||||
|
{ cancelButton }
|
||||||
</div>
|
</div>
|
||||||
{ this.props.headerButton }
|
{ this.props.children }
|
||||||
{ cancelButton }
|
</FocusLock>
|
||||||
</div>
|
</MatrixClientContext.Provider>
|
||||||
{ this.props.children }
|
|
||||||
</FocusLock>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,8 +25,8 @@ import Modal from '../../../Modal';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
export default class BugReportDialog extends React.Component {
|
export default class BugReportDialog extends React.Component {
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
sendLogs: true,
|
sendLogs: true,
|
||||||
busy: false,
|
busy: false,
|
||||||
|
|
|
@ -173,7 +173,7 @@ export default createReactClass({
|
||||||
const domain = MatrixClientPeg.get().getDomain();
|
const domain = MatrixClientPeg.get().getDomain();
|
||||||
aliasField = (
|
aliasField = (
|
||||||
<div className="mx_CreateRoomDialog_aliasContainer">
|
<div className="mx_CreateRoomDialog_aliasContainer">
|
||||||
<RoomAliasField id="alias" ref={ref => this._aliasFieldRef = ref} onChange={this.onAliasChange} domain={domain} />
|
<RoomAliasField id="alias" ref={ref => this._aliasFieldRef = ref} onChange={this.onAliasChange} domain={domain} value={this.state.alias} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
217
src/components/views/dialogs/DMInviteDialog.js
Normal file
217
src/components/views/dialogs/DMInviteDialog.js
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019, 2020 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 PropTypes from 'prop-types';
|
||||||
|
import {_t} from "../../../languageHandler";
|
||||||
|
import sdk from "../../../index";
|
||||||
|
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||||
|
import {makeUserPermalink} from "../../../utils/permalinks/Permalinks";
|
||||||
|
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||||
|
import {RoomMember} from "matrix-js-sdk/lib/matrix";
|
||||||
|
import * as humanize from "humanize";
|
||||||
|
|
||||||
|
// TODO: [TravisR] Make this generic for all kinds of invites
|
||||||
|
|
||||||
|
const INITIAL_ROOMS_SHOWN = 3; // Number of rooms to show at first
|
||||||
|
const INCREMENT_ROOMS_SHOWN = 5; // Number of rooms to add when 'show more' is clicked
|
||||||
|
|
||||||
|
class DMRoomTile extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
member: PropTypes.object.isRequired,
|
||||||
|
lastActiveTs: PropTypes.number,
|
||||||
|
onToggle: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onClick = (e) => {
|
||||||
|
// Stop the browser from highlighting text
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
this.props.onToggle(this.props.member.userId);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
|
||||||
|
|
||||||
|
let timestamp = null;
|
||||||
|
if (this.props.lastActiveTs) {
|
||||||
|
// TODO: [TravisR] Figure out how to i18n this
|
||||||
|
// `humanize` wants seconds for a timestamp, so divide by 1000
|
||||||
|
const humanTs = humanize.relativeTime(this.props.lastActiveTs / 1000);
|
||||||
|
timestamp = <span className='mx_DMInviteDialog_roomTile_time'>{humanTs}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='mx_DMInviteDialog_roomTile' onClick={this._onClick}>
|
||||||
|
<MemberAvatar member={this.props.member} width={36} height={36} />
|
||||||
|
<span className='mx_DMInviteDialog_roomTile_name'>{this.props.member.name}</span>
|
||||||
|
<span className='mx_DMInviteDialog_roomTile_userId'>{this.props.member.userId}</span>
|
||||||
|
{timestamp}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class DMInviteDialog extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
// Takes an array of user IDs/emails to invite.
|
||||||
|
onFinished: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
targets: [], // string[] of mxids/email addresses
|
||||||
|
filterText: "",
|
||||||
|
recents: this._buildRecents(),
|
||||||
|
numRecentsShown: INITIAL_ROOMS_SHOWN,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_buildRecents(): {userId: string, user: RoomMember, lastActive: number} {
|
||||||
|
const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals();
|
||||||
|
const recents = [];
|
||||||
|
for (const userId in rooms) {
|
||||||
|
const room = rooms[userId];
|
||||||
|
const member = room.getMember(userId);
|
||||||
|
if (!member) continue; // just skip people who don't have memberships for some reason
|
||||||
|
|
||||||
|
const lastEventTs = room.timeline && room.timeline.length
|
||||||
|
? room.timeline[room.timeline.length - 1].getTs()
|
||||||
|
: 0;
|
||||||
|
if (!lastEventTs) continue; // something weird is going on with this room
|
||||||
|
|
||||||
|
recents.push({userId, user: member, lastActive: lastEventTs});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the recents by last active to save us time later
|
||||||
|
recents.sort((a, b) => b.lastActive - a.lastActive);
|
||||||
|
|
||||||
|
return recents;
|
||||||
|
}
|
||||||
|
|
||||||
|
_startDm = () => {
|
||||||
|
this.props.onFinished(this.state.targets);
|
||||||
|
};
|
||||||
|
|
||||||
|
_cancel = () => {
|
||||||
|
this.props.onFinished([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
_updateFilter = (e) => {
|
||||||
|
this.setState({filterText: e.target.value});
|
||||||
|
};
|
||||||
|
|
||||||
|
_showMoreRecents = () => {
|
||||||
|
this.setState({numRecentsShown: this.state.numRecentsShown + INCREMENT_ROOMS_SHOWN});
|
||||||
|
};
|
||||||
|
|
||||||
|
_toggleMember = (userId) => {
|
||||||
|
const targets = this.state.targets.map(t => t); // cheap clone for mutation
|
||||||
|
const idx = targets.indexOf(userId);
|
||||||
|
if (idx >= 0) targets.splice(idx, 1);
|
||||||
|
else targets.push(userId);
|
||||||
|
this.setState({targets});
|
||||||
|
};
|
||||||
|
|
||||||
|
_renderRecents() {
|
||||||
|
if (!this.state.recents || this.state.recents.length === 0) return null;
|
||||||
|
|
||||||
|
// .slice() will return an incomplete array but won't error on us if we go too far
|
||||||
|
const toRender = this.state.recents.slice(0, this.state.numRecentsShown);
|
||||||
|
const hasMore = toRender.length < this.state.recents.length;
|
||||||
|
|
||||||
|
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
||||||
|
let showMore = null;
|
||||||
|
if (hasMore) {
|
||||||
|
showMore = (
|
||||||
|
<AccessibleButton onClick={this._showMoreRecents} kind="link">
|
||||||
|
{_t("Show more")}
|
||||||
|
</AccessibleButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tiles = toRender.map(r => (
|
||||||
|
<DMRoomTile member={r.user} lastActiveTs={r.lastActive} key={r.userId} onToggle={this._toggleMember} />
|
||||||
|
));
|
||||||
|
return (
|
||||||
|
<div className='mx_DMInviteDialog_section'>
|
||||||
|
<h3>{_t("Recent Conversations")}</h3>
|
||||||
|
{tiles}
|
||||||
|
{showMore}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
const Field = sdk.getComponent("elements.Field");
|
||||||
|
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
||||||
|
|
||||||
|
// Dev note: The use of Field is temporary/incomplete pending https://github.com/vector-im/riot-web/issues/11197
|
||||||
|
// For now, we just list who the targets are.
|
||||||
|
const editor = (
|
||||||
|
<div className='mx_DMInviteDialog_editor'>
|
||||||
|
<Field
|
||||||
|
id="inviteTargets"
|
||||||
|
value={this.state.filterText}
|
||||||
|
onChange={this._updateFilter}
|
||||||
|
placeholder="TODO: Implement filtering/searching (vector-im/riot-web#11199)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
const targets = this.state.targets.map(t => <div key={t}>{t}</div>);
|
||||||
|
|
||||||
|
const userId = MatrixClientPeg.get().getUserId();
|
||||||
|
return (
|
||||||
|
<BaseDialog
|
||||||
|
className='mx_DMInviteDialog'
|
||||||
|
hasCancel={true}
|
||||||
|
onFinished={this._cancel}
|
||||||
|
title={_t("Direct Messages")}
|
||||||
|
>
|
||||||
|
<div className='mx_DMInviteDialog_content'>
|
||||||
|
<p>
|
||||||
|
{_t(
|
||||||
|
"If you can't find someone, ask them for their username, or share your " +
|
||||||
|
"username (%(userId)s) or <a>profile link</a>.",
|
||||||
|
{userId},
|
||||||
|
{a: (sub) => <a href={makeUserPermalink(userId)} rel="noopener" target="_blank">{sub}</a>},
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
{targets}
|
||||||
|
<div className='mx_DMInviteDialog_addressBar'>
|
||||||
|
{editor}
|
||||||
|
<AccessibleButton
|
||||||
|
kind="primary"
|
||||||
|
onClick={this._startDm}
|
||||||
|
className='mx_DMInviteDialog_goButton'
|
||||||
|
>
|
||||||
|
{_t("Go")}
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
{this._renderRecents()}
|
||||||
|
</div>
|
||||||
|
</BaseDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,8 +25,8 @@ import * as Lifecycle from '../../../Lifecycle';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
export default class DeactivateAccountDialog extends React.Component {
|
export default class DeactivateAccountDialog extends React.Component {
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context);
|
super(props);
|
||||||
|
|
||||||
this._onOk = this._onOk.bind(this);
|
this._onOk = this._onOk.bind(this);
|
||||||
this._onCancel = this._onCancel.bind(this);
|
this._onCancel = this._onCancel.bind(this);
|
||||||
|
|
|
@ -97,7 +97,7 @@ export default class DeviceVerifyDialog extends React.Component {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const verifyingOwnDevice = this.props.userId === client.getUserId();
|
const verifyingOwnDevice = this.props.userId === client.getUserId();
|
||||||
try {
|
try {
|
||||||
if (!verifyingOwnDevice && SettingsStore.getValue("feature_dm_verification")) {
|
if (!verifyingOwnDevice && SettingsStore.getValue("feature_cross_signing")) {
|
||||||
const roomId = await ensureDMExistsAndOpen(this.props.userId);
|
const roomId = await ensureDMExistsAndOpen(this.props.userId);
|
||||||
// throws upon cancellation before having started
|
// throws upon cancellation before having started
|
||||||
this._verifier = await client.requestVerificationDM(
|
this._verifier = await client.requestVerificationDM(
|
||||||
|
|
|
@ -16,23 +16,19 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { Room } from "matrix-js-sdk";
|
||||||
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import SyntaxHighlight from '../elements/SyntaxHighlight';
|
import SyntaxHighlight from '../elements/SyntaxHighlight';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
|
||||||
import Field from "../elements/Field";
|
import Field from "../elements/Field";
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
class DevtoolsComponent extends React.Component {
|
class GenericEditor extends React.PureComponent {
|
||||||
static contextTypes = {
|
|
||||||
roomId: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class GenericEditor extends DevtoolsComponent {
|
|
||||||
// static propTypes = {onBack: PropTypes.func.isRequired};
|
// static propTypes = {onBack: PropTypes.func.isRequired};
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context);
|
super(props);
|
||||||
this._onChange = this._onChange.bind(this);
|
this._onChange = this._onChange.bind(this);
|
||||||
this.onBack = this.onBack.bind(this);
|
this.onBack = this.onBack.bind(this);
|
||||||
}
|
}
|
||||||
|
@ -67,12 +63,15 @@ class SendCustomEvent extends GenericEditor {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onBack: PropTypes.func.isRequired,
|
onBack: PropTypes.func.isRequired,
|
||||||
|
room: PropTypes.instanceOf(Room).isRequired,
|
||||||
forceStateEvent: PropTypes.bool,
|
forceStateEvent: PropTypes.bool,
|
||||||
inputs: PropTypes.object,
|
inputs: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props, context) {
|
static contextType = MatrixClientContext;
|
||||||
super(props, context);
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
this._send = this._send.bind(this);
|
this._send = this._send.bind(this);
|
||||||
|
|
||||||
const {eventType, stateKey, evContent} = Object.assign({
|
const {eventType, stateKey, evContent} = Object.assign({
|
||||||
|
@ -91,11 +90,11 @@ class SendCustomEvent extends GenericEditor {
|
||||||
}
|
}
|
||||||
|
|
||||||
send(content) {
|
send(content) {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = this.context;
|
||||||
if (this.state.isStateEvent) {
|
if (this.state.isStateEvent) {
|
||||||
return cli.sendStateEvent(this.context.roomId, this.state.eventType, content, this.state.stateKey);
|
return cli.sendStateEvent(this.props.room.roomId, this.state.eventType, content, this.state.stateKey);
|
||||||
} else {
|
} else {
|
||||||
return cli.sendEvent(this.context.roomId, this.state.eventType, content);
|
return cli.sendEvent(this.props.room.roomId, this.state.eventType, content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,13 +153,16 @@ class SendAccountData extends GenericEditor {
|
||||||
static getLabel() { return _t('Send Account Data'); }
|
static getLabel() { return _t('Send Account Data'); }
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
room: PropTypes.instanceOf(Room).isRequired,
|
||||||
isRoomAccountData: PropTypes.bool,
|
isRoomAccountData: PropTypes.bool,
|
||||||
forceMode: PropTypes.bool,
|
forceMode: PropTypes.bool,
|
||||||
inputs: PropTypes.object,
|
inputs: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props, context) {
|
static contextType = MatrixClientContext;
|
||||||
super(props, context);
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
this._send = this._send.bind(this);
|
this._send = this._send.bind(this);
|
||||||
|
|
||||||
const {eventType, evContent} = Object.assign({
|
const {eventType, evContent} = Object.assign({
|
||||||
|
@ -177,9 +179,9 @@ class SendAccountData extends GenericEditor {
|
||||||
}
|
}
|
||||||
|
|
||||||
send(content) {
|
send(content) {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = this.context;
|
||||||
if (this.state.isRoomAccountData) {
|
if (this.state.isRoomAccountData) {
|
||||||
return cli.setRoomAccountData(this.context.roomId, this.state.eventType, content);
|
return cli.setRoomAccountData(this.props.room.roomId, this.state.eventType, content);
|
||||||
}
|
}
|
||||||
return cli.setAccountData(this.state.eventType, content);
|
return cli.setAccountData(this.state.eventType, content);
|
||||||
}
|
}
|
||||||
|
@ -234,7 +236,7 @@ class SendAccountData extends GenericEditor {
|
||||||
const INITIAL_LOAD_TILES = 20;
|
const INITIAL_LOAD_TILES = 20;
|
||||||
const LOAD_TILES_STEP_SIZE = 50;
|
const LOAD_TILES_STEP_SIZE = 50;
|
||||||
|
|
||||||
class FilteredList extends React.Component {
|
class FilteredList extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.any,
|
children: PropTypes.any,
|
||||||
query: PropTypes.string,
|
query: PropTypes.string,
|
||||||
|
@ -247,8 +249,8 @@ class FilteredList extends React.Component {
|
||||||
return children.filter((child) => child.key.toLowerCase().includes(lcQuery));
|
return children.filter((child) => child.key.toLowerCase().includes(lcQuery));
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
filteredChildren: FilteredList.filterChildren(this.props.children, this.props.query),
|
filteredChildren: FilteredList.filterChildren(this.props.children, this.props.query),
|
||||||
|
@ -305,19 +307,20 @@ class FilteredList extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoomStateExplorer extends DevtoolsComponent {
|
class RoomStateExplorer extends React.PureComponent {
|
||||||
static getLabel() { return _t('Explore Room State'); }
|
static getLabel() { return _t('Explore Room State'); }
|
||||||
|
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onBack: PropTypes.func.isRequired,
|
onBack: PropTypes.func.isRequired,
|
||||||
|
room: PropTypes.instanceOf(Room).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props, context) {
|
static contextType = MatrixClientContext;
|
||||||
super(props, context);
|
|
||||||
|
|
||||||
const room = MatrixClientPeg.get().getRoom(this.context.roomId);
|
constructor(props) {
|
||||||
this.roomStateEvents = room.currentState.events;
|
super(props);
|
||||||
|
|
||||||
|
this.roomStateEvents = this.props.room.currentState.events;
|
||||||
|
|
||||||
this.onBack = this.onBack.bind(this);
|
this.onBack = this.onBack.bind(this);
|
||||||
this.editEv = this.editEv.bind(this);
|
this.editEv = this.editEv.bind(this);
|
||||||
|
@ -373,7 +376,7 @@ class RoomStateExplorer extends DevtoolsComponent {
|
||||||
render() {
|
render() {
|
||||||
if (this.state.event) {
|
if (this.state.event) {
|
||||||
if (this.state.editing) {
|
if (this.state.editing) {
|
||||||
return <SendCustomEvent forceStateEvent={true} onBack={this.onBack} inputs={{
|
return <SendCustomEvent room={this.props.room} forceStateEvent={true} onBack={this.onBack} inputs={{
|
||||||
eventType: this.state.event.getType(),
|
eventType: this.state.event.getType(),
|
||||||
evContent: JSON.stringify(this.state.event.getContent(), null, '\t'),
|
evContent: JSON.stringify(this.state.event.getContent(), null, '\t'),
|
||||||
stateKey: this.state.event.getStateKey(),
|
stateKey: this.state.event.getStateKey(),
|
||||||
|
@ -442,15 +445,18 @@ class RoomStateExplorer extends DevtoolsComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AccountDataExplorer extends DevtoolsComponent {
|
class AccountDataExplorer extends React.PureComponent {
|
||||||
static getLabel() { return _t('Explore Account Data'); }
|
static getLabel() { return _t('Explore Account Data'); }
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onBack: PropTypes.func.isRequired,
|
onBack: PropTypes.func.isRequired,
|
||||||
|
room: PropTypes.instanceOf(Room).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props, context) {
|
static contextType = MatrixClientContext;
|
||||||
super(props, context);
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
this.onBack = this.onBack.bind(this);
|
this.onBack = this.onBack.bind(this);
|
||||||
this.editEv = this.editEv.bind(this);
|
this.editEv = this.editEv.bind(this);
|
||||||
|
@ -467,11 +473,10 @@ class AccountDataExplorer extends DevtoolsComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
getData() {
|
getData() {
|
||||||
const cli = MatrixClientPeg.get();
|
|
||||||
if (this.state.isRoomAccountData) {
|
if (this.state.isRoomAccountData) {
|
||||||
return cli.getRoom(this.context.roomId).accountData;
|
return this.props.room.accountData;
|
||||||
}
|
}
|
||||||
return cli.store.accountData;
|
return this.context.store.accountData;
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewSourceClick(event) {
|
onViewSourceClick(event) {
|
||||||
|
@ -505,10 +510,14 @@ class AccountDataExplorer extends DevtoolsComponent {
|
||||||
render() {
|
render() {
|
||||||
if (this.state.event) {
|
if (this.state.event) {
|
||||||
if (this.state.editing) {
|
if (this.state.editing) {
|
||||||
return <SendAccountData isRoomAccountData={this.state.isRoomAccountData} onBack={this.onBack} inputs={{
|
return <SendAccountData
|
||||||
eventType: this.state.event.getType(),
|
room={this.props.room}
|
||||||
evContent: JSON.stringify(this.state.event.getContent(), null, '\t'),
|
isRoomAccountData={this.state.isRoomAccountData}
|
||||||
}} forceMode={true} />;
|
onBack={this.onBack}
|
||||||
|
inputs={{
|
||||||
|
eventType: this.state.event.getType(),
|
||||||
|
evContent: JSON.stringify(this.state.event.getContent(), null, '\t'),
|
||||||
|
}} forceMode={true} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="mx_ViewSource">
|
return <div className="mx_ViewSource">
|
||||||
|
@ -553,17 +562,20 @@ class AccountDataExplorer extends DevtoolsComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ServersInRoomList extends DevtoolsComponent {
|
class ServersInRoomList extends React.PureComponent {
|
||||||
static getLabel() { return _t('View Servers in Room'); }
|
static getLabel() { return _t('View Servers in Room'); }
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onBack: PropTypes.func.isRequired,
|
onBack: PropTypes.func.isRequired,
|
||||||
|
room: PropTypes.instanceOf(Room).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props, context) {
|
static contextType = MatrixClientContext;
|
||||||
super(props, context);
|
|
||||||
|
|
||||||
const room = MatrixClientPeg.get().getRoom(this.context.roomId);
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
const room = this.props.room;
|
||||||
const servers = new Set();
|
const servers = new Set();
|
||||||
room.currentState.getStateEvents("m.room.member").forEach(ev => servers.add(ev.getSender().split(":")[1]));
|
room.currentState.getStateEvents("m.room.member").forEach(ev => servers.add(ev.getSender().split(":")[1]));
|
||||||
this.servers = Array.from(servers).map(s =>
|
this.servers = Array.from(servers).map(s =>
|
||||||
|
@ -602,19 +614,14 @@ const Entries = [
|
||||||
ServersInRoomList,
|
ServersInRoomList,
|
||||||
];
|
];
|
||||||
|
|
||||||
export default class DevtoolsDialog extends React.Component {
|
export default class DevtoolsDialog extends React.PureComponent {
|
||||||
static childContextTypes = {
|
|
||||||
roomId: PropTypes.string.isRequired,
|
|
||||||
// client: PropTypes.instanceOf(MatixClient),
|
|
||||||
};
|
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
roomId: PropTypes.string.isRequired,
|
roomId: PropTypes.string.isRequired,
|
||||||
onFinished: PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context);
|
super(props);
|
||||||
this.onBack = this.onBack.bind(this);
|
this.onBack = this.onBack.bind(this);
|
||||||
this.onCancel = this.onCancel.bind(this);
|
this.onCancel = this.onCancel.bind(this);
|
||||||
|
|
||||||
|
@ -627,10 +634,6 @@ export default class DevtoolsDialog extends React.Component {
|
||||||
this._unmounted = true;
|
this._unmounted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
getChildContext() {
|
|
||||||
return { roomId: this.props.roomId };
|
|
||||||
}
|
|
||||||
|
|
||||||
_setMode(mode) {
|
_setMode(mode) {
|
||||||
return () => {
|
return () => {
|
||||||
this.setState({ mode });
|
this.setState({ mode });
|
||||||
|
@ -654,15 +657,17 @@ export default class DevtoolsDialog extends React.Component {
|
||||||
let body;
|
let body;
|
||||||
|
|
||||||
if (this.state.mode) {
|
if (this.state.mode) {
|
||||||
body = <div>
|
body = <MatrixClientContext.Consumer>
|
||||||
<div className="mx_DevTools_label_left">{ this.state.mode.getLabel() }</div>
|
{(cli) => <React.Fragment>
|
||||||
<div className="mx_DevTools_label_right">Room ID: { this.props.roomId }</div>
|
<div className="mx_DevTools_label_left">{ this.state.mode.getLabel() }</div>
|
||||||
<div className="mx_DevTools_label_bottom" />
|
<div className="mx_DevTools_label_right">Room ID: { this.props.roomId }</div>
|
||||||
<this.state.mode onBack={this.onBack} />
|
<div className="mx_DevTools_label_bottom" />
|
||||||
</div>;
|
<this.state.mode onBack={this.onBack} room={cli.getRoom(this.props.roomId)} />
|
||||||
|
</React.Fragment>}
|
||||||
|
</MatrixClientContext.Consumer>;
|
||||||
} else {
|
} else {
|
||||||
const classes = "mx_DevTools_RoomStateExplorer_button";
|
const classes = "mx_DevTools_RoomStateExplorer_button";
|
||||||
body = <div>
|
body = <React.Fragment>
|
||||||
<div>
|
<div>
|
||||||
<div className="mx_DevTools_label_left">{ _t('Toolbox') }</div>
|
<div className="mx_DevTools_label_left">{ _t('Toolbox') }</div>
|
||||||
<div className="mx_DevTools_label_right">Room ID: { this.props.roomId }</div>
|
<div className="mx_DevTools_label_right">Room ID: { this.props.roomId }</div>
|
||||||
|
@ -679,7 +684,7 @@ export default class DevtoolsDialog extends React.Component {
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<button onClick={this.onCancel}>{ _t('Cancel') }</button>
|
<button onClick={this.onCancel}>{ _t('Cancel') }</button>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</React.Fragment>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2018, 2019 New Vector Ltd
|
Copyright 2018, 2019 New Vector Ltd
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -94,10 +95,14 @@ export default class LogoutDialog extends React.Component {
|
||||||
// verified, so restore the backup which will give us the keys from it and
|
// verified, so restore the backup which will give us the keys from it and
|
||||||
// allow us to trust it (ie. upload keys to it)
|
// allow us to trust it (ie. upload keys to it)
|
||||||
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
|
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
|
||||||
Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, {});
|
Modal.createTrackedDialog(
|
||||||
|
'Restore Backup', '', RestoreKeyBackupDialog, null, null,
|
||||||
|
/* priority = */ false, /* static = */ true,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
|
Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
|
||||||
import("../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog"),
|
import("../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog"),
|
||||||
|
null, null, /* priority = */ false, /* static = */ true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,8 +30,8 @@ export default class ReportEventDialog extends PureComponent {
|
||||||
onFinished: PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
reason: "",
|
reason: "",
|
||||||
|
|
|
@ -24,9 +24,11 @@ import RolesRoomSettingsTab from "../settings/tabs/room/RolesRoomSettingsTab";
|
||||||
import GeneralRoomSettingsTab from "../settings/tabs/room/GeneralRoomSettingsTab";
|
import GeneralRoomSettingsTab from "../settings/tabs/room/GeneralRoomSettingsTab";
|
||||||
import SecurityRoomSettingsTab from "../settings/tabs/room/SecurityRoomSettingsTab";
|
import SecurityRoomSettingsTab from "../settings/tabs/room/SecurityRoomSettingsTab";
|
||||||
import NotificationSettingsTab from "../settings/tabs/room/NotificationSettingsTab";
|
import NotificationSettingsTab from "../settings/tabs/room/NotificationSettingsTab";
|
||||||
|
import BridgeSettingsTab from "../settings/tabs/room/BridgeSettingsTab";
|
||||||
import sdk from "../../../index";
|
import sdk from "../../../index";
|
||||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||||
import dis from "../../../dispatcher";
|
import dis from "../../../dispatcher";
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
|
||||||
export default class RoomSettingsDialog extends React.Component {
|
export default class RoomSettingsDialog extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -52,6 +54,9 @@ export default class RoomSettingsDialog extends React.Component {
|
||||||
|
|
||||||
_getTabs() {
|
_getTabs() {
|
||||||
const tabs = [];
|
const tabs = [];
|
||||||
|
const featureFlag = SettingsStore.isFeatureEnabled("feature_bridge_state");
|
||||||
|
const shouldShowBridgeIcon = featureFlag &&
|
||||||
|
BridgeSettingsTab.getBridgeStateEvents(this.props.roomId).length > 0;
|
||||||
|
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
_td("General"),
|
_td("General"),
|
||||||
|
@ -73,6 +78,15 @@ export default class RoomSettingsDialog extends React.Component {
|
||||||
"mx_RoomSettingsDialog_rolesIcon",
|
"mx_RoomSettingsDialog_rolesIcon",
|
||||||
<NotificationSettingsTab roomId={this.props.roomId} />,
|
<NotificationSettingsTab roomId={this.props.roomId} />,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
if (shouldShowBridgeIcon) {
|
||||||
|
tabs.push(new Tab(
|
||||||
|
_td("Bridge Info"),
|
||||||
|
"mx_RoomSettingsDialog_bridgesIcon",
|
||||||
|
<BridgeSettingsTab roomId={this.props.roomId} />,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
_td("Advanced"),
|
_td("Advanced"),
|
||||||
"mx_RoomSettingsDialog_warningIcon",
|
"mx_RoomSettingsDialog_warningIcon",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2018, 2019 New Vector Ltd
|
Copyright 2018, 2019 New Vector Ltd
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -15,17 +16,18 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { MatrixClient } from 'matrix-js-sdk';
|
||||||
|
|
||||||
import sdk from '../../../../index';
|
import sdk from '../../../../index';
|
||||||
import MatrixClientPeg from '../../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../../MatrixClientPeg';
|
||||||
import Modal from '../../../../Modal';
|
import Modal from '../../../../Modal';
|
||||||
|
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
|
||||||
|
|
||||||
import { _t } from '../../../../languageHandler';
|
import { _t } from '../../../../languageHandler';
|
||||||
import {Key} from "../../../../Keyboard";
|
import {Key} from "../../../../Keyboard";
|
||||||
|
import { accessSecretStorage } from '../../../../CrossSigningManager';
|
||||||
|
|
||||||
const RESTORE_TYPE_PASSPHRASE = 0;
|
const RESTORE_TYPE_PASSPHRASE = 0;
|
||||||
const RESTORE_TYPE_RECOVERYKEY = 1;
|
const RESTORE_TYPE_RECOVERYKEY = 1;
|
||||||
|
const RESTORE_TYPE_SECRET_STORAGE = 2;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Dialog for restoring e2e keys from a backup and the user's recovery key
|
* Dialog for restoring e2e keys from a backup and the user's recovery key
|
||||||
|
@ -35,6 +37,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
backupInfo: null,
|
backupInfo: null,
|
||||||
|
backupKeyStored: null,
|
||||||
loading: false,
|
loading: false,
|
||||||
loadError: null,
|
loadError: null,
|
||||||
restoreError: null,
|
restoreError: null,
|
||||||
|
@ -73,7 +76,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
||||||
onFinished: () => {
|
onFinished: () => {
|
||||||
this._loadBackupStatus();
|
this._loadBackupStatus();
|
||||||
},
|
},
|
||||||
},
|
}, null, /* priority = */ false, /* static = */ true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,6 +151,32 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _restoreWithSecretStorage() {
|
||||||
|
this.setState({
|
||||||
|
loading: true,
|
||||||
|
restoreError: null,
|
||||||
|
restoreType: RESTORE_TYPE_SECRET_STORAGE,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
// `accessSecretStorage` may prompt for storage access as needed.
|
||||||
|
const recoverInfo = await accessSecretStorage(async () => {
|
||||||
|
return MatrixClientPeg.get().restoreKeyBackupWithSecretStorage(
|
||||||
|
this.state.backupInfo,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
recoverInfo,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Error restoring backup", e);
|
||||||
|
this.setState({
|
||||||
|
restoreError: e,
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async _loadBackupStatus() {
|
async _loadBackupStatus() {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true,
|
loading: true,
|
||||||
|
@ -155,10 +184,20 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
||||||
|
const backupKeyStored = await MatrixClientPeg.get().isKeyBackupKeyStored();
|
||||||
|
this.setState({
|
||||||
|
backupInfo,
|
||||||
|
backupKeyStored,
|
||||||
|
});
|
||||||
|
|
||||||
|
// If the backup key is stored, we can proceed directly to restore.
|
||||||
|
if (backupKeyStored) {
|
||||||
|
return this._restoreWithSecretStorage();
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
loadError: null,
|
loadError: null,
|
||||||
loading: false,
|
loading: false,
|
||||||
backupInfo,
|
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Error loading backup status", e);
|
console.log("Error loading backup status", e);
|
||||||
|
|
|
@ -48,7 +48,7 @@ export default class AccessibleTooltipButton extends React.PureComponent {
|
||||||
const Tooltip = sdk.getComponent("elements.Tooltip");
|
const Tooltip = sdk.getComponent("elements.Tooltip");
|
||||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
||||||
|
|
||||||
const {title, ...props} = this.props;
|
const {title, children, ...props} = this.props;
|
||||||
|
|
||||||
const tip = this.state.hover ? <Tooltip
|
const tip = this.state.hover ? <Tooltip
|
||||||
className="mx_AccessibleTooltipButton_container"
|
className="mx_AccessibleTooltipButton_container"
|
||||||
|
@ -57,6 +57,7 @@ export default class AccessibleTooltipButton extends React.PureComponent {
|
||||||
/> : <div />;
|
/> : <div />;
|
||||||
return (
|
return (
|
||||||
<AccessibleButton {...props} onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut} aria-label={title}>
|
<AccessibleButton {...props} onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut} aria-label={title}>
|
||||||
|
{ children }
|
||||||
{ tip }
|
{ tip }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
|
|
|
@ -22,6 +22,8 @@ import sdk from '../../../index';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
// XXX: This component is *not* cross-signing aware. Once everything is
|
||||||
|
// cross-signing, this component should just go away.
|
||||||
export default createReactClass({
|
export default createReactClass({
|
||||||
displayName: 'DeviceVerifyButtons',
|
displayName: 'DeviceVerifyButtons',
|
||||||
|
|
||||||
|
|
|
@ -25,13 +25,13 @@ import sdk from '../../../index';
|
||||||
* Parent components should supply an 'onSubmit' callback which returns a
|
* Parent components should supply an 'onSubmit' callback which returns a
|
||||||
* promise; a spinner is shown until the promise resolves.
|
* promise; a spinner is shown until the promise resolves.
|
||||||
*
|
*
|
||||||
* The parent can also supply a 'getIntialValue' callback, which works in a
|
* The parent can also supply a 'getInitialValue' callback, which works in a
|
||||||
* similarly asynchronous way. If this is not provided, the initial value is
|
* similarly asynchronous way. If this is not provided, the initial value is
|
||||||
* taken from the 'initialValue' property.
|
* taken from the 'initialValue' property.
|
||||||
*/
|
*/
|
||||||
export default class EditableTextContainer extends React.Component {
|
export default class EditableTextContainer extends React.Component {
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context);
|
super(props);
|
||||||
|
|
||||||
this._unmounted = false;
|
this._unmounted = false;
|
||||||
this.state = {
|
this.state = {
|
||||||
|
|
|
@ -18,9 +18,9 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {MatrixClient} from 'matrix-js-sdk';
|
|
||||||
import FlairStore from '../../../stores/FlairStore';
|
import FlairStore from '../../../stores/FlairStore';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
|
|
||||||
class FlairAvatar extends React.Component {
|
class FlairAvatar extends React.Component {
|
||||||
|
@ -40,7 +40,7 @@ class FlairAvatar extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const httpUrl = this.context.matrixClient.mxcUrlToHttp(
|
const httpUrl = this.context.mxcUrlToHttp(
|
||||||
this.props.groupProfile.avatarUrl, 16, 16, 'scale', false);
|
this.props.groupProfile.avatarUrl, 16, 16, 'scale', false);
|
||||||
const tooltip = this.props.groupProfile.name ?
|
const tooltip = this.props.groupProfile.name ?
|
||||||
`${this.props.groupProfile.name} (${this.props.groupProfile.groupId})`:
|
`${this.props.groupProfile.name} (${this.props.groupProfile.groupId})`:
|
||||||
|
@ -62,9 +62,7 @@ FlairAvatar.propTypes = {
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
FlairAvatar.contextTypes = {
|
FlairAvatar.contextType = MatrixClientContext;
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class Flair extends React.Component {
|
export default class Flair extends React.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -92,7 +90,7 @@ export default class Flair extends React.Component {
|
||||||
for (const groupId of groups) {
|
for (const groupId of groups) {
|
||||||
let groupProfile = null;
|
let groupProfile = null;
|
||||||
try {
|
try {
|
||||||
groupProfile = await FlairStore.getGroupProfileCached(this.context.matrixClient, groupId);
|
groupProfile = await FlairStore.getGroupProfileCached(this.context, groupId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Could not get profile for group', groupId, err);
|
console.error('Could not get profile for group', groupId, err);
|
||||||
}
|
}
|
||||||
|
@ -134,6 +132,4 @@ Flair.propTypes = {
|
||||||
groups: PropTypes.arrayOf(PropTypes.string),
|
groups: PropTypes.arrayOf(PropTypes.string),
|
||||||
};
|
};
|
||||||
|
|
||||||
Flair.contextTypes = {
|
Flair.contextType = MatrixClientContext;
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 New Vector Ltd
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import sdk from '../../../index';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { _t } from '../../../languageHandler';
|
|
||||||
|
|
||||||
const GroupsButton = function(props) {
|
|
||||||
const ActionButton = sdk.getComponent('elements.ActionButton');
|
|
||||||
return (
|
|
||||||
<ActionButton className="mx_GroupsButton" action="toggle_my_groups"
|
|
||||||
label={_t("Communities")}
|
|
||||||
size={props.size}
|
|
||||||
tooltip={true}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
GroupsButton.propTypes = {
|
|
||||||
size: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default GroupsButton;
|
|
|
@ -20,12 +20,13 @@ import createReactClass from 'create-react-class';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Room, RoomMember, MatrixClient } from 'matrix-js-sdk';
|
import { Room, RoomMember } from 'matrix-js-sdk';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import { getDisplayAliasForRoom } from '../../../Rooms';
|
import { getDisplayAliasForRoom } from '../../../Rooms';
|
||||||
import FlairStore from "../../../stores/FlairStore";
|
import FlairStore from "../../../stores/FlairStore";
|
||||||
import {getPrimaryPermalinkEntity} from "../../../utils/permalinks/Permalinks";
|
import {getPrimaryPermalinkEntity} from "../../../utils/permalinks/Permalinks";
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
// For URLs of matrix.to links in the timeline which have been reformatted by
|
// For URLs of matrix.to links in the timeline which have been reformatted by
|
||||||
// HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`)
|
// HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`)
|
||||||
|
@ -66,17 +67,6 @@ const Pill = createReactClass({
|
||||||
isSelected: PropTypes.bool,
|
isSelected: PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
childContextTypes: {
|
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
|
||||||
},
|
|
||||||
|
|
||||||
getChildContext() {
|
|
||||||
return {
|
|
||||||
matrixClient: this._matrixClient,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
return {
|
return {
|
||||||
// ID/alias of the room/user
|
// ID/alias of the room/user
|
||||||
|
@ -127,7 +117,7 @@ const Pill = createReactClass({
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Pill.TYPE_USER_MENTION: {
|
case Pill.TYPE_USER_MENTION: {
|
||||||
const localMember = nextProps.room ? nextProps.room.getMember(resourceId) : null;
|
const localMember = nextProps.room ? nextProps.room.getMember(resourceId) : undefined;
|
||||||
member = localMember;
|
member = localMember;
|
||||||
if (!localMember) {
|
if (!localMember) {
|
||||||
member = new RoomMember(null, resourceId);
|
member = new RoomMember(null, resourceId);
|
||||||
|
@ -276,15 +266,17 @@ const Pill = createReactClass({
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.state.pillType) {
|
if (this.state.pillType) {
|
||||||
return this.props.inMessage ?
|
return <MatrixClientContext.Provider value={this._matrixClient}>
|
||||||
<a className={classes} href={href} onClick={onClick} title={resource} data-offset-key={this.props.offsetKey}>
|
{ this.props.inMessage ?
|
||||||
{ avatar }
|
<a className={classes} href={href} onClick={onClick} title={resource} data-offset-key={this.props.offsetKey}>
|
||||||
{ linkText }
|
{ avatar }
|
||||||
</a> :
|
{ linkText }
|
||||||
<span className={classes} title={resource} data-offset-key={this.props.offsetKey}>
|
</a> :
|
||||||
{ avatar }
|
<span className={classes} title={resource} data-offset-key={this.props.offsetKey}>
|
||||||
{ linkText }
|
{ avatar }
|
||||||
</span>;
|
{ linkText }
|
||||||
|
</span> }
|
||||||
|
</MatrixClientContext.Provider>;
|
||||||
} else {
|
} else {
|
||||||
// Deliberately render nothing if the URL isn't recognised
|
// Deliberately render nothing if the URL isn't recognised
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -21,10 +21,11 @@ import {_t} from '../../../languageHandler';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import {wantsDateSeparator} from '../../../DateUtils';
|
import {wantsDateSeparator} from '../../../DateUtils';
|
||||||
import {MatrixEvent, MatrixClient} from 'matrix-js-sdk';
|
import {MatrixEvent} from 'matrix-js-sdk';
|
||||||
import {makeUserPermalink, RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks";
|
import {makeUserPermalink, RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import escapeHtml from "escape-html";
|
import escapeHtml from "escape-html";
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
// This component does no cycle detection, simply because the only way to make such a cycle would be to
|
// This component does no cycle detection, simply because the only way to make such a cycle would be to
|
||||||
// craft event_id's, using a homeserver that generates predictable event IDs; even then the impact would
|
// craft event_id's, using a homeserver that generates predictable event IDs; even then the impact would
|
||||||
|
@ -38,12 +39,10 @@ export default class ReplyThread extends React.Component {
|
||||||
permalinkCreator: PropTypes.instanceOf(RoomPermalinkCreator).isRequired,
|
permalinkCreator: PropTypes.instanceOf(RoomPermalinkCreator).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
static contextTypes = {
|
static contextType = MatrixClientContext;
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
// The loaded events to be rendered as linear-replies
|
// The loaded events to be rendered as linear-replies
|
||||||
|
@ -187,7 +186,7 @@ export default class ReplyThread extends React.Component {
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.unmounted = false;
|
this.unmounted = false;
|
||||||
this.room = this.context.matrixClient.getRoom(this.props.parentEv.getRoomId());
|
this.room = this.context.getRoom(this.props.parentEv.getRoomId());
|
||||||
this.room.on("Room.redaction", this.onRoomRedaction);
|
this.room.on("Room.redaction", this.onRoomRedaction);
|
||||||
// same event handler as Room.redaction as for both we just do forceUpdate
|
// same event handler as Room.redaction as for both we just do forceUpdate
|
||||||
this.room.on("Room.redactionCancelled", this.onRoomRedaction);
|
this.room.on("Room.redactionCancelled", this.onRoomRedaction);
|
||||||
|
@ -259,7 +258,7 @@ export default class ReplyThread extends React.Component {
|
||||||
try {
|
try {
|
||||||
// ask the client to fetch the event we want using the context API, only interface to do so is to ask
|
// ask the client to fetch the event we want using the context API, only interface to do so is to ask
|
||||||
// for a timeline with that event, but once it is loaded we can use findEventById to look up the ev map
|
// for a timeline with that event, but once it is loaded we can use findEventById to look up the ev map
|
||||||
await this.context.matrixClient.getEventTimeline(this.room.getUnfilteredTimelineSet(), eventId);
|
await this.context.getEventTimeline(this.room.getUnfilteredTimelineSet(), eventId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// if it fails catch the error and return early, there's no point trying to find the event in this case.
|
// if it fails catch the error and return early, there's no point trying to find the event in this case.
|
||||||
// Return null as it is falsey and thus should be treated as an error (as the event cannot be resolved).
|
// Return null as it is falsey and thus should be treated as an error (as the event cannot be resolved).
|
||||||
|
@ -300,7 +299,7 @@ export default class ReplyThread extends React.Component {
|
||||||
} else if (this.state.loadedEv) {
|
} else if (this.state.loadedEv) {
|
||||||
const ev = this.state.loadedEv;
|
const ev = this.state.loadedEv;
|
||||||
const Pill = sdk.getComponent('elements.Pill');
|
const Pill = sdk.getComponent('elements.Pill');
|
||||||
const room = this.context.matrixClient.getRoom(ev.getRoomId());
|
const room = this.context.getRoom(ev.getRoomId());
|
||||||
header = <blockquote className="mx_ReplyThread">
|
header = <blockquote className="mx_ReplyThread">
|
||||||
{
|
{
|
||||||
_t('<a>In reply to</a> <pill>', {}, {
|
_t('<a>In reply to</a> <pill>', {}, {
|
||||||
|
|
|
@ -20,11 +20,13 @@ import sdk from '../../../index';
|
||||||
import withValidation from './Validation';
|
import withValidation from './Validation';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
|
|
||||||
|
// Controlled form component wrapping Field for inputting a room alias scoped to a given domain
|
||||||
export default class RoomAliasField extends React.PureComponent {
|
export default class RoomAliasField extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
domain: PropTypes.string.isRequired,
|
domain: PropTypes.string.isRequired,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
|
value: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -53,6 +55,7 @@ export default class RoomAliasField extends React.PureComponent {
|
||||||
onValidate={this._onValidate}
|
onValidate={this._onValidate}
|
||||||
placeholder={_t("e.g. my-room")}
|
placeholder={_t("e.g. my-room")}
|
||||||
onChange={this._onChange}
|
onChange={this._onChange}
|
||||||
|
value={this.props.value.substring(1, this.props.value.length - this.props.domain.length - 1)}
|
||||||
maxLength={maxlength} />
|
maxLength={maxlength} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -61,7 +64,7 @@ export default class RoomAliasField extends React.PureComponent {
|
||||||
if (this.props.onChange) {
|
if (this.props.onChange) {
|
||||||
this.props.onChange(this._asFullAlias(ev.target.value));
|
this.props.onChange(this._asFullAlias(ev.target.value));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
_onValidate = async (fieldState) => {
|
_onValidate = async (fieldState) => {
|
||||||
const result = await this._validationRules(fieldState);
|
const result = await this._validationRules(fieldState);
|
||||||
|
|
|
@ -24,8 +24,8 @@ export default class SyntaxHighlight extends React.Component {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context);
|
super(props);
|
||||||
|
|
||||||
this._ref = this._ref.bind(this);
|
this._ref = this._ref.bind(this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,17 +20,16 @@ import React, {createRef} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import {_t} from '../../../languageHandler';
|
|
||||||
import { isOnlyCtrlOrCmdIgnoreShiftKeyEvent } from '../../../Keyboard';
|
import { isOnlyCtrlOrCmdIgnoreShiftKeyEvent } from '../../../Keyboard';
|
||||||
import * as FormattingUtils from '../../../utils/FormattingUtils';
|
import * as FormattingUtils from '../../../utils/FormattingUtils';
|
||||||
|
|
||||||
import FlairStore from '../../../stores/FlairStore';
|
import FlairStore from '../../../stores/FlairStore';
|
||||||
import GroupStore from '../../../stores/GroupStore';
|
import GroupStore from '../../../stores/GroupStore';
|
||||||
import TagOrderStore from '../../../stores/TagOrderStore';
|
import TagOrderStore from '../../../stores/TagOrderStore';
|
||||||
import {ContextMenu, ContextMenuButton, toRightOf} from "../../structures/ContextMenu";
|
import {ContextMenu, toRightOf} from "../../structures/ContextMenu";
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
// A class for a child of TagPanel (possibly wrapped in a DNDTagTile) that represents
|
// A class for a child of TagPanel (possibly wrapped in a DNDTagTile) that represents
|
||||||
// a thing to click on for the user to filter the visible rooms in the RoomList to:
|
// a thing to click on for the user to filter the visible rooms in the RoomList to:
|
||||||
|
@ -46,8 +45,8 @@ export default createReactClass({
|
||||||
tag: PropTypes.string,
|
tag: PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
contextTypes: {
|
statics: {
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
contextType: MatrixClientContext,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
|
@ -83,7 +82,7 @@ export default createReactClass({
|
||||||
_onFlairStoreUpdated() {
|
_onFlairStoreUpdated() {
|
||||||
if (this.unmounted) return;
|
if (this.unmounted) return;
|
||||||
FlairStore.getGroupProfileCached(
|
FlairStore.getGroupProfileCached(
|
||||||
this.context.matrixClient,
|
this.context,
|
||||||
this.props.tag,
|
this.props.tag,
|
||||||
).then((profile) => {
|
).then((profile) => {
|
||||||
if (this.unmounted) return;
|
if (this.unmounted) return;
|
||||||
|
@ -114,12 +113,10 @@ export default createReactClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onMouseOver: function() {
|
onMouseOver: function() {
|
||||||
console.log("DEBUG onMouseOver");
|
|
||||||
this.setState({hover: true});
|
this.setState({hover: true});
|
||||||
},
|
},
|
||||||
|
|
||||||
onMouseOut: function() {
|
onMouseOut: function() {
|
||||||
console.log("DEBUG onMouseOut");
|
|
||||||
this.setState({hover: false});
|
this.setState({hover: false});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -142,12 +139,11 @@ export default createReactClass({
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||||
const Tooltip = sdk.getComponent('elements.Tooltip');
|
|
||||||
const profile = this.state.profile || {};
|
const profile = this.state.profile || {};
|
||||||
const name = profile.name || this.props.tag;
|
const name = profile.name || this.props.tag;
|
||||||
const avatarHeight = 40;
|
const avatarHeight = 40;
|
||||||
|
|
||||||
const httpUrl = profile.avatarUrl ? this.context.matrixClient.mxcUrlToHttp(
|
const httpUrl = profile.avatarUrl ? this.context.mxcUrlToHttp(
|
||||||
profile.avatarUrl, avatarHeight, avatarHeight, "crop",
|
profile.avatarUrl, avatarHeight, avatarHeight, "crop",
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
|
@ -166,9 +162,6 @@ export default createReactClass({
|
||||||
badgeElement = (<div className={badgeClasses}>{FormattingUtils.formatCount(badge.count)}</div>);
|
badgeElement = (<div className={badgeClasses}>{FormattingUtils.formatCount(badge.count)}</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tip = this.state.hover ?
|
|
||||||
<Tooltip className="mx_TagTile_tooltip" label={name} /> :
|
|
||||||
<div />;
|
|
||||||
// FIXME: this ought to use AccessibleButton for a11y but that causes onMouseOut/onMouseOver to fire too much
|
// FIXME: this ought to use AccessibleButton for a11y but that causes onMouseOut/onMouseOver to fire too much
|
||||||
const contextButton = this.state.hover || this.state.menuDisplayed ?
|
const contextButton = this.state.hover || this.state.menuDisplayed ?
|
||||||
<div className="mx_TagTile_context_button" onClick={this.openMenu} ref={this._contextMenuButton}>
|
<div className="mx_TagTile_context_button" onClick={this.openMenu} ref={this._contextMenuButton}>
|
||||||
|
@ -186,14 +179,9 @@ export default createReactClass({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AccessibleTooltipButton = sdk.getComponent("elements.AccessibleTooltipButton");
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
<ContextMenuButton
|
<AccessibleTooltipButton className={className} onClick={this.onClick} onContextMenu={this.openMenu} title={name}>
|
||||||
className={className}
|
|
||||||
onClick={this.onClick}
|
|
||||||
onContextMenu={this.openMenu}
|
|
||||||
label={_t("Options")}
|
|
||||||
isExpanded={this.state.menuDisplayed}
|
|
||||||
>
|
|
||||||
<div className="mx_TagTile_avatar" onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut}>
|
<div className="mx_TagTile_avatar" onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut}>
|
||||||
<BaseAvatar
|
<BaseAvatar
|
||||||
name={name}
|
name={name}
|
||||||
|
@ -202,11 +190,10 @@ export default createReactClass({
|
||||||
width={avatarHeight}
|
width={avatarHeight}
|
||||||
height={avatarHeight}
|
height={avatarHeight}
|
||||||
/>
|
/>
|
||||||
{ tip }
|
|
||||||
{ contextButton }
|
{ contextButton }
|
||||||
{ badgeElement }
|
{ badgeElement }
|
||||||
</div>
|
</div>
|
||||||
</ContextMenuButton>
|
</AccessibleTooltipButton>
|
||||||
|
|
||||||
{ contextMenu }
|
{ contextMenu }
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
|
|
|
@ -16,54 +16,12 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import EMOJIBASE from 'emojibase-data/en/compact.json';
|
|
||||||
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
import * as recent from './recent';
|
import * as recent from './recent';
|
||||||
|
import {DATA_BY_CATEGORY, getEmojiFromUnicode} from "../../../emoji";
|
||||||
const EMOJIBASE_CATEGORY_IDS = [
|
|
||||||
"people", // smileys
|
|
||||||
"people", // actually people
|
|
||||||
"control", // modifiers and such, not displayed in picker
|
|
||||||
"nature",
|
|
||||||
"foods",
|
|
||||||
"places",
|
|
||||||
"activity",
|
|
||||||
"objects",
|
|
||||||
"symbols",
|
|
||||||
"flags",
|
|
||||||
];
|
|
||||||
|
|
||||||
const DATA_BY_CATEGORY = {
|
|
||||||
"people": [],
|
|
||||||
"nature": [],
|
|
||||||
"foods": [],
|
|
||||||
"places": [],
|
|
||||||
"activity": [],
|
|
||||||
"objects": [],
|
|
||||||
"symbols": [],
|
|
||||||
"flags": [],
|
|
||||||
};
|
|
||||||
const DATA_BY_EMOJI = {};
|
|
||||||
|
|
||||||
const VARIATION_SELECTOR = String.fromCharCode(0xFE0F);
|
|
||||||
EMOJIBASE.forEach(emoji => {
|
|
||||||
if (emoji.unicode.includes(VARIATION_SELECTOR)) {
|
|
||||||
// Clone data into variation-less version
|
|
||||||
emoji = Object.assign({}, emoji, {
|
|
||||||
unicode: emoji.unicode.replace(VARIATION_SELECTOR, ""),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
DATA_BY_EMOJI[emoji.unicode] = emoji;
|
|
||||||
const categoryId = EMOJIBASE_CATEGORY_IDS[emoji.group];
|
|
||||||
if (DATA_BY_CATEGORY.hasOwnProperty(categoryId)) {
|
|
||||||
DATA_BY_CATEGORY[categoryId].push(emoji);
|
|
||||||
}
|
|
||||||
// This is used as the string to match the query against when filtering emojis.
|
|
||||||
emoji.filterString = `${emoji.annotation}\n${emoji.shortcodes.join('\n')}}\n${emoji.emoticon || ''}`.toLowerCase();
|
|
||||||
});
|
|
||||||
|
|
||||||
export const CATEGORY_HEADER_HEIGHT = 22;
|
export const CATEGORY_HEADER_HEIGHT = 22;
|
||||||
export const EMOJI_HEIGHT = 37;
|
export const EMOJI_HEIGHT = 37;
|
||||||
|
@ -91,7 +49,7 @@ class EmojiPicker extends React.Component {
|
||||||
|
|
||||||
// Convert recent emoji characters to emoji data, removing unknowns.
|
// Convert recent emoji characters to emoji data, removing unknowns.
|
||||||
this.recentlyUsed = recent.get()
|
this.recentlyUsed = recent.get()
|
||||||
.map(unicode => DATA_BY_EMOJI[unicode])
|
.map(unicode => getEmojiFromUnicode(unicode))
|
||||||
.filter(data => !!data);
|
.filter(data => !!data);
|
||||||
this.memoizedDataByCategory = {
|
this.memoizedDataByCategory = {
|
||||||
recent: this.recentlyUsed,
|
recent: this.recentlyUsed,
|
||||||
|
|
|
@ -19,15 +19,15 @@ import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { findEmojiData } from '../../../HtmlUtils';
|
import {getEmojiFromUnicode} from "../../../emoji";
|
||||||
|
|
||||||
|
// We use the variation-selector Heart in Quick Reactions for some reason
|
||||||
const QUICK_REACTIONS = ["👍", "👎", "😄", "🎉", "😕", "❤️", "🚀", "👀"].map(emoji => {
|
const QUICK_REACTIONS = ["👍", "👎", "😄", "🎉", "😕", "❤️", "🚀", "👀"].map(emoji => {
|
||||||
const data = findEmojiData(emoji);
|
const data = getEmojiFromUnicode(emoji);
|
||||||
if (!data) {
|
if (!data) {
|
||||||
throw new Error(`Emoji ${emoji} doesn't exist in emojibase`);
|
throw new Error(`Emoji ${emoji} doesn't exist in emojibase`);
|
||||||
}
|
}
|
||||||
// Prefer our unicode value for quick reactions (which does not have
|
// Prefer our unicode value for quick reactions as we sometimes use variation selectors.
|
||||||
// variation selectors).
|
|
||||||
return Object.assign({}, data, { unicode: emoji });
|
return Object.assign({}, data, { unicode: emoji });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ module.exports = createReactClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<div className="mx_MatrixToolbar">
|
<div className="mx_MatrixToolbar">
|
||||||
<img className="mx_MatrixToolbar_warning" src={require("../../../../res/img/warning.svg")} width="24" height="23" />
|
<img className="mx_MatrixToolbar_warning" src={require("../../../../res/img/warning.svg")} width="24" height="23" alt="" />
|
||||||
<div className="mx_MatrixToolbar_content">
|
<div className="mx_MatrixToolbar_content">
|
||||||
{ _t('You are not receiving desktop notifications') } <a className="mx_MatrixToolbar_link" onClick={ this.onClick }> { _t('Enable them now') }</a>
|
{ _t('You are not receiving desktop notifications') } <a className="mx_MatrixToolbar_link" onClick={ this.onClick }> { _t('Enable them now') }</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -97,7 +97,7 @@ export default createReactClass({
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="mx_MatrixToolbar">
|
<div className="mx_MatrixToolbar">
|
||||||
<img className="mx_MatrixToolbar_warning" src={require("../../../../res/img/warning.svg")} width="24" height="23" />
|
<img className="mx_MatrixToolbar_warning" src={require("../../../../res/img/warning.svg")} width="24" height="23" alt="" />
|
||||||
<div className="mx_MatrixToolbar_content">
|
<div className="mx_MatrixToolbar_content">
|
||||||
{_t("A new version of Riot is available.")}
|
{_t("A new version of Riot is available.")}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,13 +19,13 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import {_t} from '../../../languageHandler';
|
import {_t} from '../../../languageHandler';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||||
import {ContextMenu, ContextMenuButton, toRightOf} from "../../structures/ContextMenu";
|
import {ContextMenu, ContextMenuButton, toRightOf} from "../../structures/ContextMenu";
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
// XXX this class copies a lot from RoomTile.js
|
// XXX this class copies a lot from RoomTile.js
|
||||||
export default createReactClass({
|
export default createReactClass({
|
||||||
|
@ -35,8 +35,8 @@ export default createReactClass({
|
||||||
group: PropTypes.object.isRequired,
|
group: PropTypes.object.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
contextTypes: {
|
statics: {
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
contextType: MatrixClientContext,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -58,7 +58,7 @@ export default createReactClass({
|
||||||
onMouseEnter: function() {
|
onMouseEnter: function() {
|
||||||
const state = {hover: true};
|
const state = {hover: true};
|
||||||
// Only allow non-guests to access the context menu
|
// Only allow non-guests to access the context menu
|
||||||
if (!this.context.matrixClient.isGuest()) {
|
if (!this.context.isGuest()) {
|
||||||
state.badgeHover = true;
|
state.badgeHover = true;
|
||||||
}
|
}
|
||||||
this.setState(state);
|
this.setState(state);
|
||||||
|
@ -118,7 +118,7 @@ export default createReactClass({
|
||||||
|
|
||||||
const groupName = this.props.group.name || this.props.group.groupId;
|
const groupName = this.props.group.name || this.props.group.groupId;
|
||||||
const httpAvatarUrl = this.props.group.avatarUrl ?
|
const httpAvatarUrl = this.props.group.avatarUrl ?
|
||||||
this.context.matrixClient.mxcUrlToHttp(this.props.group.avatarUrl, 24, 24) : null;
|
this.context.mxcUrlToHttp(this.props.group.avatarUrl, 24, 24) : null;
|
||||||
|
|
||||||
const av = <BaseAvatar name={groupName} width={24} height={24} url={httpAvatarUrl} />;
|
const av = <BaseAvatar name={groupName} width={24} height={24} url={httpAvatarUrl} />;
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
|
@ -26,12 +25,13 @@ import { _t } from '../../../languageHandler';
|
||||||
import { GroupMemberType } from '../../../groups';
|
import { GroupMemberType } from '../../../groups';
|
||||||
import GroupStore from '../../../stores/GroupStore';
|
import GroupStore from '../../../stores/GroupStore';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
module.exports = createReactClass({
|
module.exports = createReactClass({
|
||||||
displayName: 'GroupMemberInfo',
|
displayName: 'GroupMemberInfo',
|
||||||
|
|
||||||
contextTypes: {
|
statics: {
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
contextType: MatrixClientContext,
|
||||||
},
|
},
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
|
@ -85,7 +85,7 @@ module.exports = createReactClass({
|
||||||
_onKick: function() {
|
_onKick: function() {
|
||||||
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
||||||
Modal.createDialog(ConfirmUserActionDialog, {
|
Modal.createDialog(ConfirmUserActionDialog, {
|
||||||
matrixClient: this.context.matrixClient,
|
matrixClient: this.context,
|
||||||
groupMember: this.props.groupMember,
|
groupMember: this.props.groupMember,
|
||||||
action: this.state.isUserInvited ? _t('Disinvite') : _t('Remove from community'),
|
action: this.state.isUserInvited ? _t('Disinvite') : _t('Remove from community'),
|
||||||
title: this.state.isUserInvited ? _t('Disinvite this user from community?')
|
title: this.state.isUserInvited ? _t('Disinvite this user from community?')
|
||||||
|
@ -95,7 +95,7 @@ module.exports = createReactClass({
|
||||||
if (!proceed) return;
|
if (!proceed) return;
|
||||||
|
|
||||||
this.setState({removingUser: true});
|
this.setState({removingUser: true});
|
||||||
this.context.matrixClient.removeUserFromGroup(
|
this.context.removeUserFromGroup(
|
||||||
this.props.groupId, this.props.groupMember.userId,
|
this.props.groupId, this.props.groupMember.userId,
|
||||||
).then(() => {
|
).then(() => {
|
||||||
// return to the user list
|
// return to the user list
|
||||||
|
@ -171,7 +171,7 @@ module.exports = createReactClass({
|
||||||
const avatarUrl = this.props.groupMember.avatarUrl;
|
const avatarUrl = this.props.groupMember.avatarUrl;
|
||||||
let avatarElement;
|
let avatarElement;
|
||||||
if (avatarUrl) {
|
if (avatarUrl) {
|
||||||
const httpUrl = this.context.matrixClient.mxcUrlToHttp(avatarUrl, 800, 800);
|
const httpUrl = this.context.mxcUrlToHttp(avatarUrl, 800, 800);
|
||||||
avatarElement = (<div className="mx_MemberInfo_avatar">
|
avatarElement = (<div className="mx_MemberInfo_avatar">
|
||||||
<img src={httpUrl} />
|
<img src={httpUrl} />
|
||||||
</div>);
|
</div>);
|
||||||
|
|
|
@ -19,10 +19,10 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import { GroupMemberType } from '../../../groups';
|
import { GroupMemberType } from '../../../groups';
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
export default createReactClass({
|
export default createReactClass({
|
||||||
displayName: 'GroupMemberTile',
|
displayName: 'GroupMemberTile',
|
||||||
|
@ -36,8 +36,8 @@ export default createReactClass({
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
|
|
||||||
contextTypes: {
|
statics: {
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
contextType: MatrixClientContext,
|
||||||
},
|
},
|
||||||
|
|
||||||
onClick: function(e) {
|
onClick: function(e) {
|
||||||
|
@ -53,7 +53,7 @@ export default createReactClass({
|
||||||
const EntityTile = sdk.getComponent('rooms.EntityTile');
|
const EntityTile = sdk.getComponent('rooms.EntityTile');
|
||||||
|
|
||||||
const name = this.props.member.displayname || this.props.member.userId;
|
const name = this.props.member.displayname || this.props.member.userId;
|
||||||
const avatarUrl = this.context.matrixClient.mxcUrlToHttp(
|
const avatarUrl = this.context.mxcUrlToHttp(
|
||||||
this.props.member.avatarUrl,
|
this.props.member.avatarUrl,
|
||||||
36, 36, 'crop',
|
36, 36, 'crop',
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,18 +17,18 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import GroupStore from '../../../stores/GroupStore';
|
import GroupStore from '../../../stores/GroupStore';
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
module.exports = createReactClass({
|
module.exports = createReactClass({
|
||||||
displayName: 'GroupRoomInfo',
|
displayName: 'GroupRoomInfo',
|
||||||
|
|
||||||
contextTypes: {
|
statics: {
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
contextType: MatrixClientContext,
|
||||||
},
|
},
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
|
@ -206,7 +206,7 @@ module.exports = createReactClass({
|
||||||
const avatarUrl = this.state.groupRoom.avatarUrl;
|
const avatarUrl = this.state.groupRoom.avatarUrl;
|
||||||
let avatarElement;
|
let avatarElement;
|
||||||
if (avatarUrl) {
|
if (avatarUrl) {
|
||||||
const httpUrl = this.context.matrixClient.mxcUrlToHttp(avatarUrl, 800, 800);
|
const httpUrl = this.context.mxcUrlToHttp(avatarUrl, 800, 800);
|
||||||
avatarElement = (<div className="mx_MemberInfo_avatar">
|
avatarElement = (<div className="mx_MemberInfo_avatar">
|
||||||
<img src={httpUrl} />
|
<img src={httpUrl} />
|
||||||
</div>);
|
</div>);
|
||||||
|
|
|
@ -17,10 +17,10 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import {MatrixClient} from 'matrix-js-sdk';
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import { GroupRoomType } from '../../../groups';
|
import { GroupRoomType } from '../../../groups';
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
const GroupRoomTile = createReactClass({
|
const GroupRoomTile = createReactClass({
|
||||||
displayName: 'GroupRoomTile',
|
displayName: 'GroupRoomTile',
|
||||||
|
@ -41,7 +41,7 @@ const GroupRoomTile = createReactClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
const avatarUrl = this.context.matrixClient.mxcUrlToHttp(
|
const avatarUrl = this.context.mxcUrlToHttp(
|
||||||
this.props.groupRoom.avatarUrl,
|
this.props.groupRoom.avatarUrl,
|
||||||
36, 36, 'crop',
|
36, 36, 'crop',
|
||||||
);
|
);
|
||||||
|
@ -66,9 +66,7 @@ const GroupRoomTile = createReactClass({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
GroupRoomTile.contextTypes = {
|
GroupRoomTile.contextType = MatrixClientContext;
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export default GroupRoomTile;
|
export default GroupRoomTile;
|
||||||
|
|
|
@ -17,11 +17,11 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import {MatrixClient} from 'matrix-js-sdk';
|
|
||||||
import { Draggable, Droppable } from 'react-beautiful-dnd';
|
import { Draggable, Droppable } from 'react-beautiful-dnd';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import FlairStore from '../../../stores/FlairStore';
|
import FlairStore from '../../../stores/FlairStore';
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
function nop() {}
|
function nop() {}
|
||||||
|
|
||||||
|
@ -37,8 +37,8 @@ const GroupTile = createReactClass({
|
||||||
draggable: PropTypes.bool,
|
draggable: PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
contextTypes: {
|
statics: {
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
contextType: MatrixClientContext,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
|
@ -56,7 +56,7 @@ const GroupTile = createReactClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
FlairStore.getGroupProfileCached(this.context.matrixClient, this.props.groupId).then((profile) => {
|
FlairStore.getGroupProfileCached(this.context, this.props.groupId).then((profile) => {
|
||||||
this.setState({profile});
|
this.setState({profile});
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.error('Error whilst getting cached profile for GroupTile', err);
|
console.error('Error whilst getting cached profile for GroupTile', err);
|
||||||
|
@ -80,7 +80,7 @@ const GroupTile = createReactClass({
|
||||||
const descElement = this.props.showDescription ?
|
const descElement = this.props.showDescription ?
|
||||||
<div className="mx_GroupTile_desc">{ profile.shortDescription }</div> :
|
<div className="mx_GroupTile_desc">{ profile.shortDescription }</div> :
|
||||||
<div />;
|
<div />;
|
||||||
const httpUrl = profile.avatarUrl ? this.context.matrixClient.mxcUrlToHttp(
|
const httpUrl = profile.avatarUrl ? this.context.mxcUrlToHttp(
|
||||||
profile.avatarUrl, avatarHeight, avatarHeight, "crop") : null;
|
profile.avatarUrl, avatarHeight, avatarHeight, "crop") : null;
|
||||||
|
|
||||||
let avatarElement = (
|
let avatarElement = (
|
||||||
|
|
|
@ -15,17 +15,16 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
export default createReactClass({
|
export default createReactClass({
|
||||||
displayName: 'GroupUserSettings',
|
displayName: 'GroupUserSettings',
|
||||||
|
|
||||||
contextTypes: {
|
statics: {
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
contextType: MatrixClientContext,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
|
@ -36,7 +35,7 @@ export default createReactClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
this.context.matrixClient.getJoinedGroups().then((result) => {
|
this.context.getJoinedGroups().then((result) => {
|
||||||
this.setState({groups: result.groups || [], error: null});
|
this.setState({groups: result.groups || [], error: null});
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
|
@ -26,6 +26,7 @@ import {decryptFile} from '../../../utils/DecryptFile';
|
||||||
import Tinter from '../../../Tinter';
|
import Tinter from '../../../Tinter';
|
||||||
import request from 'browser-request';
|
import request from 'browser-request';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
|
import SdkConfig from "../../../SdkConfig";
|
||||||
|
|
||||||
|
|
||||||
// A cached tinted copy of require("../../../../res/img/download.svg")
|
// A cached tinted copy of require("../../../../res/img/download.svg")
|
||||||
|
@ -214,10 +215,6 @@ module.exports = createReactClass({
|
||||||
tileShape: PropTypes.string,
|
tileShape: PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
contextTypes: {
|
|
||||||
appConfig: PropTypes.object,
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts a human readable label for the file attachment to use as
|
* Extracts a human readable label for the file attachment to use as
|
||||||
* link text.
|
* link text.
|
||||||
|
@ -360,8 +357,9 @@ module.exports = createReactClass({
|
||||||
|
|
||||||
// If the attachment is encryped then put the link inside an iframe.
|
// If the attachment is encryped then put the link inside an iframe.
|
||||||
let renderer_url = DEFAULT_CROSS_ORIGIN_RENDERER;
|
let renderer_url = DEFAULT_CROSS_ORIGIN_RENDERER;
|
||||||
if (this.context.appConfig && this.context.appConfig.cross_origin_renderer_url) {
|
const appConfig = SdkConfig.get();
|
||||||
renderer_url = this.context.appConfig.cross_origin_renderer_url;
|
if (appConfig && appConfig.cross_origin_renderer_url) {
|
||||||
|
renderer_url = appConfig.cross_origin_renderer_url;
|
||||||
}
|
}
|
||||||
renderer_url += "?origin=" + encodeURIComponent(window.location.origin);
|
renderer_url += "?origin=" + encodeURIComponent(window.location.origin);
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -18,7 +18,6 @@ limitations under the License.
|
||||||
|
|
||||||
import React, {createRef} from 'react';
|
import React, {createRef} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
|
||||||
|
|
||||||
import MFileBody from './MFileBody';
|
import MFileBody from './MFileBody';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
|
@ -26,6 +25,7 @@ import sdk from '../../../index';
|
||||||
import { decryptFile } from '../../../utils/DecryptFile';
|
import { decryptFile } from '../../../utils/DecryptFile';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
export default class MImageBody extends React.Component {
|
export default class MImageBody extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -39,9 +39,7 @@ export default class MImageBody extends React.Component {
|
||||||
maxImageHeight: PropTypes.number,
|
maxImageHeight: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
|
||||||
static contextTypes = {
|
static contextType = MatrixClientContext;
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -71,7 +69,7 @@ export default class MImageBody extends React.Component {
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.unmounted = false;
|
this.unmounted = false;
|
||||||
this.context.matrixClient.on('sync', this.onClientSync);
|
this.context.on('sync', this.onClientSync);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: factor this out and aplpy it to MVideoBody and MAudioBody too!
|
// FIXME: factor this out and aplpy it to MVideoBody and MAudioBody too!
|
||||||
|
@ -174,7 +172,7 @@ export default class MImageBody extends React.Component {
|
||||||
if (content.file !== undefined) {
|
if (content.file !== undefined) {
|
||||||
return this.state.decryptedUrl;
|
return this.state.decryptedUrl;
|
||||||
} else {
|
} else {
|
||||||
return this.context.matrixClient.mxcUrlToHttp(content.url);
|
return this.context.mxcUrlToHttp(content.url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,7 +196,7 @@ export default class MImageBody extends React.Component {
|
||||||
// special case to return clientside sender-generated thumbnails for SVGs, if any,
|
// special case to return clientside sender-generated thumbnails for SVGs, if any,
|
||||||
// given we deliberately don't thumbnail them serverside to prevent
|
// given we deliberately don't thumbnail them serverside to prevent
|
||||||
// billion lol attacks and similar
|
// billion lol attacks and similar
|
||||||
return this.context.matrixClient.mxcUrlToHttp(
|
return this.context.mxcUrlToHttp(
|
||||||
content.info.thumbnail_url,
|
content.info.thumbnail_url,
|
||||||
thumbWidth,
|
thumbWidth,
|
||||||
thumbHeight,
|
thumbHeight,
|
||||||
|
@ -221,7 +219,7 @@ export default class MImageBody extends React.Component {
|
||||||
pixelRatio === 1.0 ||
|
pixelRatio === 1.0 ||
|
||||||
(!info || !info.w || !info.h || !info.size)
|
(!info || !info.w || !info.h || !info.size)
|
||||||
) {
|
) {
|
||||||
return this.context.matrixClient.mxcUrlToHttp(content.url, thumbWidth, thumbHeight);
|
return this.context.mxcUrlToHttp(content.url, thumbWidth, thumbHeight);
|
||||||
} else {
|
} else {
|
||||||
// we should only request thumbnails if the image is bigger than 800x600
|
// we should only request thumbnails if the image is bigger than 800x600
|
||||||
// (or 1600x1200 on retina) otherwise the image in the timeline will just
|
// (or 1600x1200 on retina) otherwise the image in the timeline will just
|
||||||
|
@ -242,7 +240,7 @@ export default class MImageBody extends React.Component {
|
||||||
// image is too large physically and bytewise to clutter our timeline so
|
// image is too large physically and bytewise to clutter our timeline so
|
||||||
// we ask for a thumbnail, despite knowing that it will be max 800x600
|
// we ask for a thumbnail, despite knowing that it will be max 800x600
|
||||||
// despite us being retina (as synapse doesn't do 1600x1200 thumbs yet).
|
// despite us being retina (as synapse doesn't do 1600x1200 thumbs yet).
|
||||||
return this.context.matrixClient.mxcUrlToHttp(
|
return this.context.mxcUrlToHttp(
|
||||||
content.url,
|
content.url,
|
||||||
thumbWidth,
|
thumbWidth,
|
||||||
thumbHeight,
|
thumbHeight,
|
||||||
|
@ -251,7 +249,7 @@ export default class MImageBody extends React.Component {
|
||||||
// download the original image otherwise, so we can scale it client side
|
// download the original image otherwise, so we can scale it client side
|
||||||
// to take pixelRatio into account.
|
// to take pixelRatio into account.
|
||||||
// ( no width/height means we want the original image)
|
// ( no width/height means we want the original image)
|
||||||
return this.context.matrixClient.mxcUrlToHttp(
|
return this.context.mxcUrlToHttp(
|
||||||
content.url,
|
content.url,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -308,7 +306,7 @@ export default class MImageBody extends React.Component {
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.unmounted = true;
|
this.unmounted = true;
|
||||||
this.context.matrixClient.removeListener('sync', this.onClientSync);
|
this.context.removeListener('sync', this.onClientSync);
|
||||||
this._afterComponentWillUnmount();
|
this._afterComponentWillUnmount();
|
||||||
|
|
||||||
if (this.state.decryptedUrl) {
|
if (this.state.decryptedUrl) {
|
||||||
|
|
|
@ -25,7 +25,7 @@ import dis from '../../../dispatcher';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import {aboveLeftOf, ContextMenu, ContextMenuButton, useContextMenu} from '../../structures/ContextMenu';
|
import {aboveLeftOf, ContextMenu, ContextMenuButton, useContextMenu} from '../../structures/ContextMenu';
|
||||||
import { isContentActionable, canEditContent } from '../../../utils/EventUtils';
|
import { isContentActionable, canEditContent } from '../../../utils/EventUtils';
|
||||||
import {RoomContext} from "../../structures/RoomView";
|
import RoomContext from "../../../contexts/RoomContext";
|
||||||
|
|
||||||
const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange}) => {
|
const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange}) => {
|
||||||
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
||||||
|
@ -88,7 +88,7 @@ const ReactButton = ({mxEvent, reactions, onFocusChange}) => {
|
||||||
if (menuDisplayed) {
|
if (menuDisplayed) {
|
||||||
const buttonRect = button.current.getBoundingClientRect();
|
const buttonRect = button.current.getBoundingClientRect();
|
||||||
const ReactionPicker = sdk.getComponent('emojipicker.ReactionPicker');
|
const ReactionPicker = sdk.getComponent('emojipicker.ReactionPicker');
|
||||||
contextMenu = <ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} catchTab={false}>
|
contextMenu = <ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} managed={false}>
|
||||||
<ReactionPicker mxEvent={mxEvent} reactions={reactions} onFinished={closeMenu} />
|
<ReactionPicker mxEvent={mxEvent} reactions={reactions} onFinished={closeMenu} />
|
||||||
</ContextMenu>;
|
</ContextMenu>;
|
||||||
}
|
}
|
||||||
|
@ -117,9 +117,7 @@ export default class MessageActionBar extends React.PureComponent {
|
||||||
onFocusChange: PropTypes.func,
|
onFocusChange: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
static contextTypes = {
|
static contextType = RoomContext;
|
||||||
room: RoomContext,
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.mxEvent.on("Event.decrypted", this.onDecrypted);
|
this.props.mxEvent.on("Event.decrypted", this.onDecrypted);
|
||||||
|
@ -164,12 +162,12 @@ export default class MessageActionBar extends React.PureComponent {
|
||||||
let editButton;
|
let editButton;
|
||||||
|
|
||||||
if (isContentActionable(this.props.mxEvent)) {
|
if (isContentActionable(this.props.mxEvent)) {
|
||||||
if (this.context.room.canReact) {
|
if (this.context.canReact) {
|
||||||
reactButton = (
|
reactButton = (
|
||||||
<ReactButton mxEvent={this.props.mxEvent} reactions={this.props.reactions} onFocusChange={this.onFocusChange} />
|
<ReactButton mxEvent={this.props.mxEvent} reactions={this.props.reactions} onFocusChange={this.onFocusChange} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (this.context.room.canReply) {
|
if (this.context.canReply) {
|
||||||
replyButton = <AccessibleButton
|
replyButton = <AccessibleButton
|
||||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_replyButton"
|
className="mx_MessageActionBar_maskButton mx_MessageActionBar_replyButton"
|
||||||
title={_t("Reply")}
|
title={_t("Reply")}
|
||||||
|
|
|
@ -17,11 +17,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import {MatrixClient} from 'matrix-js-sdk';
|
|
||||||
import Flair from '../elements/Flair.js';
|
import Flair from '../elements/Flair.js';
|
||||||
import FlairStore from '../../../stores/FlairStore';
|
import FlairStore from '../../../stores/FlairStore';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import {getUserNameColorClass} from '../../../utils/FormattingUtils';
|
import {getUserNameColorClass} from '../../../utils/FormattingUtils';
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
export default createReactClass({
|
export default createReactClass({
|
||||||
displayName: 'SenderProfile',
|
displayName: 'SenderProfile',
|
||||||
|
@ -31,8 +31,8 @@ export default createReactClass({
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
},
|
},
|
||||||
|
|
||||||
contextTypes: {
|
statics: {
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
contextType: MatrixClientContext,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
|
@ -47,18 +47,18 @@ export default createReactClass({
|
||||||
this._updateRelatedGroups();
|
this._updateRelatedGroups();
|
||||||
|
|
||||||
FlairStore.getPublicisedGroupsCached(
|
FlairStore.getPublicisedGroupsCached(
|
||||||
this.context.matrixClient, this.props.mxEvent.getSender(),
|
this.context, this.props.mxEvent.getSender(),
|
||||||
).then((userGroups) => {
|
).then((userGroups) => {
|
||||||
if (this.unmounted) return;
|
if (this.unmounted) return;
|
||||||
this.setState({userGroups});
|
this.setState({userGroups});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.context.matrixClient.on('RoomState.events', this.onRoomStateEvents);
|
this.context.on('RoomState.events', this.onRoomStateEvents);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.unmounted = true;
|
this.unmounted = true;
|
||||||
this.context.matrixClient.removeListener('RoomState.events', this.onRoomStateEvents);
|
this.context.removeListener('RoomState.events', this.onRoomStateEvents);
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomStateEvents(event) {
|
onRoomStateEvents(event) {
|
||||||
|
@ -71,7 +71,7 @@ export default createReactClass({
|
||||||
|
|
||||||
_updateRelatedGroups() {
|
_updateRelatedGroups() {
|
||||||
if (this.unmounted) return;
|
if (this.unmounted) return;
|
||||||
const room = this.context.matrixClient.getRoom(this.props.mxEvent.getRoomId());
|
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
|
|
||||||
const relatedGroupsEvent = room.currentState.getStateEvents('m.room.related_groups', '');
|
const relatedGroupsEvent = room.currentState.getStateEvents('m.room.related_groups', '');
|
||||||
|
|
|
@ -17,7 +17,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {useCallback, useMemo, useState, useEffect} from 'react';
|
import React, {useCallback, useMemo, useState, useEffect, useContext} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {Group, RoomMember, User} from 'matrix-js-sdk';
|
import {Group, RoomMember, User} from 'matrix-js-sdk';
|
||||||
|
@ -37,9 +37,9 @@ import MultiInviter from "../../../utils/MultiInviter";
|
||||||
import GroupStore from "../../../stores/GroupStore";
|
import GroupStore from "../../../stores/GroupStore";
|
||||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||||
import E2EIcon from "../rooms/E2EIcon";
|
import E2EIcon from "../rooms/E2EIcon";
|
||||||
import withLegacyMatrixClient from "../../../utils/withLegacyMatrixClient";
|
|
||||||
import {useEventEmitter} from "../../../hooks/useEventEmitter";
|
import {useEventEmitter} from "../../../hooks/useEventEmitter";
|
||||||
import {textualPowerLevel} from '../../../Roles';
|
import {textualPowerLevel} from '../../../Roles';
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
const _disambiguateDevices = (devices) => {
|
const _disambiguateDevices = (devices) => {
|
||||||
const names = Object.create(null);
|
const names = Object.create(null);
|
||||||
|
@ -74,17 +74,6 @@ const _getE2EStatus = (cli, userId, devices) => {
|
||||||
return "warning";
|
return "warning";
|
||||||
};
|
};
|
||||||
|
|
||||||
async function unverifyUser(matrixClient, userId) {
|
|
||||||
const devices = await matrixClient.getStoredDevicesForUser(userId);
|
|
||||||
for (const device of devices) {
|
|
||||||
if (device.isVerified()) {
|
|
||||||
matrixClient.setDeviceVerified(
|
|
||||||
userId, device.deviceId, false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function openDMForUser(matrixClient, userId) {
|
function openDMForUser(matrixClient, userId) {
|
||||||
const dmRooms = DMRoomMap.shared().getDMRoomsForUserId(userId);
|
const dmRooms = DMRoomMap.shared().getDMRoomsForUserId(userId);
|
||||||
const lastActiveRoom = dmRooms.reduce((lastActiveRoom, roomId) => {
|
const lastActiveRoom = dmRooms.reduce((lastActiveRoom, roomId) => {
|
||||||
|
@ -129,17 +118,20 @@ function verifyDevice(userId, device) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function DeviceItem({userId, device}) {
|
function DeviceItem({userId, device}) {
|
||||||
|
const cli = useContext(MatrixClientContext);
|
||||||
|
const deviceTrust = cli.checkDeviceTrust(userId, device.deviceId);
|
||||||
|
|
||||||
const classes = classNames("mx_UserInfo_device", {
|
const classes = classNames("mx_UserInfo_device", {
|
||||||
mx_UserInfo_device_verified: device.isVerified(),
|
mx_UserInfo_device_verified: deviceTrust.isVerified(),
|
||||||
mx_UserInfo_device_unverified: !device.isVerified(),
|
mx_UserInfo_device_unverified: !deviceTrust.isVerified(),
|
||||||
});
|
});
|
||||||
const iconClasses = classNames("mx_E2EIcon", {
|
const iconClasses = classNames("mx_E2EIcon", {
|
||||||
mx_E2EIcon_verified: device.isVerified(),
|
mx_E2EIcon_verified: deviceTrust.isVerified(),
|
||||||
mx_E2EIcon_warning: !device.isVerified(),
|
mx_E2EIcon_warning: !deviceTrust.isVerified(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const onDeviceClick = () => {
|
const onDeviceClick = () => {
|
||||||
if (!device.isVerified()) {
|
if (!deviceTrust.isVerified()) {
|
||||||
verifyDevice(userId, device);
|
verifyDevice(userId, device);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -147,7 +139,7 @@ function DeviceItem({userId, device}) {
|
||||||
const deviceName = device.ambiguous ?
|
const deviceName = device.ambiguous ?
|
||||||
(device.getDisplayName() ? device.getDisplayName() : "") + " (" + device.deviceId + ")" :
|
(device.getDisplayName() ? device.getDisplayName() : "") + " (" + device.deviceId + ")" :
|
||||||
device.getDisplayName();
|
device.getDisplayName();
|
||||||
const trustedLabel = device.isVerified() ? _t("Trusted") : _t("Not trusted");
|
const trustedLabel = deviceTrust.isVerified() ? _t("Trusted") : _t("Not trusted");
|
||||||
return (<AccessibleButton className={classes} onClick={onDeviceClick}>
|
return (<AccessibleButton className={classes} onClick={onDeviceClick}>
|
||||||
<div className={iconClasses} />
|
<div className={iconClasses} />
|
||||||
<div className="mx_UserInfo_device_name">{deviceName}</div>
|
<div className="mx_UserInfo_device_name">{deviceName}</div>
|
||||||
|
@ -157,6 +149,7 @@ function DeviceItem({userId, device}) {
|
||||||
|
|
||||||
function DevicesSection({devices, userId, loading}) {
|
function DevicesSection({devices, userId, loading}) {
|
||||||
const Spinner = sdk.getComponent("elements.Spinner");
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
|
const cli = useContext(MatrixClientContext);
|
||||||
|
|
||||||
const [isExpanded, setExpanded] = useState(false);
|
const [isExpanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
|
@ -167,9 +160,21 @@ function DevicesSection({devices, userId, loading}) {
|
||||||
if (devices === null) {
|
if (devices === null) {
|
||||||
return _t("Unable to load device list");
|
return _t("Unable to load device list");
|
||||||
}
|
}
|
||||||
|
const deviceTrusts = devices.map(d => cli.checkDeviceTrust(userId, d.deviceId));
|
||||||
|
|
||||||
const unverifiedDevices = devices.filter(d => !d.isVerified());
|
const unverifiedDevices = [];
|
||||||
const verifiedDevices = devices.filter(d => d.isVerified());
|
const verifiedDevices = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < devices.length; ++i) {
|
||||||
|
const device = devices[i];
|
||||||
|
const deviceTrust = deviceTrusts[i];
|
||||||
|
|
||||||
|
if (deviceTrust.isVerified()) {
|
||||||
|
verifiedDevices.push(device);
|
||||||
|
} else {
|
||||||
|
unverifiedDevices.push(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let expandButton;
|
let expandButton;
|
||||||
if (verifiedDevices.length) {
|
if (verifiedDevices.length) {
|
||||||
|
@ -203,7 +208,9 @@ function DevicesSection({devices, userId, loading}) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserOptionsSection = withLegacyMatrixClient(({matrixClient: cli, member, isIgnored, canInvite, devices}) => {
|
const UserOptionsSection = ({member, isIgnored, canInvite, devices}) => {
|
||||||
|
const cli = useContext(MatrixClientContext);
|
||||||
|
|
||||||
let ignoreButton = null;
|
let ignoreButton = null;
|
||||||
let insertPillButton = null;
|
let insertPillButton = null;
|
||||||
let inviteUserButton = null;
|
let inviteUserButton = null;
|
||||||
|
@ -313,14 +320,6 @@ const UserOptionsSection = withLegacyMatrixClient(({matrixClient: cli, member, i
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let unverifyButton;
|
|
||||||
if (devices && devices.some(device => device.isVerified())) {
|
|
||||||
unverifyButton = (
|
|
||||||
<AccessibleButton onClick={() => unverifyUser(cli, member.userId)} className="mx_UserInfo_field mx_UserInfo_destructive">
|
|
||||||
{ _t('Unverify user') }
|
|
||||||
</AccessibleButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_UserInfo_container">
|
<div className="mx_UserInfo_container">
|
||||||
|
@ -332,11 +331,10 @@ const UserOptionsSection = withLegacyMatrixClient(({matrixClient: cli, member, i
|
||||||
{ insertPillButton }
|
{ insertPillButton }
|
||||||
{ inviteUserButton }
|
{ inviteUserButton }
|
||||||
{ ignoreButton }
|
{ ignoreButton }
|
||||||
{ unverifyButton }
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
};
|
||||||
|
|
||||||
const _warnSelfDemote = async () => {
|
const _warnSelfDemote = async () => {
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
|
@ -404,7 +402,12 @@ const useRoomPowerLevels = (cli, room) => {
|
||||||
return powerLevels;
|
return powerLevels;
|
||||||
};
|
};
|
||||||
|
|
||||||
const RoomKickButton = withLegacyMatrixClient(({matrixClient: cli, member, startUpdating, stopUpdating}) => {
|
const RoomKickButton = ({member, startUpdating, stopUpdating}) => {
|
||||||
|
const cli = useContext(MatrixClientContext);
|
||||||
|
|
||||||
|
// check if user can be kicked/disinvited
|
||||||
|
if (member.membership !== "invite" && member.membership !== "join") return null;
|
||||||
|
|
||||||
const onKick = async () => {
|
const onKick = async () => {
|
||||||
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
||||||
const {finished} = Modal.createTrackedDialog(
|
const {finished} = Modal.createTrackedDialog(
|
||||||
|
@ -444,9 +447,11 @@ const RoomKickButton = withLegacyMatrixClient(({matrixClient: cli, member, start
|
||||||
return <AccessibleButton className="mx_UserInfo_field mx_UserInfo_destructive" onClick={onKick}>
|
return <AccessibleButton className="mx_UserInfo_field mx_UserInfo_destructive" onClick={onKick}>
|
||||||
{ kickLabel }
|
{ kickLabel }
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const RedactMessagesButton = ({member}) => {
|
||||||
|
const cli = useContext(MatrixClientContext);
|
||||||
|
|
||||||
const RedactMessagesButton = withLegacyMatrixClient(({matrixClient: cli, member}) => {
|
|
||||||
const onRedactAllMessages = async () => {
|
const onRedactAllMessages = async () => {
|
||||||
const {roomId, userId} = member;
|
const {roomId, userId} = member;
|
||||||
const room = cli.getRoom(roomId);
|
const room = cli.getRoom(roomId);
|
||||||
|
@ -517,9 +522,11 @@ const RedactMessagesButton = withLegacyMatrixClient(({matrixClient: cli, member}
|
||||||
return <AccessibleButton className="mx_UserInfo_field mx_UserInfo_destructive" onClick={onRedactAllMessages}>
|
return <AccessibleButton className="mx_UserInfo_field mx_UserInfo_destructive" onClick={onRedactAllMessages}>
|
||||||
{ _t("Remove recent messages") }
|
{ _t("Remove recent messages") }
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const BanToggleButton = ({member, startUpdating, stopUpdating}) => {
|
||||||
|
const cli = useContext(MatrixClientContext);
|
||||||
|
|
||||||
const BanToggleButton = withLegacyMatrixClient(({matrixClient: cli, member, startUpdating, stopUpdating}) => {
|
|
||||||
const onBanOrUnban = async () => {
|
const onBanOrUnban = async () => {
|
||||||
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
||||||
const {finished} = Modal.createTrackedDialog(
|
const {finished} = Modal.createTrackedDialog(
|
||||||
|
@ -573,207 +580,209 @@ const BanToggleButton = withLegacyMatrixClient(({matrixClient: cli, member, star
|
||||||
return <AccessibleButton className={classes} onClick={onBanOrUnban}>
|
return <AccessibleButton className={classes} onClick={onBanOrUnban}>
|
||||||
{ label }
|
{ label }
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
});
|
};
|
||||||
|
|
||||||
const MuteToggleButton = withLegacyMatrixClient(
|
const MuteToggleButton = ({member, room, powerLevels, startUpdating, stopUpdating}) => {
|
||||||
({matrixClient: cli, member, room, powerLevels, startUpdating, stopUpdating}) => {
|
const cli = useContext(MatrixClientContext);
|
||||||
const isMuted = _isMuted(member, powerLevels);
|
|
||||||
const onMuteToggle = async () => {
|
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
const roomId = member.roomId;
|
|
||||||
const target = member.userId;
|
|
||||||
|
|
||||||
// if muting self, warn as it may be irreversible
|
// Don't show the mute/unmute option if the user is not in the room
|
||||||
if (target === cli.getUserId()) {
|
if (member.membership !== "join") return null;
|
||||||
try {
|
|
||||||
if (!(await _warnSelfDemote())) return;
|
const isMuted = _isMuted(member, powerLevels);
|
||||||
} catch (e) {
|
const onMuteToggle = async () => {
|
||||||
console.error("Failed to warn about self demotion: ", e);
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
return;
|
const roomId = member.roomId;
|
||||||
}
|
const target = member.userId;
|
||||||
|
|
||||||
|
// if muting self, warn as it may be irreversible
|
||||||
|
if (target === cli.getUserId()) {
|
||||||
|
try {
|
||||||
|
if (!(await _warnSelfDemote())) return;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to warn about self demotion: ", e);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
|
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
|
||||||
if (!powerLevelEvent) return;
|
if (!powerLevelEvent) return;
|
||||||
|
|
||||||
const powerLevels = powerLevelEvent.getContent();
|
const powerLevels = powerLevelEvent.getContent();
|
||||||
const levelToSend = (
|
const levelToSend = (
|
||||||
(powerLevels.events ? powerLevels.events["m.room.message"] : null) ||
|
(powerLevels.events ? powerLevels.events["m.room.message"] : null) ||
|
||||||
powerLevels.events_default
|
powerLevels.events_default
|
||||||
);
|
);
|
||||||
let level;
|
let level;
|
||||||
if (isMuted) { // unmute
|
if (isMuted) { // unmute
|
||||||
level = levelToSend;
|
level = levelToSend;
|
||||||
} else { // mute
|
} else { // mute
|
||||||
level = levelToSend - 1;
|
level = levelToSend - 1;
|
||||||
}
|
}
|
||||||
level = parseInt(level);
|
level = parseInt(level);
|
||||||
|
|
||||||
if (!isNaN(level)) {
|
if (!isNaN(level)) {
|
||||||
startUpdating();
|
startUpdating();
|
||||||
cli.setPowerLevel(roomId, target, level, powerLevelEvent).then(() => {
|
cli.setPowerLevel(roomId, target, level, powerLevelEvent).then(() => {
|
||||||
// NO-OP; rely on the m.room.member event coming down else we could
|
// NO-OP; rely on the m.room.member event coming down else we could
|
||||||
// get out of sync if we force setState here!
|
// get out of sync if we force setState here!
|
||||||
console.log("Mute toggle success");
|
console.log("Mute toggle success");
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
console.error("Mute error: " + err);
|
console.error("Mute error: " + err);
|
||||||
Modal.createTrackedDialog('Failed to mute user', '', ErrorDialog, {
|
Modal.createTrackedDialog('Failed to mute user', '', ErrorDialog, {
|
||||||
title: _t("Error"),
|
title: _t("Error"),
|
||||||
description: _t("Failed to mute user"),
|
description: _t("Failed to mute user"),
|
||||||
});
|
|
||||||
}).finally(() => {
|
|
||||||
stopUpdating();
|
|
||||||
});
|
});
|
||||||
}
|
}).finally(() => {
|
||||||
|
stopUpdating();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const classes = classNames("mx_UserInfo_field", {
|
||||||
|
mx_UserInfo_destructive: !isMuted,
|
||||||
|
});
|
||||||
|
|
||||||
|
const muteLabel = isMuted ? _t("Unmute") : _t("Mute");
|
||||||
|
return <AccessibleButton className={classes} onClick={onMuteToggle}>
|
||||||
|
{ muteLabel }
|
||||||
|
</AccessibleButton>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const RoomAdminToolsContainer = ({room, children, member, startUpdating, stopUpdating, powerLevels}) => {
|
||||||
|
const cli = useContext(MatrixClientContext);
|
||||||
|
let kickButton;
|
||||||
|
let banButton;
|
||||||
|
let muteButton;
|
||||||
|
let redactButton;
|
||||||
|
|
||||||
|
const editPowerLevel = (
|
||||||
|
(powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) ||
|
||||||
|
powerLevels.state_default
|
||||||
|
);
|
||||||
|
|
||||||
|
const me = room.getMember(cli.getUserId());
|
||||||
|
const isMe = me.userId === member.userId;
|
||||||
|
const canAffectUser = member.powerLevel < me.powerLevel || isMe;
|
||||||
|
|
||||||
|
if (canAffectUser && me.powerLevel >= powerLevels.kick) {
|
||||||
|
kickButton = <RoomKickButton member={member} startUpdating={startUpdating} stopUpdating={stopUpdating} />;
|
||||||
|
}
|
||||||
|
if (me.powerLevel >= powerLevels.redact) {
|
||||||
|
redactButton = (
|
||||||
|
<RedactMessagesButton member={member} startUpdating={startUpdating} stopUpdating={stopUpdating} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (canAffectUser && me.powerLevel >= powerLevels.ban) {
|
||||||
|
banButton = <BanToggleButton member={member} startUpdating={startUpdating} stopUpdating={stopUpdating} />;
|
||||||
|
}
|
||||||
|
if (canAffectUser && me.powerLevel >= editPowerLevel) {
|
||||||
|
muteButton = (
|
||||||
|
<MuteToggleButton
|
||||||
|
member={member}
|
||||||
|
room={room}
|
||||||
|
powerLevels={powerLevels}
|
||||||
|
startUpdating={startUpdating}
|
||||||
|
stopUpdating={stopUpdating}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kickButton || banButton || muteButton || redactButton || children) {
|
||||||
|
return <GenericAdminToolsContainer>
|
||||||
|
{ muteButton }
|
||||||
|
{ kickButton }
|
||||||
|
{ banButton }
|
||||||
|
{ redactButton }
|
||||||
|
{ children }
|
||||||
|
</GenericAdminToolsContainer>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const GroupAdminToolsSection = ({children, groupId, groupMember, startUpdating, stopUpdating}) => {
|
||||||
|
const cli = useContext(MatrixClientContext);
|
||||||
|
|
||||||
|
const [isPrivileged, setIsPrivileged] = useState(false);
|
||||||
|
const [isInvited, setIsInvited] = useState(false);
|
||||||
|
|
||||||
|
// Listen to group store changes
|
||||||
|
useEffect(() => {
|
||||||
|
let unmounted = false;
|
||||||
|
|
||||||
|
const onGroupStoreUpdated = () => {
|
||||||
|
if (unmounted) return;
|
||||||
|
setIsPrivileged(GroupStore.isUserPrivileged(groupId));
|
||||||
|
setIsInvited(GroupStore.getGroupInvitedMembers(groupId).some(
|
||||||
|
(m) => m.userId === groupMember.userId,
|
||||||
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
const classes = classNames("mx_UserInfo_field", {
|
GroupStore.registerListener(groupId, onGroupStoreUpdated);
|
||||||
mx_UserInfo_destructive: !isMuted,
|
onGroupStoreUpdated();
|
||||||
});
|
// Handle unmount
|
||||||
|
return () => {
|
||||||
|
unmounted = true;
|
||||||
|
GroupStore.unregisterListener(onGroupStoreUpdated);
|
||||||
|
};
|
||||||
|
}, [groupId, groupMember.userId]);
|
||||||
|
|
||||||
const muteLabel = isMuted ? _t("Unmute") : _t("Mute");
|
if (isPrivileged) {
|
||||||
return <AccessibleButton className={classes} onClick={onMuteToggle}>
|
const _onKick = async () => {
|
||||||
{ muteLabel }
|
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
||||||
</AccessibleButton>;
|
const {finished} = Modal.createDialog(ConfirmUserActionDialog, {
|
||||||
},
|
matrixClient: cli,
|
||||||
);
|
groupMember,
|
||||||
|
action: isInvited ? _t('Disinvite') : _t('Remove from community'),
|
||||||
|
title: isInvited ? _t('Disinvite this user from community?')
|
||||||
|
: _t('Remove this user from community?'),
|
||||||
|
danger: true,
|
||||||
|
});
|
||||||
|
|
||||||
const RoomAdminToolsContainer = withLegacyMatrixClient(
|
const [proceed] = await finished;
|
||||||
({matrixClient: cli, room, children, member, startUpdating, stopUpdating, powerLevels}) => {
|
if (!proceed) return;
|
||||||
let kickButton;
|
|
||||||
let banButton;
|
|
||||||
let muteButton;
|
|
||||||
let redactButton;
|
|
||||||
|
|
||||||
const editPowerLevel = (
|
startUpdating();
|
||||||
(powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) ||
|
cli.removeUserFromGroup(groupId, groupMember.userId).then(() => {
|
||||||
powerLevels.state_default
|
// return to the user list
|
||||||
|
dis.dispatch({
|
||||||
|
action: "view_user",
|
||||||
|
member: null,
|
||||||
|
});
|
||||||
|
}).catch((e) => {
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
Modal.createTrackedDialog('Failed to remove user from group', '', ErrorDialog, {
|
||||||
|
title: _t('Error'),
|
||||||
|
description: isInvited ?
|
||||||
|
_t('Failed to withdraw invitation') :
|
||||||
|
_t('Failed to remove user from community'),
|
||||||
|
});
|
||||||
|
console.log(e);
|
||||||
|
}).finally(() => {
|
||||||
|
stopUpdating();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const kickButton = (
|
||||||
|
<AccessibleButton className="mx_UserInfo_field mx_UserInfo_destructive" onClick={_onKick}>
|
||||||
|
{ isInvited ? _t('Disinvite') : _t('Remove from community') }
|
||||||
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
|
|
||||||
const me = room.getMember(cli.getUserId());
|
// No make/revoke admin API yet
|
||||||
const isMe = me.userId === member.userId;
|
/*const opLabel = this.state.isTargetMod ? _t("Revoke Moderator") : _t("Make Moderator");
|
||||||
const canAffectUser = member.powerLevel < me.powerLevel || isMe;
|
giveModButton = <AccessibleButton className="mx_UserInfo_field" onClick={this.onModToggle}>
|
||||||
|
{giveOpLabel}
|
||||||
|
</AccessibleButton>;*/
|
||||||
|
|
||||||
if (canAffectUser && me.powerLevel >= powerLevels.kick) {
|
return <GenericAdminToolsContainer>
|
||||||
kickButton = <RoomKickButton member={member} startUpdating={startUpdating} stopUpdating={stopUpdating} />;
|
{ kickButton }
|
||||||
}
|
{ children }
|
||||||
if (me.powerLevel >= powerLevels.redact) {
|
</GenericAdminToolsContainer>;
|
||||||
redactButton = (
|
}
|
||||||
<RedactMessagesButton member={member} startUpdating={startUpdating} stopUpdating={stopUpdating} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (canAffectUser && me.powerLevel >= powerLevels.ban) {
|
|
||||||
banButton = <BanToggleButton member={member} startUpdating={startUpdating} stopUpdating={stopUpdating} />;
|
|
||||||
}
|
|
||||||
if (canAffectUser && me.powerLevel >= editPowerLevel) {
|
|
||||||
muteButton = (
|
|
||||||
<MuteToggleButton
|
|
||||||
member={member}
|
|
||||||
room={room}
|
|
||||||
powerLevels={powerLevels}
|
|
||||||
startUpdating={startUpdating}
|
|
||||||
stopUpdating={stopUpdating}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (kickButton || banButton || muteButton || redactButton || children) {
|
return <div />;
|
||||||
return <GenericAdminToolsContainer>
|
};
|
||||||
{ muteButton }
|
|
||||||
{ kickButton }
|
|
||||||
{ banButton }
|
|
||||||
{ redactButton }
|
|
||||||
{ children }
|
|
||||||
</GenericAdminToolsContainer>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div />;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const GroupAdminToolsSection = withLegacyMatrixClient(
|
|
||||||
({matrixClient: cli, children, groupId, groupMember, startUpdating, stopUpdating}) => {
|
|
||||||
const [isPrivileged, setIsPrivileged] = useState(false);
|
|
||||||
const [isInvited, setIsInvited] = useState(false);
|
|
||||||
|
|
||||||
// Listen to group store changes
|
|
||||||
useEffect(() => {
|
|
||||||
let unmounted = false;
|
|
||||||
|
|
||||||
const onGroupStoreUpdated = () => {
|
|
||||||
if (unmounted) return;
|
|
||||||
setIsPrivileged(GroupStore.isUserPrivileged(groupId));
|
|
||||||
setIsInvited(GroupStore.getGroupInvitedMembers(groupId).some(
|
|
||||||
(m) => m.userId === groupMember.userId,
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
GroupStore.registerListener(groupId, onGroupStoreUpdated);
|
|
||||||
onGroupStoreUpdated();
|
|
||||||
// Handle unmount
|
|
||||||
return () => {
|
|
||||||
unmounted = true;
|
|
||||||
GroupStore.unregisterListener(onGroupStoreUpdated);
|
|
||||||
};
|
|
||||||
}, [groupId, groupMember.userId]);
|
|
||||||
|
|
||||||
if (isPrivileged) {
|
|
||||||
const _onKick = async () => {
|
|
||||||
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
|
||||||
const {finished} = Modal.createDialog(ConfirmUserActionDialog, {
|
|
||||||
matrixClient: cli,
|
|
||||||
groupMember,
|
|
||||||
action: isInvited ? _t('Disinvite') : _t('Remove from community'),
|
|
||||||
title: isInvited ? _t('Disinvite this user from community?')
|
|
||||||
: _t('Remove this user from community?'),
|
|
||||||
danger: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [proceed] = await finished;
|
|
||||||
if (!proceed) return;
|
|
||||||
|
|
||||||
startUpdating();
|
|
||||||
cli.removeUserFromGroup(groupId, groupMember.userId).then(() => {
|
|
||||||
// return to the user list
|
|
||||||
dis.dispatch({
|
|
||||||
action: "view_user",
|
|
||||||
member: null,
|
|
||||||
});
|
|
||||||
}).catch((e) => {
|
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
Modal.createTrackedDialog('Failed to remove user from group', '', ErrorDialog, {
|
|
||||||
title: _t('Error'),
|
|
||||||
description: isInvited ?
|
|
||||||
_t('Failed to withdraw invitation') :
|
|
||||||
_t('Failed to remove user from community'),
|
|
||||||
});
|
|
||||||
console.log(e);
|
|
||||||
}).finally(() => {
|
|
||||||
stopUpdating();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const kickButton = (
|
|
||||||
<AccessibleButton className="mx_UserInfo_field mx_UserInfo_destructive" onClick={_onKick}>
|
|
||||||
{ isInvited ? _t('Disinvite') : _t('Remove from community') }
|
|
||||||
</AccessibleButton>
|
|
||||||
);
|
|
||||||
|
|
||||||
// No make/revoke admin API yet
|
|
||||||
/*const opLabel = this.state.isTargetMod ? _t("Revoke Moderator") : _t("Make Moderator");
|
|
||||||
giveModButton = <AccessibleButton className="mx_UserInfo_field" onClick={this.onModToggle}>
|
|
||||||
{giveOpLabel}
|
|
||||||
</AccessibleButton>;*/
|
|
||||||
|
|
||||||
return <GenericAdminToolsContainer>
|
|
||||||
{ kickButton }
|
|
||||||
{ children }
|
|
||||||
</GenericAdminToolsContainer>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div />;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const GroupMember = PropTypes.shape({
|
const GroupMember = PropTypes.shape({
|
||||||
userId: PropTypes.string.isRequired,
|
userId: PropTypes.string.isRequired,
|
||||||
|
@ -849,7 +858,7 @@ function useRoomPermissions(cli, room, user) {
|
||||||
return roomPermissions;
|
return roomPermissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PowerLevelSection = withLegacyMatrixClient(({matrixClient: cli, user, room, roomPermissions, powerLevels}) => {
|
const PowerLevelSection = ({user, room, roomPermissions, powerLevels}) => {
|
||||||
const [isEditing, setEditing] = useState(false);
|
const [isEditing, setEditing] = useState(false);
|
||||||
if (room && user.roomId) { // is in room
|
if (room && user.roomId) { // is in room
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
|
@ -876,9 +885,11 @@ const PowerLevelSection = withLegacyMatrixClient(({matrixClient: cli, user, room
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const PowerLevelEditor = ({user, room, roomPermissions, onFinished}) => {
|
||||||
|
const cli = useContext(MatrixClientContext);
|
||||||
|
|
||||||
const PowerLevelEditor = withLegacyMatrixClient(({matrixClient: cli, user, room, roomPermissions, onFinished}) => {
|
|
||||||
const [isUpdating, setIsUpdating] = useState(false);
|
const [isUpdating, setIsUpdating] = useState(false);
|
||||||
const [selectedPowerLevel, setSelectedPowerLevel] = useState(parseInt(user.powerLevel, 10));
|
const [selectedPowerLevel, setSelectedPowerLevel] = useState(parseInt(user.powerLevel, 10));
|
||||||
const [isDirty, setIsDirty] = useState(false);
|
const [isDirty, setIsDirty] = useState(false);
|
||||||
|
@ -953,7 +964,7 @@ const PowerLevelEditor = withLegacyMatrixClient(({matrixClient: cli, user, room,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [confirmed] = await finished;
|
const [confirmed] = await finished;
|
||||||
if (confirmed) return;
|
if (!confirmed) return;
|
||||||
}
|
}
|
||||||
await _applyPowerChange(roomId, target, powerLevel, powerLevelEvent);
|
await _applyPowerChange(roomId, target, powerLevel, powerLevelEvent);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -982,10 +993,11 @@ const PowerLevelEditor = withLegacyMatrixClient(({matrixClient: cli, user, room,
|
||||||
{buttonOrSpinner}
|
{buttonOrSpinner}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const UserInfo = ({user, groupId, roomId, onClose}) => {
|
||||||
|
const cli = useContext(MatrixClientContext);
|
||||||
|
|
||||||
// cli is injected by withLegacyMatrixClient
|
|
||||||
const UserInfo = withLegacyMatrixClient(({matrixClient: cli, user, groupId, roomId, onClose}) => {
|
|
||||||
// Load room if we are given a room id and memoize it
|
// Load room if we are given a room id and memoize it
|
||||||
const room = useMemo(() => roomId ? cli.getRoom(roomId) : null, [cli, roomId]);
|
const room = useMemo(() => roomId ? cli.getRoom(roomId) : null, [cli, roomId]);
|
||||||
|
|
||||||
|
@ -1260,11 +1272,20 @@ const UserInfo = withLegacyMatrixClient(({matrixClient: cli, user, groupId, room
|
||||||
|
|
||||||
const devicesSection = isRoomEncrypted ?
|
const devicesSection = isRoomEncrypted ?
|
||||||
(<DevicesSection loading={devices === undefined} devices={devices} userId={user.userId} />) : null;
|
(<DevicesSection loading={devices === undefined} devices={devices} userId={user.userId} />) : null;
|
||||||
|
|
||||||
|
const userVerified = cli.checkUserTrust(user.userId).isVerified();
|
||||||
|
let verifyButton;
|
||||||
|
if (!userVerified) {
|
||||||
|
verifyButton = <AccessibleButton className="mx_UserInfo_verify" onClick={() => verifyDevice(user.userId, null)}>
|
||||||
|
{_t("Verify")}
|
||||||
|
</AccessibleButton>;
|
||||||
|
}
|
||||||
|
|
||||||
const securitySection = (
|
const securitySection = (
|
||||||
<div className="mx_UserInfo_container">
|
<div className="mx_UserInfo_container">
|
||||||
<h3>{ _t("Security") }</h3>
|
<h3>{ _t("Security") }</h3>
|
||||||
<p>{ text }</p>
|
<p>{ text }</p>
|
||||||
<AccessibleButton className="mx_UserInfo_verify" onClick={() => verifyDevice(user.userId, null)}>{_t("Verify")}</AccessibleButton>
|
{verifyButton}
|
||||||
{ devicesSection }
|
{ devicesSection }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1316,7 +1337,7 @@ const UserInfo = withLegacyMatrixClient(({matrixClient: cli, user, groupId, room
|
||||||
</AutoHideScrollbar>
|
</AutoHideScrollbar>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
};
|
||||||
|
|
||||||
UserInfo.propTypes = {
|
UserInfo.propTypes = {
|
||||||
user: PropTypes.oneOfType([
|
user: PropTypes.oneOfType([
|
||||||
|
|
|
@ -15,6 +15,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import EditableItemList from "../elements/EditableItemList";
|
||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||||
|
@ -22,8 +24,33 @@ const sdk = require("../../../index");
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import Field from "../elements/Field";
|
import Field from "../elements/Field";
|
||||||
import ErrorDialog from "../dialogs/ErrorDialog";
|
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||||
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
const Modal = require("../../../Modal");
|
const Modal = require("../../../Modal");
|
||||||
|
|
||||||
|
class EditableAliasesList extends EditableItemList {
|
||||||
|
_renderNewItemField() {
|
||||||
|
const RoomAliasField = sdk.getComponent('views.elements.RoomAliasField');
|
||||||
|
const onChange = (alias) => this._onNewItemChanged({target: {value: alias}});
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
onSubmit={this._onItemAdded}
|
||||||
|
autoComplete="off"
|
||||||
|
noValidate={true}
|
||||||
|
className="mx_EditableItemList_newItem"
|
||||||
|
>
|
||||||
|
<RoomAliasField
|
||||||
|
id={`mx_EditableItemList_new_${this.props.id}`}
|
||||||
|
onChange={onChange}
|
||||||
|
value={this.props.newItem || ""}
|
||||||
|
domain={this.props.domain} />
|
||||||
|
<AccessibleButton onClick={this._onItemAdded} kind="primary">
|
||||||
|
{ _t("Add") }
|
||||||
|
</AccessibleButton>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default class AliasSettings extends React.Component {
|
export default class AliasSettings extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
roomId: PropTypes.string.isRequired,
|
roomId: PropTypes.string.isRequired,
|
||||||
|
@ -47,7 +74,6 @@ export default class AliasSettings extends React.Component {
|
||||||
remoteDomains: [], // [ domain.com, foobar.com ]
|
remoteDomains: [], // [ domain.com, foobar.com ]
|
||||||
canonicalAlias: null, // #canonical:domain.com
|
canonicalAlias: null, // #canonical:domain.com
|
||||||
updatingCanonicalAlias: false,
|
updatingCanonicalAlias: false,
|
||||||
newItem: "",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const localDomain = MatrixClientPeg.get().getDomain();
|
const localDomain = MatrixClientPeg.get().getDomain();
|
||||||
|
@ -181,7 +207,6 @@ export default class AliasSettings extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const EditableItemList = sdk.getComponent("elements.EditableItemList");
|
|
||||||
const localDomain = MatrixClientPeg.get().getDomain();
|
const localDomain = MatrixClientPeg.get().getDomain();
|
||||||
|
|
||||||
let found = false;
|
let found = false;
|
||||||
|
@ -233,7 +258,7 @@ export default class AliasSettings extends React.Component {
|
||||||
return (
|
return (
|
||||||
<div className='mx_AliasSettings'>
|
<div className='mx_AliasSettings'>
|
||||||
{canonicalAliasSection}
|
{canonicalAliasSection}
|
||||||
<EditableItemList
|
<EditableAliasesList
|
||||||
id="roomAliases"
|
id="roomAliases"
|
||||||
className={"mx_RoomSettings_localAliases"}
|
className={"mx_RoomSettings_localAliases"}
|
||||||
items={this.state.domainToAliases[localDomain] || []}
|
items={this.state.domainToAliases[localDomain] || []}
|
||||||
|
@ -248,6 +273,7 @@ export default class AliasSettings extends React.Component {
|
||||||
placeholder={_t(
|
placeholder={_t(
|
||||||
'New address (e.g. #foo:%(localDomain)s)', {localDomain: localDomain},
|
'New address (e.g. #foo:%(localDomain)s)', {localDomain: localDomain},
|
||||||
)}
|
)}
|
||||||
|
domain={localDomain}
|
||||||
/>
|
/>
|
||||||
{remoteAliasesSection}
|
{remoteAliasesSection}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -16,11 +16,12 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {MatrixEvent, MatrixClient} from 'matrix-js-sdk';
|
import {MatrixEvent} from 'matrix-js-sdk';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import ErrorDialog from "../dialogs/ErrorDialog";
|
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
const GROUP_ID_REGEX = /\+\S+:\S+/;
|
const GROUP_ID_REGEX = /\+\S+:\S+/;
|
||||||
|
|
||||||
|
@ -31,9 +32,7 @@ export default class RelatedGroupSettings extends React.Component {
|
||||||
relatedGroupsEvent: PropTypes.instanceOf(MatrixEvent),
|
relatedGroupsEvent: PropTypes.instanceOf(MatrixEvent),
|
||||||
};
|
};
|
||||||
|
|
||||||
static contextTypes = {
|
static contextType = MatrixClientContext;
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
canSetRelatedGroups: false,
|
canSetRelatedGroups: false,
|
||||||
|
@ -49,7 +48,7 @@ export default class RelatedGroupSettings extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGroups(newGroupsList) {
|
updateGroups(newGroupsList) {
|
||||||
this.context.matrixClient.sendStateEvent(this.props.roomId, 'm.room.related_groups', {
|
this.context.sendStateEvent(this.props.roomId, 'm.room.related_groups', {
|
||||||
groups: newGroupsList,
|
groups: newGroupsList,
|
||||||
}, '').catch((err) => {
|
}, '').catch((err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
@ -99,7 +98,7 @@ export default class RelatedGroupSettings extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const localDomain = this.context.matrixClient.getDomain();
|
const localDomain = this.context.getDomain();
|
||||||
const EditableItemList = sdk.getComponent('elements.EditableItemList');
|
const EditableItemList = sdk.getComponent('elements.EditableItemList');
|
||||||
return <div>
|
return <div>
|
||||||
<EditableItemList
|
<EditableItemList
|
||||||
|
|
|
@ -34,11 +34,11 @@ import {parsePlainTextMessage} from '../../../editor/deserialize';
|
||||||
import {renderModel} from '../../../editor/render';
|
import {renderModel} from '../../../editor/render';
|
||||||
import {Room} from 'matrix-js-sdk';
|
import {Room} from 'matrix-js-sdk';
|
||||||
import TypingStore from "../../../stores/TypingStore";
|
import TypingStore from "../../../stores/TypingStore";
|
||||||
import EMOJIBASE from 'emojibase-data/en/compact.json';
|
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import EMOTICON_REGEX from 'emojibase-regex/emoticon';
|
import EMOTICON_REGEX from 'emojibase-regex/emoticon';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import {Key} from "../../../Keyboard";
|
import {Key} from "../../../Keyboard";
|
||||||
|
import {EMOTICON_TO_EMOJI} from "../../../emoji";
|
||||||
|
|
||||||
const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s$');
|
const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s$');
|
||||||
|
|
||||||
|
@ -80,8 +80,8 @@ export default class BasicMessageEditor extends React.Component {
|
||||||
initialCaret: PropTypes.object, // See DocumentPosition in editor/model.js
|
initialCaret: PropTypes.object, // See DocumentPosition in editor/model.js
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
autoComplete: null,
|
autoComplete: null,
|
||||||
};
|
};
|
||||||
|
@ -108,7 +108,8 @@ export default class BasicMessageEditor extends React.Component {
|
||||||
const emoticonMatch = REGEX_EMOTICON_WHITESPACE.exec(range.text);
|
const emoticonMatch = REGEX_EMOTICON_WHITESPACE.exec(range.text);
|
||||||
if (emoticonMatch) {
|
if (emoticonMatch) {
|
||||||
const query = emoticonMatch[1].toLowerCase().replace("-", "");
|
const query = emoticonMatch[1].toLowerCase().replace("-", "");
|
||||||
const data = EMOJIBASE.find(e => e.emoticon ? e.emoticon.toLowerCase() === query : false);
|
const data = EMOTICON_TO_EMOJI.get(query);
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
const {partCreator} = model;
|
const {partCreator} = model;
|
||||||
const hasPrecedingSpace = emoticonMatch[0][0] === " ";
|
const hasPrecedingSpace = emoticonMatch[0][0] === " ";
|
||||||
|
|
|
@ -26,11 +26,11 @@ import {findEditableEvent} from '../../../utils/EventUtils';
|
||||||
import {parseEvent} from '../../../editor/deserialize';
|
import {parseEvent} from '../../../editor/deserialize';
|
||||||
import {PartCreator} from '../../../editor/parts';
|
import {PartCreator} from '../../../editor/parts';
|
||||||
import EditorStateTransfer from '../../../utils/EditorStateTransfer';
|
import EditorStateTransfer from '../../../utils/EditorStateTransfer';
|
||||||
import {MatrixClient} from 'matrix-js-sdk';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {EventStatus} from 'matrix-js-sdk';
|
import {EventStatus} from 'matrix-js-sdk';
|
||||||
import BasicMessageComposer from "./BasicMessageComposer";
|
import BasicMessageComposer from "./BasicMessageComposer";
|
||||||
import {Key} from "../../../Keyboard";
|
import {Key} from "../../../Keyboard";
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
function _isReply(mxEvent) {
|
function _isReply(mxEvent) {
|
||||||
const relatesTo = mxEvent.getContent()["m.relates_to"];
|
const relatesTo = mxEvent.getContent()["m.relates_to"];
|
||||||
|
@ -105,12 +105,10 @@ export default class EditMessageComposer extends React.Component {
|
||||||
editState: PropTypes.instanceOf(EditorStateTransfer).isRequired,
|
editState: PropTypes.instanceOf(EditorStateTransfer).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
static contextTypes = {
|
static contextType = MatrixClientContext;
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context);
|
super(props);
|
||||||
this.model = null;
|
this.model = null;
|
||||||
this._editorRef = null;
|
this._editorRef = null;
|
||||||
|
|
||||||
|
@ -124,7 +122,7 @@ export default class EditMessageComposer extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
_getRoom() {
|
_getRoom() {
|
||||||
return this.context.matrixClient.getRoom(this.props.editState.getEvent().getRoomId());
|
return this.context.getRoom(this.props.editState.getEvent().getRoomId());
|
||||||
}
|
}
|
||||||
|
|
||||||
_onKeyDown = (event) => {
|
_onKeyDown = (event) => {
|
||||||
|
@ -190,7 +188,7 @@ export default class EditMessageComposer extends React.Component {
|
||||||
if (this._isContentModified(newContent)) {
|
if (this._isContentModified(newContent)) {
|
||||||
const roomId = editedEvent.getRoomId();
|
const roomId = editedEvent.getRoomId();
|
||||||
this._cancelPreviousPendingEdit();
|
this._cancelPreviousPendingEdit();
|
||||||
this.context.matrixClient.sendMessage(roomId, editContent);
|
this.context.sendMessage(roomId, editContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// close the event editing and focus composer
|
// close the event editing and focus composer
|
||||||
|
@ -205,7 +203,7 @@ export default class EditMessageComposer extends React.Component {
|
||||||
previousEdit.status === EventStatus.QUEUED ||
|
previousEdit.status === EventStatus.QUEUED ||
|
||||||
previousEdit.status === EventStatus.NOT_SENT
|
previousEdit.status === EventStatus.NOT_SENT
|
||||||
)) {
|
)) {
|
||||||
this.context.matrixClient.cancelPendingEvent(previousEdit);
|
this.context.cancelPendingEvent(previousEdit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,7 +230,7 @@ export default class EditMessageComposer extends React.Component {
|
||||||
_createEditorModel() {
|
_createEditorModel() {
|
||||||
const {editState} = this.props;
|
const {editState} = this.props;
|
||||||
const room = this._getRoom();
|
const room = this._getRoom();
|
||||||
const partCreator = new PartCreator(room, this.context.matrixClient);
|
const partCreator = new PartCreator(room, this.context);
|
||||||
let parts;
|
let parts;
|
||||||
if (editState.hasEditorState()) {
|
if (editState.hasEditorState()) {
|
||||||
// if restoring state from a previous editor,
|
// if restoring state from a previous editor,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
Copyright 2017 New Vector Ltd
|
Copyright 2017 New Vector Ltd
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
|
||||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||||
|
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -24,17 +24,17 @@ import PropTypes from 'prop-types';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
const classNames = require("classnames");
|
const classNames = require("classnames");
|
||||||
import { _t, _td } from '../../../languageHandler';
|
import { _t, _td } from '../../../languageHandler';
|
||||||
const Modal = require('../../../Modal');
|
|
||||||
|
|
||||||
const sdk = require('../../../index');
|
const sdk = require('../../../index');
|
||||||
const TextForEvent = require('../../../TextForEvent');
|
const TextForEvent = require('../../../TextForEvent');
|
||||||
|
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import {EventStatus, MatrixClient} from 'matrix-js-sdk';
|
import {EventStatus} from 'matrix-js-sdk';
|
||||||
import {formatTime} from "../../../DateUtils";
|
import {formatTime} from "../../../DateUtils";
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import {ALL_RULE_TYPES} from "../../../mjolnir/BanList";
|
import {ALL_RULE_TYPES} from "../../../mjolnir/BanList";
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
const ObjectUtils = require('../../../ObjectUtils');
|
const ObjectUtils = require('../../../ObjectUtils');
|
||||||
|
|
||||||
|
@ -222,8 +222,8 @@ module.exports = createReactClass({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
contextTypes: {
|
statics: {
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
contextType: MatrixClientContext,
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
|
@ -237,7 +237,7 @@ module.exports = createReactClass({
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
this._suppressReadReceiptAnimation = false;
|
this._suppressReadReceiptAnimation = false;
|
||||||
const client = this.context.matrixClient;
|
const client = this.context;
|
||||||
client.on("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
client.on("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||||
this.props.mxEvent.on("Event.decrypted", this._onDecrypted);
|
this.props.mxEvent.on("Event.decrypted", this._onDecrypted);
|
||||||
if (this.props.showReactions) {
|
if (this.props.showReactions) {
|
||||||
|
@ -262,7 +262,7 @@ module.exports = createReactClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
const client = this.context.matrixClient;
|
const client = this.context;
|
||||||
client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||||
this.props.mxEvent.removeListener("Event.decrypted", this._onDecrypted);
|
this.props.mxEvent.removeListener("Event.decrypted", this._onDecrypted);
|
||||||
if (this.props.showReactions) {
|
if (this.props.showReactions) {
|
||||||
|
@ -291,7 +291,7 @@ module.exports = createReactClass({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const verified = await this.context.matrixClient.isEventSenderVerified(mxEvent);
|
const verified = await this.context.isEventSenderVerified(mxEvent);
|
||||||
this.setState({
|
this.setState({
|
||||||
verified: verified,
|
verified: verified,
|
||||||
}, () => {
|
}, () => {
|
||||||
|
@ -349,11 +349,11 @@ module.exports = createReactClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
shouldHighlight: function() {
|
shouldHighlight: function() {
|
||||||
const actions = this.context.matrixClient.getPushActionsForEvent(this.props.mxEvent);
|
const actions = this.context.getPushActionsForEvent(this.props.mxEvent);
|
||||||
if (!actions || !actions.tweaks) { return false; }
|
if (!actions || !actions.tweaks) { return false; }
|
||||||
|
|
||||||
// don't show self-highlights from another of our clients
|
// don't show self-highlights from another of our clients
|
||||||
if (this.props.mxEvent.getSender() === this.context.matrixClient.credentials.userId) {
|
if (this.props.mxEvent.getSender() === this.context.credentials.userId) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,15 +442,6 @@ module.exports = createReactClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onCryptoClick: function(e) {
|
|
||||||
const event = this.props.mxEvent;
|
|
||||||
|
|
||||||
Modal.createTrackedDialogAsync('Encrypted Event Dialog', '',
|
|
||||||
import('../../../async-components/views/dialogs/EncryptedEventDialog'),
|
|
||||||
{event},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
onRequestKeysClick: function() {
|
onRequestKeysClick: function() {
|
||||||
this.setState({
|
this.setState({
|
||||||
// Indicate in the UI that the keys have been requested (this is expected to
|
// Indicate in the UI that the keys have been requested (this is expected to
|
||||||
|
@ -461,7 +452,7 @@ module.exports = createReactClass({
|
||||||
// Cancel any outgoing key request for this event and resend it. If a response
|
// Cancel any outgoing key request for this event and resend it. If a response
|
||||||
// is received for the request with the required keys, the event could be
|
// is received for the request with the required keys, the event could be
|
||||||
// decrypted successfully.
|
// decrypted successfully.
|
||||||
this.context.matrixClient.cancelAndResendEventRoomKeyRequest(this.props.mxEvent);
|
this.context.cancelAndResendEventRoomKeyRequest(this.props.mxEvent);
|
||||||
},
|
},
|
||||||
|
|
||||||
onPermalinkClicked: function(e) {
|
onPermalinkClicked: function(e) {
|
||||||
|
@ -478,11 +469,10 @@ module.exports = createReactClass({
|
||||||
|
|
||||||
_renderE2EPadlock: function() {
|
_renderE2EPadlock: function() {
|
||||||
const ev = this.props.mxEvent;
|
const ev = this.props.mxEvent;
|
||||||
const props = {onClick: this.onCryptoClick};
|
|
||||||
|
|
||||||
// event could not be decrypted
|
// event could not be decrypted
|
||||||
if (ev.getContent().msgtype === 'm.bad.encrypted') {
|
if (ev.getContent().msgtype === 'm.bad.encrypted') {
|
||||||
return <E2ePadlockUndecryptable {...props} />;
|
return <E2ePadlockUndecryptable />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// event is encrypted, display padlock corresponding to whether or not it is verified
|
// event is encrypted, display padlock corresponding to whether or not it is verified
|
||||||
|
@ -490,11 +480,11 @@ module.exports = createReactClass({
|
||||||
if (this.state.verified) {
|
if (this.state.verified) {
|
||||||
return; // no icon for verified
|
return; // no icon for verified
|
||||||
} else {
|
} else {
|
||||||
return (<E2ePadlockUnverified {...props} />);
|
return (<E2ePadlockUnverified />);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.context.matrixClient.isRoomEncrypted(ev.getRoomId())) {
|
if (this.context.isRoomEncrypted(ev.getRoomId())) {
|
||||||
// else if room is encrypted
|
// else if room is encrypted
|
||||||
// and event is being encrypted or is not_sent (Unknown Devices/Network Error)
|
// and event is being encrypted or is not_sent (Unknown Devices/Network Error)
|
||||||
if (ev.status === EventStatus.ENCRYPTING) {
|
if (ev.status === EventStatus.ENCRYPTING) {
|
||||||
|
@ -507,7 +497,7 @@ module.exports = createReactClass({
|
||||||
return; // we expect this to be unencrypted
|
return; // we expect this to be unencrypted
|
||||||
}
|
}
|
||||||
// if the event is not encrypted, but it's an e2e room, show the open padlock
|
// if the event is not encrypted, but it's an e2e room, show the open padlock
|
||||||
return <E2ePadlockUnencrypted {...props} />;
|
return <E2ePadlockUnencrypted />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// no padlock needed
|
// no padlock needed
|
||||||
|
@ -741,7 +731,7 @@ module.exports = createReactClass({
|
||||||
|
|
||||||
switch (this.props.tileShape) {
|
switch (this.props.tileShape) {
|
||||||
case 'notif': {
|
case 'notif': {
|
||||||
const room = this.context.matrixClient.getRoom(this.props.mxEvent.getRoomId());
|
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
|
||||||
return (
|
return (
|
||||||
<div className={classes}>
|
<div className={classes}>
|
||||||
<div className="mx_EventTile_roomName">
|
<div className="mx_EventTile_roomName">
|
||||||
|
@ -919,7 +909,6 @@ class E2ePadlock extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
icon: PropTypes.string.isRequired,
|
icon: PropTypes.string.isRequired,
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
onClick: PropTypes.func,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -930,10 +919,6 @@ class E2ePadlock extends React.Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick = (e) => {
|
|
||||||
if (this.props.onClick) this.props.onClick(e);
|
|
||||||
};
|
|
||||||
|
|
||||||
onHoverStart = () => {
|
onHoverStart = () => {
|
||||||
this.setState({hover: true});
|
this.setState({hover: true});
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,6 +23,8 @@ import classNames from 'classnames';
|
||||||
export default class MemberDeviceInfo extends React.Component {
|
export default class MemberDeviceInfo extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const DeviceVerifyButtons = sdk.getComponent('elements.DeviceVerifyButtons');
|
const DeviceVerifyButtons = sdk.getComponent('elements.DeviceVerifyButtons');
|
||||||
|
// XXX: These checks are not cross-signing aware but this component is only used
|
||||||
|
// from the old, pre-cross-signing memberinfopanel
|
||||||
const iconClasses = classNames({
|
const iconClasses = classNames({
|
||||||
mx_MemberDeviceInfo_icon: true,
|
mx_MemberDeviceInfo_icon: true,
|
||||||
mx_MemberDeviceInfo_icon_blacklisted: this.props.device.isBlocked(),
|
mx_MemberDeviceInfo_icon_blacklisted: this.props.device.isBlocked(),
|
||||||
|
|
|
@ -31,7 +31,6 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
|
@ -48,7 +47,7 @@ import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import E2EIcon from "./E2EIcon";
|
import E2EIcon from "./E2EIcon";
|
||||||
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||||
import {EventTimeline} from "matrix-js-sdk";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
module.exports = createReactClass({
|
module.exports = createReactClass({
|
||||||
displayName: 'MemberInfo',
|
displayName: 'MemberInfo',
|
||||||
|
@ -76,13 +75,13 @@ module.exports = createReactClass({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
contextTypes: {
|
statics: {
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
contextType: MatrixClientContext,
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
this._cancelDeviceList = null;
|
this._cancelDeviceList = null;
|
||||||
const cli = this.context.matrixClient;
|
const cli = this.context;
|
||||||
|
|
||||||
// only display the devices list if our client supports E2E
|
// only display the devices list if our client supports E2E
|
||||||
this._enableDevices = cli.isCryptoEnabled();
|
this._enableDevices = cli.isCryptoEnabled();
|
||||||
|
@ -112,7 +111,7 @@ module.exports = createReactClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
const client = this.context.matrixClient;
|
const client = this.context;
|
||||||
if (client) {
|
if (client) {
|
||||||
client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||||
client.removeListener("Room", this.onRoom);
|
client.removeListener("Room", this.onRoom);
|
||||||
|
@ -131,7 +130,7 @@ module.exports = createReactClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_checkIgnoreState: function() {
|
_checkIgnoreState: function() {
|
||||||
const isIgnoring = this.context.matrixClient.isUserIgnored(this.props.member.userId);
|
const isIgnoring = this.context.isUserIgnored(this.props.member.userId);
|
||||||
this.setState({isIgnoring: isIgnoring});
|
this.setState({isIgnoring: isIgnoring});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -163,7 +162,7 @@ module.exports = createReactClass({
|
||||||
|
|
||||||
// Promise.resolve to handle transition from static result to promise; can be removed
|
// Promise.resolve to handle transition from static result to promise; can be removed
|
||||||
// in future
|
// in future
|
||||||
Promise.resolve(this.context.matrixClient.getStoredDevicesForUser(userId)).then((devices) => {
|
Promise.resolve(this.context.getStoredDevicesForUser(userId)).then((devices) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
devices: devices,
|
devices: devices,
|
||||||
e2eStatus: this._getE2EStatus(devices),
|
e2eStatus: this._getE2EStatus(devices),
|
||||||
|
@ -197,7 +196,7 @@ module.exports = createReactClass({
|
||||||
onRoomReceipt: function(receiptEvent, room) {
|
onRoomReceipt: function(receiptEvent, room) {
|
||||||
// because if we read a notification, it will affect notification count
|
// because if we read a notification, it will affect notification count
|
||||||
// only bother updating if there's a receipt from us
|
// only bother updating if there's a receipt from us
|
||||||
if (findReadReceiptFromUserId(receiptEvent, this.context.matrixClient.credentials.userId)) {
|
if (findReadReceiptFromUserId(receiptEvent, this.context.credentials.userId)) {
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -242,7 +241,7 @@ module.exports = createReactClass({
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
this._cancelDeviceList = function() { cancelled = true; };
|
this._cancelDeviceList = function() { cancelled = true; };
|
||||||
|
|
||||||
const client = this.context.matrixClient;
|
const client = this.context;
|
||||||
const self = this;
|
const self = this;
|
||||||
client.downloadKeys([member.userId], true).then(() => {
|
client.downloadKeys([member.userId], true).then(() => {
|
||||||
return client.getStoredDevicesForUser(member.userId);
|
return client.getStoredDevicesForUser(member.userId);
|
||||||
|
@ -267,7 +266,7 @@ module.exports = createReactClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onIgnoreToggle: function() {
|
onIgnoreToggle: function() {
|
||||||
const ignoredUsers = this.context.matrixClient.getIgnoredUsers();
|
const ignoredUsers = this.context.getIgnoredUsers();
|
||||||
if (this.state.isIgnoring) {
|
if (this.state.isIgnoring) {
|
||||||
const index = ignoredUsers.indexOf(this.props.member.userId);
|
const index = ignoredUsers.indexOf(this.props.member.userId);
|
||||||
if (index !== -1) ignoredUsers.splice(index, 1);
|
if (index !== -1) ignoredUsers.splice(index, 1);
|
||||||
|
@ -275,7 +274,7 @@ module.exports = createReactClass({
|
||||||
ignoredUsers.push(this.props.member.userId);
|
ignoredUsers.push(this.props.member.userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.context.matrixClient.setIgnoredUsers(ignoredUsers).then(() => {
|
this.context.setIgnoredUsers(ignoredUsers).then(() => {
|
||||||
return this.setState({isIgnoring: !this.state.isIgnoring});
|
return this.setState({isIgnoring: !this.state.isIgnoring});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -293,7 +292,7 @@ module.exports = createReactClass({
|
||||||
if (!proceed) return;
|
if (!proceed) return;
|
||||||
|
|
||||||
this.setState({ updating: this.state.updating + 1 });
|
this.setState({ updating: this.state.updating + 1 });
|
||||||
this.context.matrixClient.kick(
|
this.context.kick(
|
||||||
this.props.member.roomId, this.props.member.userId,
|
this.props.member.roomId, this.props.member.userId,
|
||||||
reason || undefined,
|
reason || undefined,
|
||||||
).then(function() {
|
).then(function() {
|
||||||
|
@ -329,11 +328,11 @@ module.exports = createReactClass({
|
||||||
this.setState({ updating: this.state.updating + 1 });
|
this.setState({ updating: this.state.updating + 1 });
|
||||||
let promise;
|
let promise;
|
||||||
if (this.props.member.membership === 'ban') {
|
if (this.props.member.membership === 'ban') {
|
||||||
promise = this.context.matrixClient.unban(
|
promise = this.context.unban(
|
||||||
this.props.member.roomId, this.props.member.userId,
|
this.props.member.roomId, this.props.member.userId,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
promise = this.context.matrixClient.ban(
|
promise = this.context.ban(
|
||||||
this.props.member.roomId, this.props.member.userId,
|
this.props.member.roomId, this.props.member.userId,
|
||||||
reason || undefined,
|
reason || undefined,
|
||||||
);
|
);
|
||||||
|
@ -360,7 +359,7 @@ module.exports = createReactClass({
|
||||||
|
|
||||||
onRedactAllMessages: async function() {
|
onRedactAllMessages: async function() {
|
||||||
const {roomId, userId} = this.props.member;
|
const {roomId, userId} = this.props.member;
|
||||||
const room = this.context.matrixClient.getRoom(roomId);
|
const room = this.context.getRoom(roomId);
|
||||||
if (!room) {
|
if (!room) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -414,7 +413,7 @@ module.exports = createReactClass({
|
||||||
console.info(`Started redacting recent ${count} messages for ${user} in ${roomId}`);
|
console.info(`Started redacting recent ${count} messages for ${user} in ${roomId}`);
|
||||||
await Promise.all(eventsToRedact.map(async event => {
|
await Promise.all(eventsToRedact.map(async event => {
|
||||||
try {
|
try {
|
||||||
await this.context.matrixClient.redactEvent(roomId, event.getId());
|
await this.context.redactEvent(roomId, event.getId());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// log and swallow errors
|
// log and swallow errors
|
||||||
console.error("Could not redact", event.getId());
|
console.error("Could not redact", event.getId());
|
||||||
|
@ -446,11 +445,11 @@ module.exports = createReactClass({
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
const roomId = this.props.member.roomId;
|
const roomId = this.props.member.roomId;
|
||||||
const target = this.props.member.userId;
|
const target = this.props.member.userId;
|
||||||
const room = this.context.matrixClient.getRoom(roomId);
|
const room = this.context.getRoom(roomId);
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
|
|
||||||
// if muting self, warn as it may be irreversible
|
// if muting self, warn as it may be irreversible
|
||||||
if (target === this.context.matrixClient.getUserId()) {
|
if (target === this.context.getUserId()) {
|
||||||
try {
|
try {
|
||||||
if (!(await this._warnSelfDemote())) return;
|
if (!(await this._warnSelfDemote())) return;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -478,7 +477,7 @@ module.exports = createReactClass({
|
||||||
|
|
||||||
if (!isNaN(level)) {
|
if (!isNaN(level)) {
|
||||||
this.setState({ updating: this.state.updating + 1 });
|
this.setState({ updating: this.state.updating + 1 });
|
||||||
this.context.matrixClient.setPowerLevel(roomId, target, level, powerLevelEvent).then(
|
this.context.setPowerLevel(roomId, target, level, powerLevelEvent).then(
|
||||||
function() {
|
function() {
|
||||||
// NO-OP; rely on the m.room.member event coming down else we could
|
// NO-OP; rely on the m.room.member event coming down else we could
|
||||||
// get out of sync if we force setState here!
|
// get out of sync if we force setState here!
|
||||||
|
@ -500,13 +499,13 @@ module.exports = createReactClass({
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
const roomId = this.props.member.roomId;
|
const roomId = this.props.member.roomId;
|
||||||
const target = this.props.member.userId;
|
const target = this.props.member.userId;
|
||||||
const room = this.context.matrixClient.getRoom(roomId);
|
const room = this.context.getRoom(roomId);
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
|
|
||||||
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
|
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
|
||||||
if (!powerLevelEvent) return;
|
if (!powerLevelEvent) return;
|
||||||
|
|
||||||
const me = room.getMember(this.context.matrixClient.credentials.userId);
|
const me = room.getMember(this.context.credentials.userId);
|
||||||
if (!me) return;
|
if (!me) return;
|
||||||
|
|
||||||
const defaultLevel = powerLevelEvent.getContent().users_default;
|
const defaultLevel = powerLevelEvent.getContent().users_default;
|
||||||
|
@ -515,7 +514,7 @@ module.exports = createReactClass({
|
||||||
// toggle the level
|
// toggle the level
|
||||||
const newLevel = this.state.isTargetMod ? defaultLevel : modLevel;
|
const newLevel = this.state.isTargetMod ? defaultLevel : modLevel;
|
||||||
this.setState({ updating: this.state.updating + 1 });
|
this.setState({ updating: this.state.updating + 1 });
|
||||||
this.context.matrixClient.setPowerLevel(roomId, target, parseInt(newLevel), powerLevelEvent).then(
|
this.context.setPowerLevel(roomId, target, parseInt(newLevel), powerLevelEvent).then(
|
||||||
function() {
|
function() {
|
||||||
// NO-OP; rely on the m.room.member event coming down else we could
|
// NO-OP; rely on the m.room.member event coming down else we could
|
||||||
// get out of sync if we force setState here!
|
// get out of sync if we force setState here!
|
||||||
|
@ -550,7 +549,7 @@ module.exports = createReactClass({
|
||||||
danger: true,
|
danger: true,
|
||||||
onFinished: (accepted) => {
|
onFinished: (accepted) => {
|
||||||
if (!accepted) return;
|
if (!accepted) return;
|
||||||
this.context.matrixClient.deactivateSynapseUser(this.props.member.userId).catch(e => {
|
this.context.deactivateSynapseUser(this.props.member.userId).catch(e => {
|
||||||
console.error("Failed to deactivate user");
|
console.error("Failed to deactivate user");
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
||||||
|
@ -566,7 +565,7 @@ module.exports = createReactClass({
|
||||||
|
|
||||||
_applyPowerChange: function(roomId, target, powerLevel, powerLevelEvent) {
|
_applyPowerChange: function(roomId, target, powerLevel, powerLevelEvent) {
|
||||||
this.setState({ updating: this.state.updating + 1 });
|
this.setState({ updating: this.state.updating + 1 });
|
||||||
this.context.matrixClient.setPowerLevel(roomId, target, parseInt(powerLevel), powerLevelEvent).then(
|
this.context.setPowerLevel(roomId, target, parseInt(powerLevel), powerLevelEvent).then(
|
||||||
function() {
|
function() {
|
||||||
// NO-OP; rely on the m.room.member event coming down else we could
|
// NO-OP; rely on the m.room.member event coming down else we could
|
||||||
// get out of sync if we force setState here!
|
// get out of sync if we force setState here!
|
||||||
|
@ -587,7 +586,7 @@ module.exports = createReactClass({
|
||||||
onPowerChange: async function(powerLevel) {
|
onPowerChange: async function(powerLevel) {
|
||||||
const roomId = this.props.member.roomId;
|
const roomId = this.props.member.roomId;
|
||||||
const target = this.props.member.userId;
|
const target = this.props.member.userId;
|
||||||
const room = this.context.matrixClient.getRoom(roomId);
|
const room = this.context.getRoom(roomId);
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
|
|
||||||
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
|
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
|
||||||
|
@ -598,7 +597,7 @@ module.exports = createReactClass({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const myUserId = this.context.matrixClient.getUserId();
|
const myUserId = this.context.getUserId();
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
|
|
||||||
// If we are changing our own PL it can only ever be decreasing, which we cannot reverse.
|
// If we are changing our own PL it can only ever be decreasing, which we cannot reverse.
|
||||||
|
@ -650,9 +649,9 @@ module.exports = createReactClass({
|
||||||
|
|
||||||
_calculateOpsPermissions: async function(member) {
|
_calculateOpsPermissions: async function(member) {
|
||||||
let canDeactivate = false;
|
let canDeactivate = false;
|
||||||
if (this.context.matrixClient) {
|
if (this.context) {
|
||||||
try {
|
try {
|
||||||
canDeactivate = await this.context.matrixClient.isSynapseAdministrator();
|
canDeactivate = await this.context.isSynapseAdministrator();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
@ -665,13 +664,13 @@ module.exports = createReactClass({
|
||||||
},
|
},
|
||||||
muted: false,
|
muted: false,
|
||||||
};
|
};
|
||||||
const room = this.context.matrixClient.getRoom(member.roomId);
|
const room = this.context.getRoom(member.roomId);
|
||||||
if (!room) return defaultPerms;
|
if (!room) return defaultPerms;
|
||||||
|
|
||||||
const powerLevels = room.currentState.getStateEvents("m.room.power_levels", "");
|
const powerLevels = room.currentState.getStateEvents("m.room.power_levels", "");
|
||||||
if (!powerLevels) return defaultPerms;
|
if (!powerLevels) return defaultPerms;
|
||||||
|
|
||||||
const me = room.getMember(this.context.matrixClient.credentials.userId);
|
const me = room.getMember(this.context.credentials.userId);
|
||||||
if (!me) return defaultPerms;
|
if (!me) return defaultPerms;
|
||||||
|
|
||||||
const them = member;
|
const them = member;
|
||||||
|
@ -738,7 +737,7 @@ module.exports = createReactClass({
|
||||||
const avatarUrl = member.getMxcAvatarUrl();
|
const avatarUrl = member.getMxcAvatarUrl();
|
||||||
if (!avatarUrl) return;
|
if (!avatarUrl) return;
|
||||||
|
|
||||||
const httpUrl = this.context.matrixClient.mxcUrlToHttp(avatarUrl);
|
const httpUrl = this.context.mxcUrlToHttp(avatarUrl);
|
||||||
const ImageView = sdk.getComponent("elements.ImageView");
|
const ImageView = sdk.getComponent("elements.ImageView");
|
||||||
const params = {
|
const params = {
|
||||||
src: httpUrl,
|
src: httpUrl,
|
||||||
|
@ -797,7 +796,7 @@ module.exports = createReactClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderUserOptions: function() {
|
_renderUserOptions: function() {
|
||||||
const cli = this.context.matrixClient;
|
const cli = this.context;
|
||||||
const member = this.props.member;
|
const member = this.props.member;
|
||||||
|
|
||||||
let ignoreButton = null;
|
let ignoreButton = null;
|
||||||
|
@ -905,9 +904,9 @@ module.exports = createReactClass({
|
||||||
let synapseDeactivateButton;
|
let synapseDeactivateButton;
|
||||||
let spinner;
|
let spinner;
|
||||||
|
|
||||||
if (this.props.member.userId !== this.context.matrixClient.credentials.userId) {
|
if (this.props.member.userId !== this.context.credentials.userId) {
|
||||||
// TODO: Immutable DMs replaces a lot of this
|
// TODO: Immutable DMs replaces a lot of this
|
||||||
const dmRoomMap = new DMRoomMap(this.context.matrixClient);
|
const dmRoomMap = new DMRoomMap(this.context);
|
||||||
// dmRooms will not include dmRooms that we have been invited into but did not join.
|
// dmRooms will not include dmRooms that we have been invited into but did not join.
|
||||||
// Because DMRoomMap runs off account_data[m.direct] which is only set on join of dm room.
|
// Because DMRoomMap runs off account_data[m.direct] which is only set on join of dm room.
|
||||||
// XXX: we potentially want DMs we have been invited to, to also show up here :L
|
// XXX: we potentially want DMs we have been invited to, to also show up here :L
|
||||||
|
@ -918,7 +917,7 @@ module.exports = createReactClass({
|
||||||
|
|
||||||
const tiles = [];
|
const tiles = [];
|
||||||
for (const roomId of dmRooms) {
|
for (const roomId of dmRooms) {
|
||||||
const room = this.context.matrixClient.getRoom(roomId);
|
const room = this.context.getRoom(roomId);
|
||||||
if (room) {
|
if (room) {
|
||||||
const myMembership = room.getMyMembership();
|
const myMembership = room.getMyMembership();
|
||||||
// not a DM room if we have are not joined
|
// not a DM room if we have are not joined
|
||||||
|
@ -1064,12 +1063,12 @@ module.exports = createReactClass({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const room = this.context.matrixClient.getRoom(this.props.member.roomId);
|
const room = this.context.getRoom(this.props.member.roomId);
|
||||||
const powerLevelEvent = room ? room.currentState.getStateEvents("m.room.power_levels", "") : null;
|
const powerLevelEvent = room ? room.currentState.getStateEvents("m.room.power_levels", "") : null;
|
||||||
const powerLevelUsersDefault = powerLevelEvent ? powerLevelEvent.getContent().users_default : 0;
|
const powerLevelUsersDefault = powerLevelEvent ? powerLevelEvent.getContent().users_default : 0;
|
||||||
|
|
||||||
const enablePresenceByHsUrl = SdkConfig.get()["enable_presence_by_hs_url"];
|
const enablePresenceByHsUrl = SdkConfig.get()["enable_presence_by_hs_url"];
|
||||||
const hsUrl = this.context.matrixClient.baseUrl;
|
const hsUrl = this.context.baseUrl;
|
||||||
let showPresence = true;
|
let showPresence = true;
|
||||||
if (enablePresenceByHsUrl && enablePresenceByHsUrl[hsUrl] !== undefined) {
|
if (enablePresenceByHsUrl && enablePresenceByHsUrl[hsUrl] !== undefined) {
|
||||||
showPresence = enablePresenceByHsUrl[hsUrl];
|
showPresence = enablePresenceByHsUrl[hsUrl];
|
||||||
|
@ -1108,7 +1107,7 @@ module.exports = createReactClass({
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
||||||
const isEncrypted = this.context.matrixClient.isRoomEncrypted(this.props.member.roomId);
|
const isEncrypted = this.context.isRoomEncrypted(this.props.member.roomId);
|
||||||
if (this.state.e2eStatus && isEncrypted) {
|
if (this.state.e2eStatus && isEncrypted) {
|
||||||
e2eIconElement = (<E2EIcon status={this.state.e2eStatus} isUser={true} />);
|
e2eIconElement = (<E2EIcon status={this.state.e2eStatus} isUser={true} />);
|
||||||
}
|
}
|
||||||
|
@ -1117,7 +1116,7 @@ module.exports = createReactClass({
|
||||||
const avatarUrl = this.props.member.getMxcAvatarUrl();
|
const avatarUrl = this.props.member.getMxcAvatarUrl();
|
||||||
let avatarElement;
|
let avatarElement;
|
||||||
if (avatarUrl) {
|
if (avatarUrl) {
|
||||||
const httpUrl = this.context.matrixClient.mxcUrlToHttp(avatarUrl, 800, 800);
|
const httpUrl = this.context.mxcUrlToHttp(avatarUrl, 800, 800);
|
||||||
avatarElement = <div className="mx_MemberInfo_avatar">
|
avatarElement = <div className="mx_MemberInfo_avatar">
|
||||||
<img src={httpUrl} />
|
<img src={httpUrl} />
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -32,6 +32,10 @@ const INITIAL_LOAD_NUM_MEMBERS = 30;
|
||||||
const INITIAL_LOAD_NUM_INVITED = 5;
|
const INITIAL_LOAD_NUM_INVITED = 5;
|
||||||
const SHOW_MORE_INCREMENT = 100;
|
const SHOW_MORE_INCREMENT = 100;
|
||||||
|
|
||||||
|
// Regex applied to filter our punctuation in member names before applying sort, to fuzzy it a little
|
||||||
|
// matches all ASCII punctuation: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
|
||||||
|
const SORT_REGEX = /[\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]+/g;
|
||||||
|
|
||||||
module.exports = createReactClass({
|
module.exports = createReactClass({
|
||||||
displayName: 'MemberList',
|
displayName: 'MemberList',
|
||||||
|
|
||||||
|
@ -336,10 +340,13 @@ module.exports = createReactClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fourth by name (alphabetical)
|
// Fourth by name (alphabetical)
|
||||||
const nameA = memberA.name[0] === '@' ? memberA.name.substr(1) : memberA.name;
|
const nameA = (memberA.name[0] === '@' ? memberA.name.substr(1) : memberA.name).replace(SORT_REGEX, "");
|
||||||
const nameB = memberB.name[0] === '@' ? memberB.name.substr(1) : memberB.name;
|
const nameB = (memberB.name[0] === '@' ? memberB.name.substr(1) : memberB.name).replace(SORT_REGEX, "");
|
||||||
// console.log(`Comparing userA_name=${nameA} against userB_name=${nameB} - returning`);
|
// console.log(`Comparing userA_name=${nameA} against userB_name=${nameB} - returning`);
|
||||||
return nameA.localeCompare(nameB);
|
return nameA.localeCompare(nameB, {
|
||||||
|
ignorePunctuation: true,
|
||||||
|
sensitivity: "base",
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onSearchQueryChanged: function(searchQuery) {
|
onSearchQueryChanged: function(searchQuery) {
|
||||||
|
|
|
@ -107,8 +107,8 @@ class UploadButton extends React.Component {
|
||||||
roomId: PropTypes.string.isRequired,
|
roomId: PropTypes.string.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context);
|
super(props);
|
||||||
this.onUploadClick = this.onUploadClick.bind(this);
|
this.onUploadClick = this.onUploadClick.bind(this);
|
||||||
this.onUploadFileInputChange = this.onUploadFileInputChange.bind(this);
|
this.onUploadFileInputChange = this.onUploadFileInputChange.bind(this);
|
||||||
|
|
||||||
|
@ -165,8 +165,8 @@ class UploadButton extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class MessageComposer extends React.Component {
|
export default class MessageComposer extends React.Component {
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context);
|
super(props);
|
||||||
this.onInputStateChanged = this.onInputStateChanged.bind(this);
|
this.onInputStateChanged = this.onInputStateChanged.bind(this);
|
||||||
this.onEvent = this.onEvent.bind(this);
|
this.onEvent = this.onEvent.bind(this);
|
||||||
this._onRoomStateEvents = this._onRoomStateEvents.bind(this);
|
this._onRoomStateEvents = this._onRoomStateEvents.bind(this);
|
||||||
|
|
|
@ -48,7 +48,6 @@ import Markdown from '../../../Markdown';
|
||||||
import MessageComposerStore from '../../../stores/MessageComposerStore';
|
import MessageComposerStore from '../../../stores/MessageComposerStore';
|
||||||
import ContentMessages from '../../../ContentMessages';
|
import ContentMessages from '../../../ContentMessages';
|
||||||
|
|
||||||
import EMOJIBASE from 'emojibase-data/en/compact.json';
|
|
||||||
import EMOTICON_REGEX from 'emojibase-regex/emoticon';
|
import EMOTICON_REGEX from 'emojibase-regex/emoticon';
|
||||||
|
|
||||||
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
||||||
|
@ -61,6 +60,7 @@ import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import {findEditableEvent} from '../../../utils/EventUtils';
|
import {findEditableEvent} from '../../../utils/EventUtils';
|
||||||
import SlateComposerHistoryManager from "../../../SlateComposerHistoryManager";
|
import SlateComposerHistoryManager from "../../../SlateComposerHistoryManager";
|
||||||
import TypingStore from "../../../stores/TypingStore";
|
import TypingStore from "../../../stores/TypingStore";
|
||||||
|
import {EMOTICON_TO_EMOJI} from "../../../emoji";
|
||||||
|
|
||||||
const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s$');
|
const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s$');
|
||||||
|
|
||||||
|
@ -141,8 +141,8 @@ export default class MessageComposerInput extends React.Component {
|
||||||
autocomplete: Autocomplete;
|
autocomplete: Autocomplete;
|
||||||
historyManager: SlateComposerHistoryManager;
|
historyManager: SlateComposerHistoryManager;
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context);
|
super(props);
|
||||||
|
|
||||||
const isRichTextEnabled = SettingsStore.getValue('MessageComposerInput.isRichTextEnabled');
|
const isRichTextEnabled = SettingsStore.getValue('MessageComposerInput.isRichTextEnabled');
|
||||||
|
|
||||||
|
@ -464,7 +464,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
const emoticonMatch = REGEX_EMOTICON_WHITESPACE.exec(text.slice(0, currentStartOffset));
|
const emoticonMatch = REGEX_EMOTICON_WHITESPACE.exec(text.slice(0, currentStartOffset));
|
||||||
if (emoticonMatch) {
|
if (emoticonMatch) {
|
||||||
const query = emoticonMatch[1].toLowerCase().replace("-", "");
|
const query = emoticonMatch[1].toLowerCase().replace("-", "");
|
||||||
const data = EMOJIBASE.find(e => e.emoticon ? e.emoticon.toLowerCase() === query : false);
|
const data = EMOTICON_TO_EMOJI.get(query);
|
||||||
|
|
||||||
// only perform replacement if we found a match, otherwise we would be not letting user type
|
// only perform replacement if we found a match, otherwise we would be not letting user type
|
||||||
if (data) {
|
if (data) {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue