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 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:
|
||||
* Customised implementations of presentation components.
|
||||
* 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)
|
||||
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
|
||||
uses the naming prefix "mx_". A company called Yoyodyne Inc might use a
|
||||
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
|
||||
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
|
||||
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
|
||||
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",
|
||||
"build": "yarn reskindex && yarn start:init",
|
||||
"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: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",
|
||||
|
@ -75,7 +74,6 @@
|
|||
"file-saver": "^1.3.3",
|
||||
"filesize": "3.5.6",
|
||||
"flux": "2.1.1",
|
||||
"react-focus-lock": "^2.2.1",
|
||||
"focus-visible": "^5.0.2",
|
||||
"fuse.js": "^2.2.0",
|
||||
"gemini-scrollbar": "github:matrix-org/gemini-scrollbar#91e1e566",
|
||||
|
@ -83,6 +81,7 @@
|
|||
"glob": "^5.0.14",
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
"highlight.js": "^9.15.8",
|
||||
"humanize": "^0.0.9",
|
||||
"is-ip": "^2.0.0",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"linkifyjs": "^2.1.6",
|
||||
|
@ -100,6 +99,7 @@
|
|||
"react-addons-css-transition-group": "15.6.2",
|
||||
"react-beautiful-dnd": "^4.0.1",
|
||||
"react-dom": "^16.9.0",
|
||||
"react-focus-lock": "^2.2.1",
|
||||
"react-gemini-scrollbar": "github:matrix-org/react-gemini-scrollbar#9cf17f63b7c0b0ec5f31df27da0f82f7238dc594",
|
||||
"resize-observer-polyfill": "^1.5.0",
|
||||
"sanitize-html": "^1.18.4",
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
@import "./structures/_SearchBox.scss";
|
||||
@import "./structures/_TabbedView.scss";
|
||||
@import "./structures/_TagPanel.scss";
|
||||
@import "./structures/_TagPanelButtons.scss";
|
||||
@import "./structures/_ToastContainer.scss";
|
||||
@import "./structures/_TopLeftMenuButton.scss";
|
||||
@import "./structures/_UploadBar.scss";
|
||||
|
@ -57,6 +56,7 @@
|
|||
@import "./views/dialogs/_ConfirmUserActionDialog.scss";
|
||||
@import "./views/dialogs/_CreateGroupDialog.scss";
|
||||
@import "./views/dialogs/_CreateRoomDialog.scss";
|
||||
@import "./views/dialogs/_DMInviteDialog.scss";
|
||||
@import "./views/dialogs/_DeactivateAccountDialog.scss";
|
||||
@import "./views/dialogs/_DeviceVerifyDialog.scss";
|
||||
@import "./views/dialogs/_DevtoolsDialog.scss";
|
||||
|
@ -174,6 +174,7 @@
|
|||
@import "./views/rooms/_SendMessageComposer.scss";
|
||||
@import "./views/rooms/_Stickers.scss";
|
||||
@import "./views/rooms/_TopUnreadMessagesBar.scss";
|
||||
@import "./views/rooms/_UserOnlineDot.scss";
|
||||
@import "./views/rooms/_WhoIsTypingTile.scss";
|
||||
@import "./views/settings/_AvatarSetting.scss";
|
||||
@import "./views/settings/_CrossSigningPanel.scss";
|
||||
|
|
|
@ -26,11 +26,16 @@ limitations under the License.
|
|||
|
||||
.mx_CustomRoomTagPanel_scroller {
|
||||
max-height: inherit;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mx_CustomRoomTagPanel .mx_AccessibleButton {
|
||||
margin: 9px auto;
|
||||
margin: 0 auto;
|
||||
width: 40px;
|
||||
padding: 10px 0 9px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mx_CustomRoomTagPanel .mx_BaseAvatar_image {
|
||||
|
@ -39,7 +44,13 @@ limitations under the License.
|
|||
height: 40px;
|
||||
}
|
||||
|
||||
.mx_CustomRoomTagPanel .mx_AccessibleButton.CustomRoomTagPanel_tileSelected .mx_BaseAvatar_image {
|
||||
border: 3px solid $warning-color;
|
||||
border-radius: 40px;
|
||||
.mx_CustomRoomTagPanel .mx_AccessibleButton.CustomRoomTagPanel_tileSelected::before {
|
||||
content: '';
|
||||
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 {
|
||||
height: 40px;
|
||||
padding: 5px 0 4px 0;
|
||||
padding: 10px 0 9px 0;
|
||||
}
|
||||
|
||||
.mx_TagPanel .mx_TagTile {
|
||||
|
@ -82,21 +82,39 @@ limitations under the License.
|
|||
// opacity: 1;
|
||||
}
|
||||
|
||||
.mx_TagPanel .mx_TagTile.mx_TagTile_selected .mx_TagTile_avatar .mx_BaseAvatar {
|
||||
background-color: $accent-color;
|
||||
border-radius: 40px;
|
||||
|
||||
/* In case this is a "initial" avatar */
|
||||
display: block;
|
||||
.mx_TagPanel .mx_TagTile_plus {
|
||||
margin-bottom: 12px;
|
||||
height: 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 {
|
||||
border: 3px solid $accent-color;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
box-sizing: border-box;
|
||||
.mx_TagPanel .mx_TagTile.mx_TagTile_selected::before {
|
||||
content: '';
|
||||
height: 56px;
|
||||
background-color: $accent-color;
|
||||
width: 5px;
|
||||
position: absolute;
|
||||
left: -15px;
|
||||
border-radius: 0 3px 3px 0;
|
||||
top: -8px; // (56 - 40)/2
|
||||
}
|
||||
|
||||
.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');
|
||||
}
|
||||
|
||||
.mx_TopLeftMenu_icon_help::after {
|
||||
mask-image: url('$(res)/img/feather-customised/life-buoy.svg');
|
||||
}
|
||||
|
||||
.mx_TopLeftMenu_icon_settings::after {
|
||||
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');
|
||||
}
|
||||
|
||||
.mx_RoomSettingsDialog_bridgesIcon::before {
|
||||
// This icon is pants, please improve :)
|
||||
mask-image: url('$(res)/img/feather-customised/bridge.svg');
|
||||
}
|
||||
|
||||
.mx_RoomSettingsDialog_warningIcon::before {
|
||||
mask-image: url('$(res)/img/feather-customised/warning-triangle.svg');
|
||||
}
|
||||
|
@ -50,3 +55,17 @@ limitations under the License.
|
|||
mask-size: 36px;
|
||||
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 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.
|
||||
|
@ -353,7 +354,6 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody {
|
|||
left: 46px;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
|
|
@ -40,4 +40,5 @@ limitations under the License.
|
|||
|
||||
.mx_RoomRecoveryReminder_secondary {
|
||||
font-size: 90%;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
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)
|
||||
$primary-fg-color: $text-primary-color;
|
||||
$primary-bg-color: $bg-color;
|
||||
$muted-fg-color: $header-panel-text-primary-color;
|
||||
|
||||
// used for dialog box text
|
||||
$light-fg-color: $header-panel-text-secondary-color;
|
||||
|
@ -172,6 +173,8 @@ $interactive-tooltip-fg-color: #ffffff;
|
|||
|
||||
$breadcrumb-placeholder-bg-color: #272c35;
|
||||
|
||||
$user-tile-hover-bg-color: $header-panel-bg-color;
|
||||
|
||||
// ***** Mixins! *****
|
||||
|
||||
@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)
|
||||
$primary-fg-color: #2e2f32;
|
||||
$primary-bg-color: #ffffff;
|
||||
$muted-fg-color: #61708b; // Commonly used in headings and relevant alt text
|
||||
|
||||
// used for dialog box text
|
||||
$light-fg-color: #747474;
|
||||
|
@ -293,6 +294,8 @@ $interactive-tooltip-fg-color: #ffffff;
|
|||
|
||||
$breadcrumb-placeholder-bg-color: #e8eef5;
|
||||
|
||||
$user-tile-hover-bg-color: $header-panel-bg-color;
|
||||
|
||||
// ***** Mixins! *****
|
||||
|
||||
@define-mixin mx_DialogButton {
|
||||
|
@ -338,3 +341,12 @@ $breadcrumb-placeholder-bg-color: #e8eef5;
|
|||
color: $accent-color;
|
||||
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 static webserver to server symlinked local copy of riot
|
||||
./riot/install-webserver.sh
|
||||
mkdir logs || rm -r logs/*
|
||||
rm -r logs || true
|
||||
mkdir logs
|
||||
echo "+++ Running end-to-end tests"
|
||||
TESTS_STARTED=1
|
||||
./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");
|
||||
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) {
|
||||
const file = okFiles[i];
|
||||
if (!uploadAll) {
|
||||
|
@ -440,11 +443,11 @@ export default class ContentMessages {
|
|||
});
|
||||
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 = {
|
||||
body: file.name || 'Attachment',
|
||||
info: {
|
||||
|
@ -517,7 +520,10 @@ export default class ContentMessages {
|
|||
content.file = result.file;
|
||||
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);
|
||||
}, function(err) {
|
||||
error = err;
|
||||
|
|
|
@ -97,7 +97,7 @@ export const crossSigningCallbacks = {
|
|||
*
|
||||
* 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
|
||||
* 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
|
||||
* bootstrapped. Optional.
|
||||
|
|
|
@ -32,9 +32,9 @@ import classNames from 'classnames';
|
|||
import MatrixClientPeg from './MatrixClientPeg';
|
||||
import url from 'url';
|
||||
|
||||
import EMOJIBASE from 'emojibase-data/en/compact.json';
|
||||
import EMOJIBASE_REGEX from 'emojibase-regex';
|
||||
import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks";
|
||||
import {SHORTCODE_TO_EMOJI, getEmojiFromUnicode} from "./emoji";
|
||||
|
||||
linkifyMatrix(linkify);
|
||||
|
||||
|
@ -58,8 +58,6 @@ const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
|
|||
|
||||
const PERMITTED_URL_SCHEMES = ['http', 'https', 'ftp', 'mailto', 'magnet'];
|
||||
|
||||
const VARIATION_SELECTOR = String.fromCharCode(0xFE0F);
|
||||
|
||||
/*
|
||||
* Return true if the given string contains emoji
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
@ -93,7 +76,7 @@ export function findEmojiData(char) {
|
|||
* @return {String} The shortcode (such as :thumbup:)
|
||||
*/
|
||||
export function unicodeToShortcode(char) {
|
||||
const data = findEmojiData(char);
|
||||
const data = getEmojiFromUnicode(char);
|
||||
return (data && data.shortcodes ? `:${data.shortcodes[0]}:` : '');
|
||||
}
|
||||
|
||||
|
@ -105,7 +88,7 @@ export function unicodeToShortcode(char) {
|
|||
*/
|
||||
export function shortcodeToUnicode(shortcode) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -111,6 +111,12 @@ export default class KeyRequestHandler {
|
|||
this._currentUser = 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) {
|
||||
for (const req of this._pendingKeyRequests[userId][deviceId]) {
|
||||
req.share();
|
||||
|
|
|
@ -25,6 +25,7 @@ import sdk from './';
|
|||
import dis from './dispatcher';
|
||||
import DMRoomMap from './utils/DMRoomMap';
|
||||
import { _t } from './languageHandler';
|
||||
import SettingsStore from "./settings/SettingsStore";
|
||||
|
||||
/**
|
||||
* Invites multiple addresses to a room
|
||||
|
@ -41,6 +42,18 @@ function inviteMultipleToRoom(roomId, addrs) {
|
|||
}
|
||||
|
||||
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");
|
||||
|
||||
Modal.createTrackedDialog('Start a chat', '', AddressPickerDialog, {
|
||||
|
@ -99,7 +112,7 @@ export function isValid3pidInvite(event) {
|
|||
return true;
|
||||
}
|
||||
|
||||
// TODO: Immutable DMs replaces this
|
||||
// TODO: Canonical DMs replaces this
|
||||
function _onStartDmFinished(shouldInvite, addrs) {
|
||||
if (!shouldInvite) return;
|
||||
|
||||
|
|
|
@ -780,54 +780,52 @@ export const CommandMap = {
|
|||
const deviceId = matches[2];
|
||||
const fingerprint = matches[3];
|
||||
|
||||
return success(
|
||||
// Promise.resolve to handle transition from static result to promise; can be removed
|
||||
// in future
|
||||
Promise.resolve(cli.getStoredDevice(userId, deviceId)).then((device) => {
|
||||
if (!device) {
|
||||
throw new Error(_t('Unknown (user, device) pair:') + ` (${userId}, ${deviceId})`);
|
||||
}
|
||||
return success((async () => {
|
||||
const device = await cli.getStoredDevice(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 (device.getFingerprint() === fingerprint) {
|
||||
throw new Error(_t('Device already verified!'));
|
||||
} else {
|
||||
throw new Error(_t('WARNING: Device already verified, but keys do NOT MATCH!'));
|
||||
}
|
||||
if (deviceTrust.isVerified()) {
|
||||
if (device.getFingerprint() === fingerprint) {
|
||||
throw new Error(_t('Device already verified!'));
|
||||
} else {
|
||||
throw new Error(_t('WARNING: Device already verified, but keys do NOT MATCH!'));
|
||||
}
|
||||
}
|
||||
|
||||
if (device.getFingerprint() !== fingerprint) {
|
||||
const fprint = device.getFingerprint();
|
||||
throw new Error(
|
||||
_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 ' +
|
||||
'"%(fingerprint)s". This could mean your communications are being intercepted!',
|
||||
{
|
||||
fprint,
|
||||
userId,
|
||||
deviceId,
|
||||
fingerprint,
|
||||
}));
|
||||
}
|
||||
if (device.getFingerprint() !== fingerprint) {
|
||||
const fprint = device.getFingerprint();
|
||||
throw new Error(
|
||||
_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 ' +
|
||||
'"%(fingerprint)s". This could mean your communications are being intercepted!',
|
||||
{
|
||||
fprint,
|
||||
userId,
|
||||
deviceId,
|
||||
fingerprint,
|
||||
}));
|
||||
}
|
||||
|
||||
return cli.setDeviceVerified(userId, deviceId, true);
|
||||
}).then(() => {
|
||||
// Tell the user we verified everything
|
||||
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
|
||||
Modal.createTrackedDialog('Slash Commands', 'Verified key', InfoDialog, {
|
||||
title: _t('Verified key'),
|
||||
description: <div>
|
||||
<p>
|
||||
{
|
||||
_t('The signing key you provided matches the signing key you received ' +
|
||||
'from %(userId)s\'s device %(deviceId)s. Device marked as verified.',
|
||||
{userId, deviceId})
|
||||
}
|
||||
</p>
|
||||
</div>,
|
||||
});
|
||||
}),
|
||||
);
|
||||
await cli.setDeviceVerified(userId, deviceId, true);
|
||||
|
||||
// Tell the user we verified everything
|
||||
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
|
||||
Modal.createTrackedDialog('Slash Commands', 'Verified key', InfoDialog, {
|
||||
title: _t('Verified key'),
|
||||
description: <div>
|
||||
<p>
|
||||
{
|
||||
_t('The signing key you provided matches the signing key you received ' +
|
||||
'from %(userId)s\'s device %(deviceId)s. Device marked as verified.',
|
||||
{userId, deviceId})
|
||||
}
|
||||
</p>
|
||||
</div>,
|
||||
});
|
||||
})());
|
||||
}
|
||||
}
|
||||
return reject(this.getUsage());
|
||||
|
|
|
@ -473,7 +473,7 @@ function textForPowerEvent(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});
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,9 @@ import { _t } from '../../../languageHandler';
|
|||
const sdk = require('../../../index');
|
||||
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({
|
||||
displayName: 'EncryptedEventDialog',
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
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");
|
||||
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 FileSaver from 'file-saver';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import sdk from '../../../../index';
|
||||
import MatrixClientPeg from '../../../../MatrixClientPeg';
|
||||
import { scorePassword } from '../../../../utils/PasswordScorer';
|
||||
import { _t } from '../../../../languageHandler';
|
||||
import { accessSecretStorage } from '../../../../CrossSigningManager';
|
||||
import SettingsStore from '../../../../../lib/settings/SettingsStore';
|
||||
|
||||
const PHASE_PASSPHRASE = 0;
|
||||
const PHASE_PASSPHRASE_CONFIRM = 1;
|
||||
|
@ -49,10 +52,20 @@ function selectText(target) {
|
|||
* on the server.
|
||||
*/
|
||||
export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||
static propTypes = {
|
||||
secureSecretStorage: PropTypes.bool,
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._recoveryKeyNode = null;
|
||||
this._keyBackupInfo = null;
|
||||
this._setZxcvbnResultTimeout = null;
|
||||
|
||||
this.state = {
|
||||
secureSecretStorage: props.secureSecretStorage,
|
||||
phase: PHASE_PASSPHRASE,
|
||||
passPhrase: '',
|
||||
passPhraseConfirm: '',
|
||||
|
@ -61,12 +74,25 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
|||
zxcvbnResult: null,
|
||||
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() {
|
||||
this._recoveryKeyNode = null;
|
||||
this._keyBackupInfo = null;
|
||||
this._setZxcvbnResultTimeout = null;
|
||||
componentDidMount() {
|
||||
// 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._createBackup();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -103,15 +129,26 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
|||
}
|
||||
|
||||
_createBackup = async () => {
|
||||
const { secureSecretStorage } = this.state;
|
||||
this.setState({
|
||||
phase: PHASE_BACKINGUP,
|
||||
error: null,
|
||||
});
|
||||
let info;
|
||||
try {
|
||||
info = await MatrixClientPeg.get().createKeyBackupVersion(
|
||||
this._keyBackupInfo,
|
||||
);
|
||||
if (secureSecretStorage) {
|
||||
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();
|
||||
this.setState({
|
||||
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");
|
||||
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 () => {
|
||||
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
|
||||
Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, {
|
||||
onFinished: this.props.onFinished,
|
||||
});
|
||||
Modal.createTrackedDialog(
|
||||
'Restore Backup', '', RestoreKeyBackupDialog, {
|
||||
onFinished: this.props.onFinished,
|
||||
}, null, /* priority = */ false, /* static = */ true,
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
Copyright 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.
|
||||
|
@ -35,6 +36,7 @@ export default class RecoveryMethodRemovedDialog extends React.PureComponent {
|
|||
this.props.onFinished();
|
||||
Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
|
||||
import("./CreateKeyBackupDialog"),
|
||||
null, null, /* priority = */ false, /* static = */ true,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,13 +23,15 @@ import FileSaver from 'file-saver';
|
|||
import { _t } from '../../../../languageHandler';
|
||||
import Modal from '../../../../Modal';
|
||||
|
||||
const PHASE_PASSPHRASE = 0;
|
||||
const PHASE_PASSPHRASE_CONFIRM = 1;
|
||||
const PHASE_SHOWKEY = 2;
|
||||
const PHASE_KEEPITSAFE = 3;
|
||||
const PHASE_STORING = 4;
|
||||
const PHASE_DONE = 5;
|
||||
const PHASE_OPTOUT_CONFIRM = 6;
|
||||
const PHASE_LOADING = 0;
|
||||
const PHASE_MIGRATE = 1;
|
||||
const PHASE_PASSPHRASE = 2;
|
||||
const PHASE_PASSPHRASE_CONFIRM = 3;
|
||||
const PHASE_SHOWKEY = 4;
|
||||
const PHASE_KEEPITSAFE = 5;
|
||||
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 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.state = {
|
||||
phase: PHASE_PASSPHRASE,
|
||||
phase: PHASE_LOADING,
|
||||
passPhrase: '',
|
||||
passPhraseConfirm: '',
|
||||
copied: false,
|
||||
|
@ -66,6 +68,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
zxcvbnResult: null,
|
||||
setPassPhrase: false,
|
||||
};
|
||||
|
||||
this._fetchBackupInfo();
|
||||
}
|
||||
|
||||
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) => {
|
||||
this._recoveryKeyNode = n;
|
||||
}
|
||||
|
||||
_onMigrateNextClick = () => {
|
||||
this._bootstrapSecretStorage();
|
||||
}
|
||||
|
||||
_onCopyClick = () => {
|
||||
selectText(this._recoveryKeyNode);
|
||||
const successful = document.execCommand('copy');
|
||||
|
@ -125,6 +142,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
}
|
||||
},
|
||||
createSecretStorageKey: async () => this._keyInfo,
|
||||
keyBackupInfo: this.state.backupInfo,
|
||||
});
|
||||
this.setState({
|
||||
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;
|
||||
}
|
||||
|
||||
_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() {
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
|
||||
|
@ -449,7 +488,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
</div>;
|
||||
}
|
||||
|
||||
_renderBusyPhase(text) {
|
||||
_renderBusyPhase() {
|
||||
const Spinner = sdk.getComponent('views.elements.Spinner');
|
||||
return <div>
|
||||
<Spinner />
|
||||
|
@ -488,6 +527,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
|
||||
_titleForPhase(phase) {
|
||||
switch (phase) {
|
||||
case PHASE_MIGRATE:
|
||||
return _t('Migrate from Key Backup');
|
||||
case PHASE_PASSPHRASE:
|
||||
return _t('Secure your encrypted messages with a passphrase');
|
||||
case PHASE_PASSPHRASE_CONFIRM:
|
||||
|
@ -525,6 +566,12 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
</div>;
|
||||
} else {
|
||||
switch (this.state.phase) {
|
||||
case PHASE_LOADING:
|
||||
content = this._renderBusyPhase();
|
||||
break;
|
||||
case PHASE_MIGRATE:
|
||||
content = this._renderPhaseMigrate();
|
||||
break;
|
||||
case PHASE_PASSPHRASE:
|
||||
content = this._renderPhasePassPhrase();
|
||||
break;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
Copyright 2016 Aviral Dasgupta
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2017, 2018 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -28,7 +29,7 @@ import SettingsStore from "../settings/SettingsStore";
|
|||
import { shortcodeToUnicode } from '../HtmlUtils';
|
||||
|
||||
import EMOTICON_REGEX from 'emojibase-regex/emoticon';
|
||||
import EmojiData from '../stripped-emoji.json';
|
||||
import EMOJIBASE from 'emojibase-data/en/compact.json';
|
||||
|
||||
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.
|
||||
// all it means is that we end up bloating the bundle with precomputed stuff
|
||||
// which would be trivial to calculate and cache on demand.
|
||||
const EMOJI_SHORTNAMES = Object.keys(EmojiData).map((key) => EmojiData[key]).sort(
|
||||
(a, b) => {
|
||||
if (a.category === b.category) {
|
||||
return a.emoji_order - b.emoji_order;
|
||||
}
|
||||
return a.category - b.category;
|
||||
},
|
||||
).map((a, index) => {
|
||||
const EMOJI_SHORTNAMES = EMOJIBASE.sort((a, b) => {
|
||||
if (a.group === b.group) {
|
||||
return a.order - b.order;
|
||||
}
|
||||
return a.group - b.group;
|
||||
}).map((emoji, index) => {
|
||||
return {
|
||||
name: a.name,
|
||||
shortname: a.shortname,
|
||||
aliases: a.aliases ? a.aliases.join(' ') : '',
|
||||
aliases_ascii: a.aliases_ascii ? a.aliases_ascii.join(' ') : '',
|
||||
emoji,
|
||||
shortname: `:${emoji.shortcodes[0]}:`,
|
||||
// Include the index so that we can preserve the original order
|
||||
_orderBy: index,
|
||||
};
|
||||
|
@ -69,12 +66,15 @@ export default class EmojiProvider extends AutocompleteProvider {
|
|||
constructor() {
|
||||
super(EMOJI_REGEX);
|
||||
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
|
||||
shouldMatchWordsOnly: false,
|
||||
});
|
||||
this.nameMatcher = new QueryMatcher(EMOJI_SHORTNAMES, {
|
||||
keys: ['name'],
|
||||
keys: ['emoji.annotation'],
|
||||
// For removing punctuation
|
||||
shouldMatchWordsOnly: true,
|
||||
});
|
||||
|
@ -96,7 +96,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
|||
|
||||
const sorters = [];
|
||||
// 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)
|
||||
sorters.push((c) => score(matchedString, c.shortname));
|
||||
|
@ -110,8 +110,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
|||
sorters.push((c) => c._orderBy);
|
||||
completions = _sortBy(_uniq(completions), sorters);
|
||||
|
||||
completions = completions.map((result) => {
|
||||
const { shortname } = result;
|
||||
completions = completions.map(({shortname}) => {
|
||||
const unicode = shortcodeToUnicode(shortname);
|
||||
return {
|
||||
completion: unicode,
|
||||
|
|
|
@ -71,6 +71,7 @@ export default class QueryMatcher {
|
|||
}
|
||||
|
||||
for (const keyValue of keyValues) {
|
||||
if (!keyValue) continue; // skip falsy keyValues
|
||||
const key = stripDiacritics(keyValue).toLowerCase();
|
||||
if (!this._items.has(key)) {
|
||||
this._items.set(key, []);
|
||||
|
|
|
@ -71,12 +71,12 @@ export class ContextMenu extends React.Component {
|
|||
// on resize callback
|
||||
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 = {
|
||||
hasBackground: true,
|
||||
catchTab: true,
|
||||
managed: true,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
|
@ -186,15 +186,19 @@ export class ContextMenu extends React.Component {
|
|||
};
|
||||
|
||||
_onKeyDown = (ev) => {
|
||||
if (!this.props.managed) {
|
||||
if (ev.key === Key.ESCAPE) {
|
||||
this.props.onFinished();
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let handled = true;
|
||||
|
||||
switch (ev.key) {
|
||||
case Key.TAB:
|
||||
if (!this.props.catchTab) {
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
// fallthrough
|
||||
case Key.ESCAPE:
|
||||
this.props.onFinished();
|
||||
break;
|
||||
|
@ -321,7 +325,7 @@ export class ContextMenu extends React.Component {
|
|||
|
||||
return (
|
||||
<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 }
|
||||
{ props.children }
|
||||
</div>
|
||||
|
|
|
@ -61,30 +61,13 @@ class CustomRoomTagPanel extends React.Component {
|
|||
}
|
||||
|
||||
class CustomRoomTagTile extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {hover: false};
|
||||
this.onClick = this.onClick.bind(this);
|
||||
this.onMouseOut = this.onMouseOut.bind(this);
|
||||
this.onMouseOver = this.onMouseOver.bind(this);
|
||||
}
|
||||
|
||||
onMouseOver() {
|
||||
this.setState({hover: true});
|
||||
}
|
||||
|
||||
onMouseOut() {
|
||||
this.setState({hover: false});
|
||||
}
|
||||
|
||||
onClick() {
|
||||
onClick = () => {
|
||||
dis.dispatch({action: 'select_custom_room_tag', tag: this.props.tag.name});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const Tooltip = sdk.getComponent('elements.Tooltip');
|
||||
const AccessibleTooltipButton = sdk.getComponent('elements.AccessibleTooltipButton');
|
||||
|
||||
const tag = this.props.tag;
|
||||
const avatarHeight = 40;
|
||||
|
@ -102,12 +85,9 @@ class CustomRoomTagTile extends React.Component {
|
|||
badgeElement = (<div className={badgeClasses}>{FormattingUtils.formatCount(badge.count)}</div>);
|
||||
}
|
||||
|
||||
const tip = (this.state.hover ?
|
||||
<Tooltip className="mx_TagTile_tooltip" label={name} /> :
|
||||
<div />);
|
||||
return (
|
||||
<AccessibleButton className={className} onClick={this.onClick}>
|
||||
<div className="mx_TagTile_avatar" onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut}>
|
||||
<AccessibleTooltipButton className={className} onClick={this.onClick} title={name}>
|
||||
<div className="mx_TagTile_avatar">
|
||||
<BaseAvatar
|
||||
name={tag.avatarLetter}
|
||||
idName={name}
|
||||
|
@ -115,9 +95,8 @@ class CustomRoomTagTile extends React.Component {
|
|||
height={avatarHeight}
|
||||
/>
|
||||
{ badgeElement }
|
||||
{ tip }
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
</AccessibleTooltipButton>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,8 +26,8 @@ import sanitizeHtml from 'sanitize-html';
|
|||
import sdk from '../../index';
|
||||
import dis from '../../dispatcher';
|
||||
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import classnames from 'classnames';
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
|
||||
export default class EmbeddedPage extends React.PureComponent {
|
||||
static propTypes = {
|
||||
|
@ -39,9 +39,7 @@ export default class EmbeddedPage extends React.PureComponent {
|
|||
scrollbar: PropTypes.bool,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||
};
|
||||
static contextType = MatrixClientContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -104,7 +102,7 @@ export default class EmbeddedPage extends React.PureComponent {
|
|||
|
||||
render() {
|
||||
// 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 className = this.props.className;
|
||||
const classes = classnames({
|
||||
|
|
|
@ -19,12 +19,10 @@ import React from 'react';
|
|||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import { Key } from '../../Keyboard';
|
||||
import sdk from '../../index';
|
||||
import dis from '../../dispatcher';
|
||||
import VectorConferenceHandler from '../../VectorConferenceHandler';
|
||||
import TagPanelButtons from './TagPanelButtons';
|
||||
import SettingsStore from '../../settings/SettingsStore';
|
||||
import {_t} from "../../languageHandler";
|
||||
import Analytics from "../../Analytics";
|
||||
|
@ -39,10 +37,6 @@ const LeftPanel = createReactClass({
|
|||
collapsed: PropTypes.bool.isRequired,
|
||||
},
|
||||
|
||||
contextTypes: {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
searchFilter: '',
|
||||
|
@ -243,7 +237,6 @@ const LeftPanel = createReactClass({
|
|||
tagPanelContainer = (<div className="mx_LeftPanel_tagPanelContainer">
|
||||
<TagPanel />
|
||||
{ isCustomTagsEnabled ? <CustomRoomTagPanel /> : undefined }
|
||||
<TagPanelButtons />
|
||||
</div>);
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ import TagOrderActions from '../../actions/TagOrderActions';
|
|||
import RoomListActions from '../../actions/RoomListActions';
|
||||
import ResizeHandle from '../views/elements/ResizeHandle';
|
||||
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)
|
||||
// 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.
|
||||
|
@ -77,21 +78,6 @@ const LoggedInView = createReactClass({
|
|||
// 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() {
|
||||
return {
|
||||
// use compact timeline view
|
||||
|
@ -631,21 +617,30 @@ const LoggedInView = createReactClass({
|
|||
}
|
||||
|
||||
return (
|
||||
<div onPaste={this._onPaste} onKeyDown={this._onReactKeyDown} className='mx_MatrixChat_wrapper' aria-hidden={this.props.hideToSRUsers} onMouseDown={this._onMouseDown} onMouseUp={this._onMouseUp}>
|
||||
{ topBar }
|
||||
<ToastContainer />
|
||||
<DragDropContext onDragEnd={this._onDragEnd}>
|
||||
<div ref={this._setResizeContainerRef} className={bodyClasses}>
|
||||
<LeftPanel
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
collapsed={this.props.collapseLhs || false}
|
||||
disabled={this.props.leftDisabled}
|
||||
/>
|
||||
<ResizeHandle />
|
||||
{ pageElement }
|
||||
</div>
|
||||
</DragDropContext>
|
||||
</div>
|
||||
<MatrixClientContext.Provider value={this._matrixClient}>
|
||||
<div
|
||||
onPaste={this._onPaste}
|
||||
onKeyDown={this._onReactKeyDown}
|
||||
className='mx_MatrixChat_wrapper'
|
||||
aria-hidden={this.props.hideToSRUsers}
|
||||
onMouseDown={this._onMouseDown}
|
||||
onMouseUp={this._onMouseUp}
|
||||
>
|
||||
{ topBar }
|
||||
<ToastContainer />
|
||||
<DragDropContext onDragEnd={this._onDragEnd}>
|
||||
<div ref={this._setResizeContainerRef} className={bodyClasses}>
|
||||
<LeftPanel
|
||||
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() {
|
||||
const bodyView = React.Children.only(this.props.children);
|
||||
const panelView = this.props.panel;
|
||||
|
|
|
@ -150,16 +150,6 @@ export default createReactClass({
|
|||
makeRegistrationUrl: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
childContextTypes: {
|
||||
appConfig: PropTypes.object,
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return {
|
||||
appConfig: this.props.config,
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
const s = {
|
||||
// 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 => {
|
||||
let requestObserver;
|
||||
if (request.event.getRoomId()) {
|
||||
|
@ -1579,9 +1569,17 @@ export default createReactClass({
|
|||
action: 'start_post_registration',
|
||||
});
|
||||
} else if (screen.indexOf('room/') == 0) {
|
||||
const segments = screen.substring(5).split('/');
|
||||
const roomString = segments[0];
|
||||
let eventId = segments.splice(1).join("/"); // empty string if no event id given
|
||||
// Rooms can have the following formats:
|
||||
// #room_alias:domain or !opaque_id:domain
|
||||
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
|
||||
// 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 createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import sdk from '../../index';
|
||||
import { _t } from '../../languageHandler';
|
||||
import dis from '../../dispatcher';
|
||||
import AccessibleButton from '../views/elements/AccessibleButton';
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
|
||||
export default createReactClass({
|
||||
displayName: 'MyGroups',
|
||||
|
@ -34,8 +33,8 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
contextTypes: {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||
statics: {
|
||||
contextType: MatrixClientContext,
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
|
@ -47,7 +46,7 @@ export default createReactClass({
|
|||
},
|
||||
|
||||
_fetch: function() {
|
||||
this.context.matrixClient.getJoinedGroups().then((result) => {
|
||||
this.context.getJoinedGroups().then((result) => {
|
||||
this.setState({groups: result.groups, error: null});
|
||||
}, (err) => {
|
||||
if (err.errcode === 'M_GUEST_ACCESS_FORBIDDEN') {
|
||||
|
|
|
@ -23,13 +23,13 @@ import PropTypes from 'prop-types';
|
|||
import classNames from 'classnames';
|
||||
import sdk from '../../index';
|
||||
import dis from '../../dispatcher';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import RateLimitedFunc from '../../ratelimitedfunc';
|
||||
import { showGroupInviteDialog, showGroupAddRoomDialog } from '../../GroupAddressPicker';
|
||||
import GroupStore from '../../stores/GroupStore';
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import {RIGHT_PANEL_PHASES, RIGHT_PANEL_PHASES_NO_ARGS} from "../../stores/RightPanelStorePhases";
|
||||
import RightPanelStore from "../../stores/RightPanelStore";
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
|
||||
export default class RightPanel extends React.Component {
|
||||
static get propTypes() {
|
||||
|
@ -40,14 +40,10 @@ export default class RightPanel extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
static get contextTypes() {
|
||||
return {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||
};
|
||||
}
|
||||
static contextType = MatrixClientContext;
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
phase: this._getPhaseFromProps(),
|
||||
isUserPrivilegedInGroup: null,
|
||||
|
@ -93,15 +89,15 @@ export default class RightPanel extends React.Component {
|
|||
|
||||
componentWillMount() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
const cli = this.context.matrixClient;
|
||||
const cli = this.context;
|
||||
cli.on("RoomState.members", this.onRoomStateMember);
|
||||
this._initGroupStore(this.props.groupId);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
if (this.context.matrixClient) {
|
||||
this.context.matrixClient.removeListener("RoomState.members", this.onRoomStateMember);
|
||||
if (this.context) {
|
||||
this.context.removeListener("RoomState.members", this.onRoomStateMember);
|
||||
}
|
||||
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) {
|
||||
panel = <GroupRoomList groupId={this.props.groupId} key={this.props.groupId} />;
|
||||
} else if (this.state.phase === RIGHT_PANEL_PHASES.RoomMemberInfo) {
|
||||
if (SettingsStore.isFeatureEnabled("feature_dm_verification")) {
|
||||
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||
const onClose = () => {
|
||||
dis.dispatch({
|
||||
action: "view_user",
|
||||
|
@ -209,7 +205,7 @@ export default class RightPanel extends React.Component {
|
|||
} else if (this.state.phase === RIGHT_PANEL_PHASES.Room3pidMemberInfo) {
|
||||
panel = <ThirdPartyMemberInfo event={this.state.event} key={this.props.roomId} />;
|
||||
} else if (this.state.phase === RIGHT_PANEL_PHASES.GroupMemberInfo) {
|
||||
if (SettingsStore.isFeatureEnabled("feature_dm_verification")) {
|
||||
if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||
const onClose = () => {
|
||||
dis.dispatch({
|
||||
action: "view_user",
|
||||
|
|
|
@ -30,6 +30,7 @@ import PropTypes from 'prop-types';
|
|||
import { _t } from '../../languageHandler';
|
||||
import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils';
|
||||
import Analytics from '../../Analytics';
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
|
||||
const MAX_NAME_LENGTH = 80;
|
||||
const MAX_TOPIC_LENGTH = 160;
|
||||
|
@ -65,16 +66,6 @@ module.exports = createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
childContextTypes: {
|
||||
matrixClient: PropTypes.object,
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return {
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this._unmounted = false;
|
||||
this.nextBatch = null;
|
||||
|
|
|
@ -25,7 +25,7 @@ import MatrixClientPeg from '../../MatrixClientPeg';
|
|||
import Resend from '../../Resend';
|
||||
import * as cryptodevices from '../../cryptodevices';
|
||||
import dis from '../../dispatcher';
|
||||
import { messageForResourceLimitError } from '../../utils/ErrorUtils';
|
||||
import {messageForResourceLimitError, messageForSendError} from '../../utils/ErrorUtils';
|
||||
|
||||
const STATUS_BAR_HIDDEN = 0;
|
||||
const STATUS_BAR_EXPANDED = 1;
|
||||
|
@ -272,7 +272,7 @@ module.exports = createReactClass({
|
|||
unsentMessages[0].error.data &&
|
||||
unsentMessages[0].error.data.error
|
||||
) {
|
||||
title = unsentMessages[0].error.data.error;
|
||||
title = messageForSendError(unsentMessages[0].error.data) || unsentMessages[0].error.data.error;
|
||||
} else {
|
||||
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 createReactClass from 'create-react-class';
|
||||
import ReactDOM from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import {Room} from "matrix-js-sdk";
|
||||
import { _t } from '../../languageHandler';
|
||||
import {RoomPermalinkCreator} from '../../utils/permalinks/Permalinks';
|
||||
|
||||
|
@ -55,6 +53,7 @@ import SettingsStore, {SettingLevel} from "../../settings/SettingsStore";
|
|||
import WidgetUtils from '../../utils/WidgetUtils';
|
||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||
import RightPanelStore from "../../stores/RightPanelStore";
|
||||
import RoomContext from "../../contexts/RoomContext";
|
||||
|
||||
const DEBUG = false;
|
||||
let debuglog = function() {};
|
||||
|
@ -66,12 +65,6 @@ if (DEBUG) {
|
|||
debuglog = console.log.bind(console);
|
||||
}
|
||||
|
||||
const RoomContext = PropTypes.shape({
|
||||
canReact: PropTypes.bool.isRequired,
|
||||
canReply: PropTypes.bool.isRequired,
|
||||
room: PropTypes.instanceOf(Room),
|
||||
});
|
||||
|
||||
module.exports = createReactClass({
|
||||
displayName: 'RoomView',
|
||||
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() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
MatrixClientPeg.get().on("Room", this.onRoom);
|
||||
|
@ -461,7 +439,7 @@ module.exports = createReactClass({
|
|||
|
||||
componentDidUpdate: function() {
|
||||
if (this._roomView.current) {
|
||||
const roomView = ReactDOM.findDOMNode(this._roomView.current);
|
||||
const roomView = this._roomView.current;
|
||||
if (!roomView.ondrop) {
|
||||
roomView.addEventListener('drop', this.onDrop);
|
||||
roomView.addEventListener('dragover', this.onDragOver);
|
||||
|
@ -505,7 +483,7 @@ module.exports = createReactClass({
|
|||
// is really just for hygiene - we're going to be
|
||||
// deleted anyway, so it doesn't matter if the event listeners
|
||||
// don't get cleaned up.
|
||||
const roomView = ReactDOM.findDOMNode(this._roomView.current);
|
||||
const roomView = this._roomView.current;
|
||||
roomView.removeEventListener('drop', this.onDrop);
|
||||
roomView.removeEventListener('dragover', this.onDragOver);
|
||||
roomView.removeEventListener('dragleave', this.onDragLeaveOrEnd);
|
||||
|
@ -1953,7 +1931,8 @@ module.exports = createReactClass({
|
|||
/>);
|
||||
|
||||
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');
|
||||
topUnreadMessagesBar = (<TopUnreadMessagesBar
|
||||
onScrollUpClick={this.jumpToReadMarker}
|
||||
|
@ -1961,7 +1940,8 @@ module.exports = createReactClass({
|
|||
/>);
|
||||
}
|
||||
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');
|
||||
jumpToBottom = (<JumpToBottomButton
|
||||
numUnreadMessages={this.state.numUnreadMessages}
|
||||
|
@ -1989,45 +1969,47 @@ module.exports = createReactClass({
|
|||
: null;
|
||||
|
||||
return (
|
||||
<main className={"mx_RoomView" + (inCall ? " mx_RoomView_inCall" : "")} ref={this._roomView}>
|
||||
<ErrorBoundary>
|
||||
<RoomHeader
|
||||
room={this.state.room}
|
||||
searchInfo={searchInfo}
|
||||
oobData={this.props.oobData}
|
||||
inRoom={myMembership === 'join'}
|
||||
onSearchClick={this.onSearchClick}
|
||||
onSettingsClick={this.onSettingsClick}
|
||||
onPinnedClick={this.onPinnedClick}
|
||||
onCancelClick={(aux && !hideCancel) ? this.onCancelClick : null}
|
||||
onForgetClick={(myMembership === "leave") ? this.onForgetClick : null}
|
||||
onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null}
|
||||
e2eStatus={this.state.e2eStatus}
|
||||
/>
|
||||
<MainSplit
|
||||
panel={rightPanel}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
>
|
||||
<div className={fadableSectionClasses}>
|
||||
{auxPanel}
|
||||
<div className="mx_RoomView_timeline">
|
||||
{topUnreadMessagesBar}
|
||||
{jumpToBottom}
|
||||
{messagePanel}
|
||||
{searchResultsPanel}
|
||||
</div>
|
||||
<div className={statusBarAreaClass}>
|
||||
<div className="mx_RoomView_statusAreaBox">
|
||||
<div className="mx_RoomView_statusAreaBox_line"></div>
|
||||
{statusBar}
|
||||
<RoomContext.Provider value={this.state}>
|
||||
<main className={"mx_RoomView" + (inCall ? " mx_RoomView_inCall" : "")} ref={this._roomView}>
|
||||
<ErrorBoundary>
|
||||
<RoomHeader
|
||||
room={this.state.room}
|
||||
searchInfo={searchInfo}
|
||||
oobData={this.props.oobData}
|
||||
inRoom={myMembership === 'join'}
|
||||
onSearchClick={this.onSearchClick}
|
||||
onSettingsClick={this.onSettingsClick}
|
||||
onPinnedClick={this.onPinnedClick}
|
||||
onCancelClick={(aux && !hideCancel) ? this.onCancelClick : null}
|
||||
onForgetClick={(myMembership === "leave") ? this.onForgetClick : null}
|
||||
onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null}
|
||||
e2eStatus={this.state.e2eStatus}
|
||||
/>
|
||||
<MainSplit
|
||||
panel={rightPanel}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
>
|
||||
<div className={fadableSectionClasses}>
|
||||
{auxPanel}
|
||||
<div className="mx_RoomView_timeline">
|
||||
{topUnreadMessagesBar}
|
||||
{jumpToBottom}
|
||||
{messagePanel}
|
||||
{searchResultsPanel}
|
||||
</div>
|
||||
<div className={statusBarAreaClass}>
|
||||
<div className="mx_RoomView_statusAreaBox">
|
||||
<div className="mx_RoomView_statusAreaBox_line" />
|
||||
{statusBar}
|
||||
</div>
|
||||
</div>
|
||||
{previewBar}
|
||||
{messageComposer}
|
||||
</div>
|
||||
{previewBar}
|
||||
{messageComposer}
|
||||
</div>
|
||||
</MainSplit>
|
||||
</ErrorBoundary>
|
||||
</main>
|
||||
</MainSplit>
|
||||
</ErrorBoundary>
|
||||
</main>
|
||||
</RoomContext.Provider>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -16,8 +16,6 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import TagOrderStore from '../../stores/TagOrderStore';
|
||||
|
||||
import GroupActions from '../../actions/GroupActions';
|
||||
|
@ -28,12 +26,13 @@ import { _t } from '../../languageHandler';
|
|||
|
||||
import { Droppable } from 'react-beautiful-dnd';
|
||||
import classNames from 'classnames';
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
|
||||
const TagPanel = createReactClass({
|
||||
displayName: 'TagPanel',
|
||||
|
||||
contextTypes: {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||
statics: {
|
||||
contextType: MatrixClientContext,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
|
@ -45,8 +44,8 @@ const TagPanel = createReactClass({
|
|||
|
||||
componentWillMount: function() {
|
||||
this.unmounted = false;
|
||||
this.context.matrixClient.on("Group.myMembership", this._onGroupMyMembership);
|
||||
this.context.matrixClient.on("sync", this._onClientSync);
|
||||
this.context.on("Group.myMembership", this._onGroupMyMembership);
|
||||
this.context.on("sync", this._onClientSync);
|
||||
|
||||
this._tagOrderStoreToken = TagOrderStore.addListener(() => {
|
||||
if (this.unmounted) {
|
||||
|
@ -58,13 +57,13 @@ const TagPanel = createReactClass({
|
|||
});
|
||||
});
|
||||
// This could be done by anything with a matrix client
|
||||
dis.dispatch(GroupActions.fetchJoinedGroups(this.context.matrixClient));
|
||||
dis.dispatch(GroupActions.fetchJoinedGroups(this.context));
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unmounted = true;
|
||||
this.context.matrixClient.removeListener("Group.myMembership", this._onGroupMyMembership);
|
||||
this.context.matrixClient.removeListener("sync", this._onClientSync);
|
||||
this.context.removeListener("Group.myMembership", this._onGroupMyMembership);
|
||||
this.context.removeListener("sync", this._onClientSync);
|
||||
if (this._filterStoreToken) {
|
||||
this._filterStoreToken.remove();
|
||||
}
|
||||
|
@ -72,7 +71,7 @@ const TagPanel = createReactClass({
|
|||
|
||||
_onGroupMyMembership() {
|
||||
if (this.unmounted) return;
|
||||
dis.dispatch(GroupActions.fetchJoinedGroups(this.context.matrixClient));
|
||||
dis.dispatch(GroupActions.fetchJoinedGroups(this.context));
|
||||
},
|
||||
|
||||
_onClientSync(syncState, prevState) {
|
||||
|
@ -81,7 +80,7 @@ const TagPanel = createReactClass({
|
|||
const reconnected = syncState !== "ERROR" && prevState !== syncState;
|
||||
if (reconnected) {
|
||||
// Load joined groups
|
||||
dis.dispatch(GroupActions.fetchJoinedGroups(this.context.matrixClient));
|
||||
dis.dispatch(GroupActions.fetchJoinedGroups(this.context));
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -104,6 +103,7 @@ const TagPanel = createReactClass({
|
|||
render() {
|
||||
const DNDTagTile = sdk.getComponent('elements.DNDTagTile');
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const ActionButton = sdk.getComponent('elements.ActionButton');
|
||||
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
||||
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
||||
|
||||
|
@ -154,6 +154,13 @@ const TagPanel = createReactClass({
|
|||
ref={provided.innerRef}
|
||||
>
|
||||
{ tags }
|
||||
<div>
|
||||
<ActionButton
|
||||
tooltip
|
||||
label={_t("Communities")}
|
||||
action="toggle_my_groups"
|
||||
className="mx_TagTile mx_TagTile_plus" />
|
||||
</div>
|
||||
{ provided.placeholder }
|
||||
</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 PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import AvatarLogic from '../../../Avatar';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
module.exports = createReactClass({
|
||||
displayName: 'BaseAvatar',
|
||||
|
@ -38,10 +38,16 @@ module.exports = createReactClass({
|
|||
// XXX resizeMethod not actually used.
|
||||
resizeMethod: PropTypes.string,
|
||||
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: {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||
statics: {
|
||||
contextType: MatrixClientContext,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
|
@ -59,12 +65,12 @@ module.exports = createReactClass({
|
|||
|
||||
componentDidMount() {
|
||||
this.unmounted = false;
|
||||
this.context.matrixClient.on('sync', this.onClientSync);
|
||||
this.context.on('sync', this.onClientSync);
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unmounted = true;
|
||||
this.context.matrixClient.removeListener('sync', this.onClientSync);
|
||||
this.context.removeListener('sync', this.onClientSync);
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
|
@ -148,7 +154,7 @@ module.exports = createReactClass({
|
|||
|
||||
const {
|
||||
name, idName, title, url, urls, width, height, resizeMethod,
|
||||
defaultToInitialLetter, onClick,
|
||||
defaultToInitialLetter, onClick, inputRef,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
|
@ -171,7 +177,7 @@ module.exports = createReactClass({
|
|||
if (onClick != null) {
|
||||
return (
|
||||
<AccessibleButton element='span' className="mx_BaseAvatar"
|
||||
onClick={onClick} {...otherProps}
|
||||
onClick={onClick} inputRef={inputRef} {...otherProps}
|
||||
>
|
||||
{ textNode }
|
||||
{ imgNode }
|
||||
|
@ -179,7 +185,7 @@ module.exports = createReactClass({
|
|||
);
|
||||
} else {
|
||||
return (
|
||||
<span className="mx_BaseAvatar" {...otherProps}>
|
||||
<span className="mx_BaseAvatar" ref={inputRef} {...otherProps}>
|
||||
{ textNode }
|
||||
{ imgNode }
|
||||
</span>
|
||||
|
@ -188,21 +194,26 @@ module.exports = createReactClass({
|
|||
}
|
||||
if (onClick != null) {
|
||||
return (
|
||||
<AccessibleButton className="mx_BaseAvatar mx_BaseAvatar_image"
|
||||
<AccessibleButton
|
||||
className="mx_BaseAvatar mx_BaseAvatar_image"
|
||||
element='img'
|
||||
src={imageUrl}
|
||||
onClick={onClick}
|
||||
onError={this.onError}
|
||||
width={width} height={height}
|
||||
title={title} alt=""
|
||||
inputRef={inputRef}
|
||||
{...otherProps} />
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<img className="mx_BaseAvatar mx_BaseAvatar_image" src={imageUrl}
|
||||
<img
|
||||
className="mx_BaseAvatar mx_BaseAvatar_image"
|
||||
src={imageUrl}
|
||||
onError={this.onError}
|
||||
width={width} height={height}
|
||||
title={title} alt=""
|
||||
ref={inputRef}
|
||||
{...otherProps} />
|
||||
);
|
||||
}
|
||||
|
|
|
@ -38,8 +38,8 @@ export default class MemberStatusMessageAvatar extends React.Component {
|
|||
resizeMethod: 'crop',
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
hasStatus: this.hasStatus,
|
||||
|
|
|
@ -31,8 +31,8 @@ export default class GroupInviteTileContextMenu extends React.Component {
|
|||
onFinished: PropTypes.func,
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._onClickReject = this._onClickReject.bind(this);
|
||||
}
|
||||
|
|
|
@ -27,8 +27,8 @@ export default class StatusMessageContextMenu extends React.Component {
|
|||
user: PropTypes.object,
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
message: this.comittedStatusMessage,
|
||||
|
|
|
@ -17,12 +17,12 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import dis from '../../../dispatcher';
|
||||
import TagOrderActions from '../../../actions/TagOrderActions';
|
||||
import sdk from '../../../index';
|
||||
import {MenuItem} from "../../structures/ContextMenu";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
export default class TagTileContextMenu extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -31,9 +31,7 @@ export default class TagTileContextMenu extends React.Component {
|
|||
onFinished: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||
};
|
||||
static contextType = MatrixClientContext;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
@ -51,7 +49,7 @@ export default class TagTileContextMenu extends React.Component {
|
|||
}
|
||||
|
||||
_onRemoveClick() {
|
||||
dis.dispatch(TagOrderActions.removeTag(this.context.matrixClient, this.props.tag));
|
||||
dis.dispatch(TagOrderActions.removeTag(this.context, this.props.tag));
|
||||
this.props.onFinished();
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import SdkConfig from '../../../SdkConfig';
|
|||
import { getHostingLink } from '../../../utils/HostingLink';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import {MenuItem} from "../../structures/ContextMenu";
|
||||
import sdk from "../../../index";
|
||||
|
||||
export class TopLeftMenu extends React.Component {
|
||||
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 = (
|
||||
<MenuItem className="mx_TopLeftMenu_icon_settings" onClick={this.openSettings}>
|
||||
{_t("Settings")}
|
||||
|
@ -115,11 +122,18 @@ export class TopLeftMenu extends React.Component {
|
|||
<ul className="mx_TopLeftMenu_section_withIcon" role="none">
|
||||
{homePageItem}
|
||||
{settingsItem}
|
||||
{helpItem}
|
||||
{signInOutItem}
|
||||
</ul>
|
||||
</div>;
|
||||
}
|
||||
|
||||
openHelp = () => {
|
||||
this.closeMenu();
|
||||
const RedesignFeedbackDialog = sdk.getComponent("views.dialogs.RedesignFeedbackDialog");
|
||||
Modal.createTrackedDialog('Report bugs & give feedback', '', RedesignFeedbackDialog);
|
||||
};
|
||||
|
||||
viewHomePage() {
|
||||
dis.dispatch({action: 'view_home_page'});
|
||||
this.closeMenu();
|
||||
|
|
|
@ -22,12 +22,11 @@ import FocusLock from 'react-focus-lock';
|
|||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
|
||||
import { Key } from '../../../Keyboard';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import { _t } from "../../../languageHandler";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
/**
|
||||
* Basic container for modal dialogs.
|
||||
|
@ -84,16 +83,6 @@ export default createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
childContextTypes: {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return {
|
||||
matrixClient: this._matrixClient,
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount() {
|
||||
this._matrixClient = MatrixClientPeg.get();
|
||||
},
|
||||
|
@ -122,36 +111,38 @@ export default createReactClass({
|
|||
}
|
||||
|
||||
return (
|
||||
<FocusLock
|
||||
returnFocus={true}
|
||||
lockProps={{
|
||||
onKeyDown: this._onKeyDown,
|
||||
role: "dialog",
|
||||
["aria-labelledby"]: "mx_BaseDialog_title",
|
||||
// This should point to a node describing the dialog.
|
||||
// If we were about to completely follow this recommendation we'd need to
|
||||
// make all the components relying on BaseDialog to be aware of it.
|
||||
// So instead we will use the whole content as the description.
|
||||
// Description comes first and if the content contains more text,
|
||||
// AT users can skip its presentation.
|
||||
["aria-describedby"]: this.props.contentId,
|
||||
}}
|
||||
className={classNames({
|
||||
[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_title', this.props.titleClass)} id='mx_BaseDialog_title'>
|
||||
{ this.props.title }
|
||||
<MatrixClientContext.Provider value={this._matrixClient}>
|
||||
<FocusLock
|
||||
returnFocus={true}
|
||||
lockProps={{
|
||||
onKeyDown: this._onKeyDown,
|
||||
role: "dialog",
|
||||
["aria-labelledby"]: "mx_BaseDialog_title",
|
||||
// This should point to a node describing the dialog.
|
||||
// If we were about to completely follow this recommendation we'd need to
|
||||
// make all the components relying on BaseDialog to be aware of it.
|
||||
// So instead we will use the whole content as the description.
|
||||
// Description comes first and if the content contains more text,
|
||||
// AT users can skip its presentation.
|
||||
["aria-describedby"]: this.props.contentId,
|
||||
}}
|
||||
className={classNames({
|
||||
[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_title', this.props.titleClass)} id='mx_BaseDialog_title'>
|
||||
{ this.props.title }
|
||||
</div>
|
||||
{ this.props.headerButton }
|
||||
{ cancelButton }
|
||||
</div>
|
||||
{ this.props.headerButton }
|
||||
{ cancelButton }
|
||||
</div>
|
||||
{ this.props.children }
|
||||
</FocusLock>
|
||||
{ this.props.children }
|
||||
</FocusLock>
|
||||
</MatrixClientContext.Provider>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -25,8 +25,8 @@ import Modal from '../../../Modal';
|
|||
import { _t } from '../../../languageHandler';
|
||||
|
||||
export default class BugReportDialog extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
sendLogs: true,
|
||||
busy: false,
|
||||
|
|
|
@ -173,7 +173,7 @@ export default createReactClass({
|
|||
const domain = MatrixClientPeg.get().getDomain();
|
||||
aliasField = (
|
||||
<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>
|
||||
);
|
||||
} 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';
|
||||
|
||||
export default class DeactivateAccountDialog extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._onOk = this._onOk.bind(this);
|
||||
this._onCancel = this._onCancel.bind(this);
|
||||
|
|
|
@ -97,7 +97,7 @@ export default class DeviceVerifyDialog extends React.Component {
|
|||
const client = MatrixClientPeg.get();
|
||||
const verifyingOwnDevice = this.props.userId === client.getUserId();
|
||||
try {
|
||||
if (!verifyingOwnDevice && SettingsStore.getValue("feature_dm_verification")) {
|
||||
if (!verifyingOwnDevice && SettingsStore.getValue("feature_cross_signing")) {
|
||||
const roomId = await ensureDMExistsAndOpen(this.props.userId);
|
||||
// throws upon cancellation before having started
|
||||
this._verifier = await client.requestVerificationDM(
|
||||
|
|
|
@ -16,23 +16,19 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Room } from "matrix-js-sdk";
|
||||
|
||||
import sdk from '../../../index';
|
||||
import SyntaxHighlight from '../elements/SyntaxHighlight';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import Field from "../elements/Field";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
class DevtoolsComponent extends React.Component {
|
||||
static contextTypes = {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
class GenericEditor extends DevtoolsComponent {
|
||||
class GenericEditor extends React.PureComponent {
|
||||
// static propTypes = {onBack: PropTypes.func.isRequired};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._onChange = this._onChange.bind(this);
|
||||
this.onBack = this.onBack.bind(this);
|
||||
}
|
||||
|
@ -67,12 +63,15 @@ class SendCustomEvent extends GenericEditor {
|
|||
|
||||
static propTypes = {
|
||||
onBack: PropTypes.func.isRequired,
|
||||
room: PropTypes.instanceOf(Room).isRequired,
|
||||
forceStateEvent: PropTypes.bool,
|
||||
inputs: PropTypes.object,
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
static contextType = MatrixClientContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._send = this._send.bind(this);
|
||||
|
||||
const {eventType, stateKey, evContent} = Object.assign({
|
||||
|
@ -91,11 +90,11 @@ class SendCustomEvent extends GenericEditor {
|
|||
}
|
||||
|
||||
send(content) {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const cli = this.context;
|
||||
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 {
|
||||
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 propTypes = {
|
||||
room: PropTypes.instanceOf(Room).isRequired,
|
||||
isRoomAccountData: PropTypes.bool,
|
||||
forceMode: PropTypes.bool,
|
||||
inputs: PropTypes.object,
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
static contextType = MatrixClientContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._send = this._send.bind(this);
|
||||
|
||||
const {eventType, evContent} = Object.assign({
|
||||
|
@ -177,9 +179,9 @@ class SendAccountData extends GenericEditor {
|
|||
}
|
||||
|
||||
send(content) {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const cli = this.context;
|
||||
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);
|
||||
}
|
||||
|
@ -234,7 +236,7 @@ class SendAccountData extends GenericEditor {
|
|||
const INITIAL_LOAD_TILES = 20;
|
||||
const LOAD_TILES_STEP_SIZE = 50;
|
||||
|
||||
class FilteredList extends React.Component {
|
||||
class FilteredList extends React.PureComponent {
|
||||
static propTypes = {
|
||||
children: PropTypes.any,
|
||||
query: PropTypes.string,
|
||||
|
@ -247,8 +249,8 @@ class FilteredList extends React.Component {
|
|||
return children.filter((child) => child.key.toLowerCase().includes(lcQuery));
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
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 propTypes = {
|
||||
onBack: PropTypes.func.isRequired,
|
||||
room: PropTypes.instanceOf(Room).isRequired,
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
static contextType = MatrixClientContext;
|
||||
|
||||
const room = MatrixClientPeg.get().getRoom(this.context.roomId);
|
||||
this.roomStateEvents = room.currentState.events;
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.roomStateEvents = this.props.room.currentState.events;
|
||||
|
||||
this.onBack = this.onBack.bind(this);
|
||||
this.editEv = this.editEv.bind(this);
|
||||
|
@ -373,7 +376,7 @@ class RoomStateExplorer extends DevtoolsComponent {
|
|||
render() {
|
||||
if (this.state.event) {
|
||||
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(),
|
||||
evContent: JSON.stringify(this.state.event.getContent(), null, '\t'),
|
||||
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 propTypes = {
|
||||
onBack: PropTypes.func.isRequired,
|
||||
room: PropTypes.instanceOf(Room).isRequired,
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
static contextType = MatrixClientContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onBack = this.onBack.bind(this);
|
||||
this.editEv = this.editEv.bind(this);
|
||||
|
@ -467,11 +473,10 @@ class AccountDataExplorer extends DevtoolsComponent {
|
|||
}
|
||||
|
||||
getData() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
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) {
|
||||
|
@ -505,10 +510,14 @@ class AccountDataExplorer extends DevtoolsComponent {
|
|||
render() {
|
||||
if (this.state.event) {
|
||||
if (this.state.editing) {
|
||||
return <SendAccountData isRoomAccountData={this.state.isRoomAccountData} onBack={this.onBack} inputs={{
|
||||
eventType: this.state.event.getType(),
|
||||
evContent: JSON.stringify(this.state.event.getContent(), null, '\t'),
|
||||
}} forceMode={true} />;
|
||||
return <SendAccountData
|
||||
room={this.props.room}
|
||||
isRoomAccountData={this.state.isRoomAccountData}
|
||||
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">
|
||||
|
@ -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 propTypes = {
|
||||
onBack: PropTypes.func.isRequired,
|
||||
room: PropTypes.instanceOf(Room).isRequired,
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
static contextType = MatrixClientContext;
|
||||
|
||||
const room = MatrixClientPeg.get().getRoom(this.context.roomId);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const room = this.props.room;
|
||||
const servers = new Set();
|
||||
room.currentState.getStateEvents("m.room.member").forEach(ev => servers.add(ev.getSender().split(":")[1]));
|
||||
this.servers = Array.from(servers).map(s =>
|
||||
|
@ -602,19 +614,14 @@ const Entries = [
|
|||
ServersInRoomList,
|
||||
];
|
||||
|
||||
export default class DevtoolsDialog extends React.Component {
|
||||
static childContextTypes = {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
// client: PropTypes.instanceOf(MatixClient),
|
||||
};
|
||||
|
||||
export default class DevtoolsDialog extends React.PureComponent {
|
||||
static propTypes = {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onBack = this.onBack.bind(this);
|
||||
this.onCancel = this.onCancel.bind(this);
|
||||
|
||||
|
@ -627,10 +634,6 @@ export default class DevtoolsDialog extends React.Component {
|
|||
this._unmounted = true;
|
||||
}
|
||||
|
||||
getChildContext() {
|
||||
return { roomId: this.props.roomId };
|
||||
}
|
||||
|
||||
_setMode(mode) {
|
||||
return () => {
|
||||
this.setState({ mode });
|
||||
|
@ -654,15 +657,17 @@ export default class DevtoolsDialog extends React.Component {
|
|||
let body;
|
||||
|
||||
if (this.state.mode) {
|
||||
body = <div>
|
||||
<div className="mx_DevTools_label_left">{ this.state.mode.getLabel() }</div>
|
||||
<div className="mx_DevTools_label_right">Room ID: { this.props.roomId }</div>
|
||||
<div className="mx_DevTools_label_bottom" />
|
||||
<this.state.mode onBack={this.onBack} />
|
||||
</div>;
|
||||
body = <MatrixClientContext.Consumer>
|
||||
{(cli) => <React.Fragment>
|
||||
<div className="mx_DevTools_label_left">{ this.state.mode.getLabel() }</div>
|
||||
<div className="mx_DevTools_label_right">Room ID: { this.props.roomId }</div>
|
||||
<div className="mx_DevTools_label_bottom" />
|
||||
<this.state.mode onBack={this.onBack} room={cli.getRoom(this.props.roomId)} />
|
||||
</React.Fragment>}
|
||||
</MatrixClientContext.Consumer>;
|
||||
} else {
|
||||
const classes = "mx_DevTools_RoomStateExplorer_button";
|
||||
body = <div>
|
||||
body = <React.Fragment>
|
||||
<div>
|
||||
<div className="mx_DevTools_label_left">{ _t('Toolbox') }</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">
|
||||
<button onClick={this.onCancel}>{ _t('Cancel') }</button>
|
||||
</div>
|
||||
</div>;
|
||||
</React.Fragment>;
|
||||
}
|
||||
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
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");
|
||||
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
|
||||
// allow us to trust it (ie. upload keys to it)
|
||||
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
|
||||
Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, {});
|
||||
Modal.createTrackedDialog(
|
||||
'Restore Backup', '', RestoreKeyBackupDialog, null, null,
|
||||
/* priority = */ false, /* static = */ true,
|
||||
);
|
||||
} else {
|
||||
Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
|
||||
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,
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
reason: "",
|
||||
|
|
|
@ -24,9 +24,11 @@ import RolesRoomSettingsTab from "../settings/tabs/room/RolesRoomSettingsTab";
|
|||
import GeneralRoomSettingsTab from "../settings/tabs/room/GeneralRoomSettingsTab";
|
||||
import SecurityRoomSettingsTab from "../settings/tabs/room/SecurityRoomSettingsTab";
|
||||
import NotificationSettingsTab from "../settings/tabs/room/NotificationSettingsTab";
|
||||
import BridgeSettingsTab from "../settings/tabs/room/BridgeSettingsTab";
|
||||
import sdk from "../../../index";
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import dis from "../../../dispatcher";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
|
||||
export default class RoomSettingsDialog extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -52,6 +54,9 @@ export default class RoomSettingsDialog extends React.Component {
|
|||
|
||||
_getTabs() {
|
||||
const tabs = [];
|
||||
const featureFlag = SettingsStore.isFeatureEnabled("feature_bridge_state");
|
||||
const shouldShowBridgeIcon = featureFlag &&
|
||||
BridgeSettingsTab.getBridgeStateEvents(this.props.roomId).length > 0;
|
||||
|
||||
tabs.push(new Tab(
|
||||
_td("General"),
|
||||
|
@ -73,6 +78,15 @@ export default class RoomSettingsDialog extends React.Component {
|
|||
"mx_RoomSettingsDialog_rolesIcon",
|
||||
<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(
|
||||
_td("Advanced"),
|
||||
"mx_RoomSettingsDialog_warningIcon",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
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");
|
||||
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 { MatrixClient } from 'matrix-js-sdk';
|
||||
|
||||
import sdk from '../../../../index';
|
||||
import MatrixClientPeg from '../../../../MatrixClientPeg';
|
||||
import Modal from '../../../../Modal';
|
||||
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
|
||||
import { _t } from '../../../../languageHandler';
|
||||
import {Key} from "../../../../Keyboard";
|
||||
import { accessSecretStorage } from '../../../../CrossSigningManager';
|
||||
|
||||
const RESTORE_TYPE_PASSPHRASE = 0;
|
||||
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
|
||||
|
@ -35,6 +37,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
|||
super(props);
|
||||
this.state = {
|
||||
backupInfo: null,
|
||||
backupKeyStored: null,
|
||||
loading: false,
|
||||
loadError: null,
|
||||
restoreError: null,
|
||||
|
@ -73,7 +76,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
|||
onFinished: () => {
|
||||
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() {
|
||||
this.setState({
|
||||
loading: true,
|
||||
|
@ -155,10 +184,20 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
|||
});
|
||||
try {
|
||||
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({
|
||||
loadError: null,
|
||||
loading: false,
|
||||
backupInfo,
|
||||
});
|
||||
} catch (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 AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
||||
|
||||
const {title, ...props} = this.props;
|
||||
const {title, children, ...props} = this.props;
|
||||
|
||||
const tip = this.state.hover ? <Tooltip
|
||||
className="mx_AccessibleTooltipButton_container"
|
||||
|
@ -57,6 +57,7 @@ export default class AccessibleTooltipButton extends React.PureComponent {
|
|||
/> : <div />;
|
||||
return (
|
||||
<AccessibleButton {...props} onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut} aria-label={title}>
|
||||
{ children }
|
||||
{ tip }
|
||||
</AccessibleButton>
|
||||
);
|
||||
|
|
|
@ -22,6 +22,8 @@ import sdk from '../../../index';
|
|||
import Modal from '../../../Modal';
|
||||
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({
|
||||
displayName: 'DeviceVerifyButtons',
|
||||
|
||||
|
|
|
@ -25,13 +25,13 @@ import sdk from '../../../index';
|
|||
* Parent components should supply an 'onSubmit' callback which returns a
|
||||
* 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
|
||||
* taken from the 'initialValue' property.
|
||||
*/
|
||||
export default class EditableTextContainer extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._unmounted = false;
|
||||
this.state = {
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {MatrixClient} from 'matrix-js-sdk';
|
||||
import FlairStore from '../../../stores/FlairStore';
|
||||
import dis from '../../../dispatcher';
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
|
||||
class FlairAvatar extends React.Component {
|
||||
|
@ -40,7 +40,7 @@ class FlairAvatar extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const httpUrl = this.context.matrixClient.mxcUrlToHttp(
|
||||
const httpUrl = this.context.mxcUrlToHttp(
|
||||
this.props.groupProfile.avatarUrl, 16, 16, 'scale', false);
|
||||
const tooltip = this.props.groupProfile.name ?
|
||||
`${this.props.groupProfile.name} (${this.props.groupProfile.groupId})`:
|
||||
|
@ -62,9 +62,7 @@ FlairAvatar.propTypes = {
|
|||
}),
|
||||
};
|
||||
|
||||
FlairAvatar.contextTypes = {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||
};
|
||||
FlairAvatar.contextType = MatrixClientContext;
|
||||
|
||||
export default class Flair extends React.Component {
|
||||
constructor() {
|
||||
|
@ -92,7 +90,7 @@ export default class Flair extends React.Component {
|
|||
for (const groupId of groups) {
|
||||
let groupProfile = null;
|
||||
try {
|
||||
groupProfile = await FlairStore.getGroupProfileCached(this.context.matrixClient, groupId);
|
||||
groupProfile = await FlairStore.getGroupProfileCached(this.context, groupId);
|
||||
} catch (err) {
|
||||
console.error('Could not get profile for group', groupId, err);
|
||||
}
|
||||
|
@ -134,6 +132,4 @@ Flair.propTypes = {
|
|||
groups: PropTypes.arrayOf(PropTypes.string),
|
||||
};
|
||||
|
||||
Flair.contextTypes = {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||
};
|
||||
Flair.contextType = MatrixClientContext;
|
||||
|
|
|
@ -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 dis from '../../../dispatcher';
|
||||
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 MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import { getDisplayAliasForRoom } from '../../../Rooms';
|
||||
import FlairStore from "../../../stores/FlairStore";
|
||||
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
|
||||
// HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`)
|
||||
|
@ -66,17 +67,6 @@ const Pill = createReactClass({
|
|||
isSelected: PropTypes.bool,
|
||||
},
|
||||
|
||||
|
||||
childContextTypes: {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||
},
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
matrixClient: this._matrixClient,
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
// ID/alias of the room/user
|
||||
|
@ -127,7 +117,7 @@ const Pill = createReactClass({
|
|||
}
|
||||
break;
|
||||
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;
|
||||
if (!localMember) {
|
||||
member = new RoomMember(null, resourceId);
|
||||
|
@ -276,15 +266,17 @@ const Pill = createReactClass({
|
|||
});
|
||||
|
||||
if (this.state.pillType) {
|
||||
return this.props.inMessage ?
|
||||
<a className={classes} href={href} onClick={onClick} title={resource} data-offset-key={this.props.offsetKey}>
|
||||
{ avatar }
|
||||
{ linkText }
|
||||
</a> :
|
||||
<span className={classes} title={resource} data-offset-key={this.props.offsetKey}>
|
||||
{ avatar }
|
||||
{ linkText }
|
||||
</span>;
|
||||
return <MatrixClientContext.Provider value={this._matrixClient}>
|
||||
{ this.props.inMessage ?
|
||||
<a className={classes} href={href} onClick={onClick} title={resource} data-offset-key={this.props.offsetKey}>
|
||||
{ avatar }
|
||||
{ linkText }
|
||||
</a> :
|
||||
<span className={classes} title={resource} data-offset-key={this.props.offsetKey}>
|
||||
{ avatar }
|
||||
{ linkText }
|
||||
</span> }
|
||||
</MatrixClientContext.Provider>;
|
||||
} else {
|
||||
// Deliberately render nothing if the URL isn't recognised
|
||||
return null;
|
||||
|
|
|
@ -21,10 +21,11 @@ import {_t} from '../../../languageHandler';
|
|||
import PropTypes from 'prop-types';
|
||||
import dis from '../../../dispatcher';
|
||||
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 SettingsStore from "../../../settings/SettingsStore";
|
||||
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
|
||||
// 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,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||
};
|
||||
static contextType = MatrixClientContext;
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
// The loaded events to be rendered as linear-replies
|
||||
|
@ -187,7 +186,7 @@ export default class ReplyThread extends React.Component {
|
|||
|
||||
componentWillMount() {
|
||||
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);
|
||||
// same event handler as Room.redaction as for both we just do forceUpdate
|
||||
this.room.on("Room.redactionCancelled", this.onRoomRedaction);
|
||||
|
@ -259,7 +258,7 @@ export default class ReplyThread extends React.Component {
|
|||
try {
|
||||
// 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
|
||||
await this.context.matrixClient.getEventTimeline(this.room.getUnfilteredTimelineSet(), eventId);
|
||||
await this.context.getEventTimeline(this.room.getUnfilteredTimelineSet(), eventId);
|
||||
} catch (e) {
|
||||
// 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).
|
||||
|
@ -300,7 +299,7 @@ export default class ReplyThread extends React.Component {
|
|||
} else if (this.state.loadedEv) {
|
||||
const ev = this.state.loadedEv;
|
||||
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">
|
||||
{
|
||||
_t('<a>In reply to</a> <pill>', {}, {
|
||||
|
|
|
@ -20,11 +20,13 @@ import sdk from '../../../index';
|
|||
import withValidation from './Validation';
|
||||
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 {
|
||||
static propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
domain: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func,
|
||||
value: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
@ -53,6 +55,7 @@ export default class RoomAliasField extends React.PureComponent {
|
|||
onValidate={this._onValidate}
|
||||
placeholder={_t("e.g. my-room")}
|
||||
onChange={this._onChange}
|
||||
value={this.props.value.substring(1, this.props.value.length - this.props.domain.length - 1)}
|
||||
maxLength={maxlength} />
|
||||
);
|
||||
}
|
||||
|
@ -61,7 +64,7 @@ export default class RoomAliasField extends React.PureComponent {
|
|||
if (this.props.onChange) {
|
||||
this.props.onChange(this._asFullAlias(ev.target.value));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_onValidate = async (fieldState) => {
|
||||
const result = await this._validationRules(fieldState);
|
||||
|
|
|
@ -24,8 +24,8 @@ export default class SyntaxHighlight extends React.Component {
|
|||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._ref = this._ref.bind(this);
|
||||
}
|
||||
|
|
|
@ -20,17 +20,16 @@ import React, {createRef} from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import classNames from 'classnames';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import sdk from '../../../index';
|
||||
import dis from '../../../dispatcher';
|
||||
import {_t} from '../../../languageHandler';
|
||||
import { isOnlyCtrlOrCmdIgnoreShiftKeyEvent } from '../../../Keyboard';
|
||||
import * as FormattingUtils from '../../../utils/FormattingUtils';
|
||||
|
||||
import FlairStore from '../../../stores/FlairStore';
|
||||
import GroupStore from '../../../stores/GroupStore';
|
||||
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 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,
|
||||
},
|
||||
|
||||
contextTypes: {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||
statics: {
|
||||
contextType: MatrixClientContext,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
|
@ -83,7 +82,7 @@ export default createReactClass({
|
|||
_onFlairStoreUpdated() {
|
||||
if (this.unmounted) return;
|
||||
FlairStore.getGroupProfileCached(
|
||||
this.context.matrixClient,
|
||||
this.context,
|
||||
this.props.tag,
|
||||
).then((profile) => {
|
||||
if (this.unmounted) return;
|
||||
|
@ -114,12 +113,10 @@ export default createReactClass({
|
|||
},
|
||||
|
||||
onMouseOver: function() {
|
||||
console.log("DEBUG onMouseOver");
|
||||
this.setState({hover: true});
|
||||
},
|
||||
|
||||
onMouseOut: function() {
|
||||
console.log("DEBUG onMouseOut");
|
||||
this.setState({hover: false});
|
||||
},
|
||||
|
||||
|
@ -142,12 +139,11 @@ export default createReactClass({
|
|||
|
||||
render: function() {
|
||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||
const Tooltip = sdk.getComponent('elements.Tooltip');
|
||||
const profile = this.state.profile || {};
|
||||
const name = profile.name || this.props.tag;
|
||||
const avatarHeight = 40;
|
||||
|
||||
const httpUrl = profile.avatarUrl ? this.context.matrixClient.mxcUrlToHttp(
|
||||
const httpUrl = profile.avatarUrl ? this.context.mxcUrlToHttp(
|
||||
profile.avatarUrl, avatarHeight, avatarHeight, "crop",
|
||||
) : null;
|
||||
|
||||
|
@ -166,9 +162,6 @@ export default createReactClass({
|
|||
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
|
||||
const contextButton = this.state.hover || this.state.menuDisplayed ?
|
||||
<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>
|
||||
<ContextMenuButton
|
||||
className={className}
|
||||
onClick={this.onClick}
|
||||
onContextMenu={this.openMenu}
|
||||
label={_t("Options")}
|
||||
isExpanded={this.state.menuDisplayed}
|
||||
>
|
||||
<AccessibleTooltipButton className={className} onClick={this.onClick} onContextMenu={this.openMenu} title={name}>
|
||||
<div className="mx_TagTile_avatar" onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut}>
|
||||
<BaseAvatar
|
||||
name={name}
|
||||
|
@ -202,11 +190,10 @@ export default createReactClass({
|
|||
width={avatarHeight}
|
||||
height={avatarHeight}
|
||||
/>
|
||||
{ tip }
|
||||
{ contextButton }
|
||||
{ badgeElement }
|
||||
</div>
|
||||
</ContextMenuButton>
|
||||
</AccessibleTooltipButton>
|
||||
|
||||
{ contextMenu }
|
||||
</React.Fragment>;
|
||||
|
|
|
@ -16,54 +16,12 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import EMOJIBASE from 'emojibase-data/en/compact.json';
|
||||
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
import * as recent from './recent';
|
||||
|
||||
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();
|
||||
});
|
||||
import {DATA_BY_CATEGORY, getEmojiFromUnicode} from "../../../emoji";
|
||||
|
||||
export const CATEGORY_HEADER_HEIGHT = 22;
|
||||
export const EMOJI_HEIGHT = 37;
|
||||
|
@ -91,7 +49,7 @@ class EmojiPicker extends React.Component {
|
|||
|
||||
// Convert recent emoji characters to emoji data, removing unknowns.
|
||||
this.recentlyUsed = recent.get()
|
||||
.map(unicode => DATA_BY_EMOJI[unicode])
|
||||
.map(unicode => getEmojiFromUnicode(unicode))
|
||||
.filter(data => !!data);
|
||||
this.memoizedDataByCategory = {
|
||||
recent: this.recentlyUsed,
|
||||
|
|
|
@ -19,15 +19,15 @@ import PropTypes from 'prop-types';
|
|||
|
||||
import sdk from '../../../index';
|
||||
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 data = findEmojiData(emoji);
|
||||
const data = getEmojiFromUnicode(emoji);
|
||||
if (!data) {
|
||||
throw new Error(`Emoji ${emoji} doesn't exist in emojibase`);
|
||||
}
|
||||
// Prefer our unicode value for quick reactions (which does not have
|
||||
// variation selectors).
|
||||
// Prefer our unicode value for quick reactions as we sometimes use variation selectors.
|
||||
return Object.assign({}, data, { unicode: emoji });
|
||||
});
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ module.exports = createReactClass({
|
|||
render: function() {
|
||||
return (
|
||||
<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">
|
||||
{ _t('You are not receiving desktop notifications') } <a className="mx_MatrixToolbar_link" onClick={ this.onClick }> { _t('Enable them now') }</a>
|
||||
</div>
|
||||
|
|
|
@ -97,7 +97,7 @@ export default createReactClass({
|
|||
}
|
||||
return (
|
||||
<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">
|
||||
{_t("A new version of Riot is available.")}
|
||||
</div>
|
||||
|
|
|
@ -19,13 +19,13 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import sdk from '../../../index';
|
||||
import dis from '../../../dispatcher';
|
||||
import {_t} from '../../../languageHandler';
|
||||
import classNames from 'classnames';
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import {ContextMenu, ContextMenuButton, toRightOf} from "../../structures/ContextMenu";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
// XXX this class copies a lot from RoomTile.js
|
||||
export default createReactClass({
|
||||
|
@ -35,8 +35,8 @@ export default createReactClass({
|
|||
group: PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
contextTypes: {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||
statics: {
|
||||
contextType: MatrixClientContext,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -58,7 +58,7 @@ export default createReactClass({
|
|||
onMouseEnter: function() {
|
||||
const state = {hover: true};
|
||||
// Only allow non-guests to access the context menu
|
||||
if (!this.context.matrixClient.isGuest()) {
|
||||
if (!this.context.isGuest()) {
|
||||
state.badgeHover = true;
|
||||
}
|
||||
this.setState(state);
|
||||
|
@ -118,7 +118,7 @@ export default createReactClass({
|
|||
|
||||
const groupName = this.props.group.name || this.props.group.groupId;
|
||||
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} />;
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import dis from '../../../dispatcher';
|
||||
import Modal from '../../../Modal';
|
||||
import sdk from '../../../index';
|
||||
|
@ -26,12 +25,13 @@ import { _t } from '../../../languageHandler';
|
|||
import { GroupMemberType } from '../../../groups';
|
||||
import GroupStore from '../../../stores/GroupStore';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
module.exports = createReactClass({
|
||||
displayName: 'GroupMemberInfo',
|
||||
|
||||
contextTypes: {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||
statics: {
|
||||
contextType: MatrixClientContext,
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
|
@ -85,7 +85,7 @@ module.exports = createReactClass({
|
|||
_onKick: function() {
|
||||
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
||||
Modal.createDialog(ConfirmUserActionDialog, {
|
||||
matrixClient: this.context.matrixClient,
|
||||
matrixClient: this.context,
|
||||
groupMember: this.props.groupMember,
|
||||
action: this.state.isUserInvited ? _t('Disinvite') : _t('Remove from community'),
|
||||
title: this.state.isUserInvited ? _t('Disinvite this user from community?')
|
||||
|
@ -95,7 +95,7 @@ module.exports = createReactClass({
|
|||
if (!proceed) return;
|
||||
|
||||
this.setState({removingUser: true});
|
||||
this.context.matrixClient.removeUserFromGroup(
|
||||
this.context.removeUserFromGroup(
|
||||
this.props.groupId, this.props.groupMember.userId,
|
||||
).then(() => {
|
||||
// return to the user list
|
||||
|
@ -171,7 +171,7 @@ module.exports = createReactClass({
|
|||
const avatarUrl = this.props.groupMember.avatarUrl;
|
||||
let avatarElement;
|
||||
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">
|
||||
<img src={httpUrl} />
|
||||
</div>);
|
||||
|
|
|
@ -19,10 +19,10 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import sdk from '../../../index';
|
||||
import dis from '../../../dispatcher';
|
||||
import { GroupMemberType } from '../../../groups';
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
export default createReactClass({
|
||||
displayName: 'GroupMemberTile',
|
||||
|
@ -36,8 +36,8 @@ export default createReactClass({
|
|||
return {};
|
||||
},
|
||||
|
||||
contextTypes: {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||
statics: {
|
||||
contextType: MatrixClientContext,
|
||||
},
|
||||
|
||||
onClick: function(e) {
|
||||
|
@ -53,7 +53,7 @@ export default createReactClass({
|
|||
const EntityTile = sdk.getComponent('rooms.EntityTile');
|
||||
|
||||
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,
|
||||
36, 36, 'crop',
|
||||
);
|
||||
|
|
|
@ -17,18 +17,18 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import dis from '../../../dispatcher';
|
||||
import Modal from '../../../Modal';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import GroupStore from '../../../stores/GroupStore';
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
module.exports = createReactClass({
|
||||
displayName: 'GroupRoomInfo',
|
||||
|
||||
contextTypes: {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||
statics: {
|
||||
contextType: MatrixClientContext,
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
|
@ -206,7 +206,7 @@ module.exports = createReactClass({
|
|||
const avatarUrl = this.state.groupRoom.avatarUrl;
|
||||
let avatarElement;
|
||||
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">
|
||||
<img src={httpUrl} />
|
||||
</div>);
|
||||
|
|
|
@ -17,10 +17,10 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import {MatrixClient} from 'matrix-js-sdk';
|
||||
import sdk from '../../../index';
|
||||
import dis from '../../../dispatcher';
|
||||
import { GroupRoomType } from '../../../groups';
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
const GroupRoomTile = createReactClass({
|
||||
displayName: 'GroupRoomTile',
|
||||
|
@ -41,7 +41,7 @@ const GroupRoomTile = createReactClass({
|
|||
render: function() {
|
||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const avatarUrl = this.context.matrixClient.mxcUrlToHttp(
|
||||
const avatarUrl = this.context.mxcUrlToHttp(
|
||||
this.props.groupRoom.avatarUrl,
|
||||
36, 36, 'crop',
|
||||
);
|
||||
|
@ -66,9 +66,7 @@ const GroupRoomTile = createReactClass({
|
|||
},
|
||||
});
|
||||
|
||||
GroupRoomTile.contextTypes = {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||
};
|
||||
GroupRoomTile.contextType = MatrixClientContext;
|
||||
|
||||
|
||||
export default GroupRoomTile;
|
||||
|
|
|
@ -17,11 +17,11 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import {MatrixClient} from 'matrix-js-sdk';
|
||||
import { Draggable, Droppable } from 'react-beautiful-dnd';
|
||||
import sdk from '../../../index';
|
||||
import dis from '../../../dispatcher';
|
||||
import FlairStore from '../../../stores/FlairStore';
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
function nop() {}
|
||||
|
||||
|
@ -37,8 +37,8 @@ const GroupTile = createReactClass({
|
|||
draggable: PropTypes.bool,
|
||||
},
|
||||
|
||||
contextTypes: {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||
statics: {
|
||||
contextType: MatrixClientContext,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
|
@ -56,7 +56,7 @@ const GroupTile = createReactClass({
|
|||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
FlairStore.getGroupProfileCached(this.context.matrixClient, this.props.groupId).then((profile) => {
|
||||
FlairStore.getGroupProfileCached(this.context, this.props.groupId).then((profile) => {
|
||||
this.setState({profile});
|
||||
}).catch((err) => {
|
||||
console.error('Error whilst getting cached profile for GroupTile', err);
|
||||
|
@ -80,7 +80,7 @@ const GroupTile = createReactClass({
|
|||
const descElement = this.props.showDescription ?
|
||||
<div className="mx_GroupTile_desc">{ profile.shortDescription }</div> :
|
||||
<div />;
|
||||
const httpUrl = profile.avatarUrl ? this.context.matrixClient.mxcUrlToHttp(
|
||||
const httpUrl = profile.avatarUrl ? this.context.mxcUrlToHttp(
|
||||
profile.avatarUrl, avatarHeight, avatarHeight, "crop") : null;
|
||||
|
||||
let avatarElement = (
|
||||
|
|
|
@ -15,17 +15,16 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import sdk from '../../../index';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
export default createReactClass({
|
||||
displayName: 'GroupUserSettings',
|
||||
|
||||
contextTypes: {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||
statics: {
|
||||
contextType: MatrixClientContext,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
|
@ -36,7 +35,7 @@ export default createReactClass({
|
|||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this.context.matrixClient.getJoinedGroups().then((result) => {
|
||||
this.context.getJoinedGroups().then((result) => {
|
||||
this.setState({groups: result.groups || [], error: null});
|
||||
}, (err) => {
|
||||
console.error(err);
|
||||
|
|
|
@ -26,6 +26,7 @@ import {decryptFile} from '../../../utils/DecryptFile';
|
|||
import Tinter from '../../../Tinter';
|
||||
import request from 'browser-request';
|
||||
import Modal from '../../../Modal';
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
|
||||
|
||||
// A cached tinted copy of require("../../../../res/img/download.svg")
|
||||
|
@ -214,10 +215,6 @@ module.exports = createReactClass({
|
|||
tileShape: PropTypes.string,
|
||||
},
|
||||
|
||||
contextTypes: {
|
||||
appConfig: PropTypes.object,
|
||||
},
|
||||
|
||||
/**
|
||||
* Extracts a human readable label for the file attachment to use as
|
||||
* link text.
|
||||
|
@ -360,8 +357,9 @@ module.exports = createReactClass({
|
|||
|
||||
// If the attachment is encryped then put the link inside an iframe.
|
||||
let renderer_url = DEFAULT_CROSS_ORIGIN_RENDERER;
|
||||
if (this.context.appConfig && this.context.appConfig.cross_origin_renderer_url) {
|
||||
renderer_url = this.context.appConfig.cross_origin_renderer_url;
|
||||
const appConfig = SdkConfig.get();
|
||||
if (appConfig && appConfig.cross_origin_renderer_url) {
|
||||
renderer_url = appConfig.cross_origin_renderer_url;
|
||||
}
|
||||
renderer_url += "?origin=" + encodeURIComponent(window.location.origin);
|
||||
return (
|
||||
|
|
|
@ -18,7 +18,6 @@ limitations under the License.
|
|||
|
||||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
|
||||
import MFileBody from './MFileBody';
|
||||
import Modal from '../../../Modal';
|
||||
|
@ -26,6 +25,7 @@ import sdk from '../../../index';
|
|||
import { decryptFile } from '../../../utils/DecryptFile';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
export default class MImageBody extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -39,9 +39,7 @@ export default class MImageBody extends React.Component {
|
|||
maxImageHeight: PropTypes.number,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||
};
|
||||
static contextType = MatrixClientContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -71,7 +69,7 @@ export default class MImageBody extends React.Component {
|
|||
|
||||
componentWillMount() {
|
||||
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!
|
||||
|
@ -174,7 +172,7 @@ export default class MImageBody extends React.Component {
|
|||
if (content.file !== undefined) {
|
||||
return this.state.decryptedUrl;
|
||||
} 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,
|
||||
// given we deliberately don't thumbnail them serverside to prevent
|
||||
// billion lol attacks and similar
|
||||
return this.context.matrixClient.mxcUrlToHttp(
|
||||
return this.context.mxcUrlToHttp(
|
||||
content.info.thumbnail_url,
|
||||
thumbWidth,
|
||||
thumbHeight,
|
||||
|
@ -221,7 +219,7 @@ export default class MImageBody extends React.Component {
|
|||
pixelRatio === 1.0 ||
|
||||
(!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 {
|
||||
// we should only request thumbnails if the image is bigger than 800x600
|
||||
// (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
|
||||
// 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).
|
||||
return this.context.matrixClient.mxcUrlToHttp(
|
||||
return this.context.mxcUrlToHttp(
|
||||
content.url,
|
||||
thumbWidth,
|
||||
thumbHeight,
|
||||
|
@ -251,7 +249,7 @@ export default class MImageBody extends React.Component {
|
|||
// download the original image otherwise, so we can scale it client side
|
||||
// to take pixelRatio into account.
|
||||
// ( no width/height means we want the original image)
|
||||
return this.context.matrixClient.mxcUrlToHttp(
|
||||
return this.context.mxcUrlToHttp(
|
||||
content.url,
|
||||
);
|
||||
}
|
||||
|
@ -308,7 +306,7 @@ export default class MImageBody extends React.Component {
|
|||
|
||||
componentWillUnmount() {
|
||||
this.unmounted = true;
|
||||
this.context.matrixClient.removeListener('sync', this.onClientSync);
|
||||
this.context.removeListener('sync', this.onClientSync);
|
||||
this._afterComponentWillUnmount();
|
||||
|
||||
if (this.state.decryptedUrl) {
|
||||
|
|
|
@ -25,7 +25,7 @@ import dis from '../../../dispatcher';
|
|||
import Modal from '../../../Modal';
|
||||
import {aboveLeftOf, ContextMenu, ContextMenuButton, useContextMenu} from '../../structures/ContextMenu';
|
||||
import { isContentActionable, canEditContent } from '../../../utils/EventUtils';
|
||||
import {RoomContext} from "../../structures/RoomView";
|
||||
import RoomContext from "../../../contexts/RoomContext";
|
||||
|
||||
const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange}) => {
|
||||
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
||||
|
@ -88,7 +88,7 @@ const ReactButton = ({mxEvent, reactions, onFocusChange}) => {
|
|||
if (menuDisplayed) {
|
||||
const buttonRect = button.current.getBoundingClientRect();
|
||||
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} />
|
||||
</ContextMenu>;
|
||||
}
|
||||
|
@ -117,9 +117,7 @@ export default class MessageActionBar extends React.PureComponent {
|
|||
onFocusChange: PropTypes.func,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
room: RoomContext,
|
||||
};
|
||||
static contextType = RoomContext;
|
||||
|
||||
componentDidMount() {
|
||||
this.props.mxEvent.on("Event.decrypted", this.onDecrypted);
|
||||
|
@ -164,12 +162,12 @@ export default class MessageActionBar extends React.PureComponent {
|
|||
let editButton;
|
||||
|
||||
if (isContentActionable(this.props.mxEvent)) {
|
||||
if (this.context.room.canReact) {
|
||||
if (this.context.canReact) {
|
||||
reactButton = (
|
||||
<ReactButton mxEvent={this.props.mxEvent} reactions={this.props.reactions} onFocusChange={this.onFocusChange} />
|
||||
);
|
||||
}
|
||||
if (this.context.room.canReply) {
|
||||
if (this.context.canReply) {
|
||||
replyButton = <AccessibleButton
|
||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_replyButton"
|
||||
title={_t("Reply")}
|
||||
|
|
|
@ -17,11 +17,11 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import {MatrixClient} from 'matrix-js-sdk';
|
||||
import Flair from '../elements/Flair.js';
|
||||
import FlairStore from '../../../stores/FlairStore';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {getUserNameColorClass} from '../../../utils/FormattingUtils';
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
export default createReactClass({
|
||||
displayName: 'SenderProfile',
|
||||
|
@ -31,8 +31,8 @@ export default createReactClass({
|
|||
onClick: PropTypes.func,
|
||||
},
|
||||
|
||||
contextTypes: {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||
statics: {
|
||||
contextType: MatrixClientContext,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
|
@ -47,18 +47,18 @@ export default createReactClass({
|
|||
this._updateRelatedGroups();
|
||||
|
||||
FlairStore.getPublicisedGroupsCached(
|
||||
this.context.matrixClient, this.props.mxEvent.getSender(),
|
||||
this.context, this.props.mxEvent.getSender(),
|
||||
).then((userGroups) => {
|
||||
if (this.unmounted) return;
|
||||
this.setState({userGroups});
|
||||
});
|
||||
|
||||
this.context.matrixClient.on('RoomState.events', this.onRoomStateEvents);
|
||||
this.context.on('RoomState.events', this.onRoomStateEvents);
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unmounted = true;
|
||||
this.context.matrixClient.removeListener('RoomState.events', this.onRoomStateEvents);
|
||||
this.context.removeListener('RoomState.events', this.onRoomStateEvents);
|
||||
},
|
||||
|
||||
onRoomStateEvents(event) {
|
||||
|
@ -71,7 +71,7 @@ export default createReactClass({
|
|||
|
||||
_updateRelatedGroups() {
|
||||
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;
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import React, {useCallback, useMemo, useState, useEffect} from 'react';
|
||||
import React, {useCallback, useMemo, useState, useEffect, useContext} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import {Group, RoomMember, User} from 'matrix-js-sdk';
|
||||
|
@ -37,9 +37,9 @@ import MultiInviter from "../../../utils/MultiInviter";
|
|||
import GroupStore from "../../../stores/GroupStore";
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import E2EIcon from "../rooms/E2EIcon";
|
||||
import withLegacyMatrixClient from "../../../utils/withLegacyMatrixClient";
|
||||
import {useEventEmitter} from "../../../hooks/useEventEmitter";
|
||||
import {textualPowerLevel} from '../../../Roles';
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
const _disambiguateDevices = (devices) => {
|
||||
const names = Object.create(null);
|
||||
|
@ -74,17 +74,6 @@ const _getE2EStatus = (cli, userId, devices) => {
|
|||
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) {
|
||||
const dmRooms = DMRoomMap.shared().getDMRoomsForUserId(userId);
|
||||
const lastActiveRoom = dmRooms.reduce((lastActiveRoom, roomId) => {
|
||||
|
@ -129,17 +118,20 @@ function verifyDevice(userId, device) {
|
|||
}
|
||||
|
||||
function DeviceItem({userId, device}) {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const deviceTrust = cli.checkDeviceTrust(userId, device.deviceId);
|
||||
|
||||
const classes = classNames("mx_UserInfo_device", {
|
||||
mx_UserInfo_device_verified: device.isVerified(),
|
||||
mx_UserInfo_device_unverified: !device.isVerified(),
|
||||
mx_UserInfo_device_verified: deviceTrust.isVerified(),
|
||||
mx_UserInfo_device_unverified: !deviceTrust.isVerified(),
|
||||
});
|
||||
const iconClasses = classNames("mx_E2EIcon", {
|
||||
mx_E2EIcon_verified: device.isVerified(),
|
||||
mx_E2EIcon_warning: !device.isVerified(),
|
||||
mx_E2EIcon_verified: deviceTrust.isVerified(),
|
||||
mx_E2EIcon_warning: !deviceTrust.isVerified(),
|
||||
});
|
||||
|
||||
const onDeviceClick = () => {
|
||||
if (!device.isVerified()) {
|
||||
if (!deviceTrust.isVerified()) {
|
||||
verifyDevice(userId, device);
|
||||
}
|
||||
};
|
||||
|
@ -147,7 +139,7 @@ function DeviceItem({userId, device}) {
|
|||
const deviceName = device.ambiguous ?
|
||||
(device.getDisplayName() ? device.getDisplayName() : "") + " (" + device.deviceId + ")" :
|
||||
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}>
|
||||
<div className={iconClasses} />
|
||||
<div className="mx_UserInfo_device_name">{deviceName}</div>
|
||||
|
@ -157,6 +149,7 @@ function DeviceItem({userId, device}) {
|
|||
|
||||
function DevicesSection({devices, userId, loading}) {
|
||||
const Spinner = sdk.getComponent("elements.Spinner");
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
const [isExpanded, setExpanded] = useState(false);
|
||||
|
||||
|
@ -167,9 +160,21 @@ function DevicesSection({devices, userId, loading}) {
|
|||
if (devices === null) {
|
||||
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 verifiedDevices = devices.filter(d => d.isVerified());
|
||||
const unverifiedDevices = [];
|
||||
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;
|
||||
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 insertPillButton = null;
|
||||
let inviteUserButton = null;
|
||||
|
@ -313,14 +320,6 @@ const UserOptionsSection = withLegacyMatrixClient(({matrixClient: cli, member, i
|
|||
</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 (
|
||||
<div className="mx_UserInfo_container">
|
||||
|
@ -332,11 +331,10 @@ const UserOptionsSection = withLegacyMatrixClient(({matrixClient: cli, member, i
|
|||
{ insertPillButton }
|
||||
{ inviteUserButton }
|
||||
{ ignoreButton }
|
||||
{ unverifyButton }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const _warnSelfDemote = async () => {
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
|
@ -404,7 +402,12 @@ const useRoomPowerLevels = (cli, room) => {
|
|||
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 ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
||||
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}>
|
||||
{ kickLabel }
|
||||
</AccessibleButton>;
|
||||
});
|
||||
};
|
||||
|
||||
const RedactMessagesButton = ({member}) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
const RedactMessagesButton = withLegacyMatrixClient(({matrixClient: cli, member}) => {
|
||||
const onRedactAllMessages = async () => {
|
||||
const {roomId, userId} = member;
|
||||
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}>
|
||||
{ _t("Remove recent messages") }
|
||||
</AccessibleButton>;
|
||||
});
|
||||
};
|
||||
|
||||
const BanToggleButton = ({member, startUpdating, stopUpdating}) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
const BanToggleButton = withLegacyMatrixClient(({matrixClient: cli, member, startUpdating, stopUpdating}) => {
|
||||
const onBanOrUnban = async () => {
|
||||
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
||||
const {finished} = Modal.createTrackedDialog(
|
||||
|
@ -573,207 +580,209 @@ const BanToggleButton = withLegacyMatrixClient(({matrixClient: cli, member, star
|
|||
return <AccessibleButton className={classes} onClick={onBanOrUnban}>
|
||||
{ label }
|
||||
</AccessibleButton>;
|
||||
});
|
||||
};
|
||||
|
||||
const MuteToggleButton = withLegacyMatrixClient(
|
||||
({matrixClient: cli, member, room, powerLevels, startUpdating, stopUpdating}) => {
|
||||
const isMuted = _isMuted(member, powerLevels);
|
||||
const onMuteToggle = async () => {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
const roomId = member.roomId;
|
||||
const target = member.userId;
|
||||
const MuteToggleButton = ({member, room, powerLevels, startUpdating, stopUpdating}) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
// 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;
|
||||
}
|
||||
// Don't show the mute/unmute option if the user is not in the room
|
||||
if (member.membership !== "join") return null;
|
||||
|
||||
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
|
||||
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", "");
|
||||
if (!powerLevelEvent) return;
|
||||
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
|
||||
if (!powerLevelEvent) return;
|
||||
|
||||
const powerLevels = powerLevelEvent.getContent();
|
||||
const levelToSend = (
|
||||
(powerLevels.events ? powerLevels.events["m.room.message"] : null) ||
|
||||
powerLevels.events_default
|
||||
);
|
||||
let level;
|
||||
if (isMuted) { // unmute
|
||||
level = levelToSend;
|
||||
} else { // mute
|
||||
level = levelToSend - 1;
|
||||
}
|
||||
level = parseInt(level);
|
||||
const powerLevels = powerLevelEvent.getContent();
|
||||
const levelToSend = (
|
||||
(powerLevels.events ? powerLevels.events["m.room.message"] : null) ||
|
||||
powerLevels.events_default
|
||||
);
|
||||
let level;
|
||||
if (isMuted) { // unmute
|
||||
level = levelToSend;
|
||||
} else { // mute
|
||||
level = levelToSend - 1;
|
||||
}
|
||||
level = parseInt(level);
|
||||
|
||||
if (!isNaN(level)) {
|
||||
startUpdating();
|
||||
cli.setPowerLevel(roomId, target, level, powerLevelEvent).then(() => {
|
||||
// NO-OP; rely on the m.room.member event coming down else we could
|
||||
// get out of sync if we force setState here!
|
||||
console.log("Mute toggle success");
|
||||
}, function(err) {
|
||||
console.error("Mute error: " + err);
|
||||
Modal.createTrackedDialog('Failed to mute user', '', ErrorDialog, {
|
||||
title: _t("Error"),
|
||||
description: _t("Failed to mute user"),
|
||||
});
|
||||
}).finally(() => {
|
||||
stopUpdating();
|
||||
if (!isNaN(level)) {
|
||||
startUpdating();
|
||||
cli.setPowerLevel(roomId, target, level, powerLevelEvent).then(() => {
|
||||
// NO-OP; rely on the m.room.member event coming down else we could
|
||||
// get out of sync if we force setState here!
|
||||
console.log("Mute toggle success");
|
||||
}, function(err) {
|
||||
console.error("Mute error: " + err);
|
||||
Modal.createTrackedDialog('Failed to mute user', '', ErrorDialog, {
|
||||
title: _t("Error"),
|
||||
description: _t("Failed to mute user"),
|
||||
});
|
||||
}
|
||||
}).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", {
|
||||
mx_UserInfo_destructive: !isMuted,
|
||||
});
|
||||
GroupStore.registerListener(groupId, onGroupStoreUpdated);
|
||||
onGroupStoreUpdated();
|
||||
// Handle unmount
|
||||
return () => {
|
||||
unmounted = true;
|
||||
GroupStore.unregisterListener(onGroupStoreUpdated);
|
||||
};
|
||||
}, [groupId, groupMember.userId]);
|
||||
|
||||
const muteLabel = isMuted ? _t("Unmute") : _t("Mute");
|
||||
return <AccessibleButton className={classes} onClick={onMuteToggle}>
|
||||
{ muteLabel }
|
||||
</AccessibleButton>;
|
||||
},
|
||||
);
|
||||
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 RoomAdminToolsContainer = withLegacyMatrixClient(
|
||||
({matrixClient: cli, room, children, member, startUpdating, stopUpdating, powerLevels}) => {
|
||||
let kickButton;
|
||||
let banButton;
|
||||
let muteButton;
|
||||
let redactButton;
|
||||
const [proceed] = await finished;
|
||||
if (!proceed) return;
|
||||
|
||||
const editPowerLevel = (
|
||||
(powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) ||
|
||||
powerLevels.state_default
|
||||
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>
|
||||
);
|
||||
|
||||
const me = room.getMember(cli.getUserId());
|
||||
const isMe = me.userId === member.userId;
|
||||
const canAffectUser = member.powerLevel < me.powerLevel || isMe;
|
||||
// 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>;*/
|
||||
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <GenericAdminToolsContainer>
|
||||
{ kickButton }
|
||||
{ children }
|
||||
</GenericAdminToolsContainer>;
|
||||
}
|
||||
|
||||
if (kickButton || banButton || muteButton || redactButton || children) {
|
||||
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 />;
|
||||
},
|
||||
);
|
||||
return <div />;
|
||||
};
|
||||
|
||||
const GroupMember = PropTypes.shape({
|
||||
userId: PropTypes.string.isRequired,
|
||||
|
@ -849,7 +858,7 @@ function useRoomPermissions(cli, room, user) {
|
|||
return roomPermissions;
|
||||
}
|
||||
|
||||
const PowerLevelSection = withLegacyMatrixClient(({matrixClient: cli, user, room, roomPermissions, powerLevels}) => {
|
||||
const PowerLevelSection = ({user, room, roomPermissions, powerLevels}) => {
|
||||
const [isEditing, setEditing] = useState(false);
|
||||
if (room && user.roomId) { // is in room
|
||||
if (isEditing) {
|
||||
|
@ -876,9 +885,11 @@ const PowerLevelSection = withLegacyMatrixClient(({matrixClient: cli, user, room
|
|||
} else {
|
||||
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 [selectedPowerLevel, setSelectedPowerLevel] = useState(parseInt(user.powerLevel, 10));
|
||||
const [isDirty, setIsDirty] = useState(false);
|
||||
|
@ -953,7 +964,7 @@ const PowerLevelEditor = withLegacyMatrixClient(({matrixClient: cli, user, room,
|
|||
});
|
||||
|
||||
const [confirmed] = await finished;
|
||||
if (confirmed) return;
|
||||
if (!confirmed) return;
|
||||
}
|
||||
await _applyPowerChange(roomId, target, powerLevel, powerLevelEvent);
|
||||
} finally {
|
||||
|
@ -982,10 +993,11 @@ const PowerLevelEditor = withLegacyMatrixClient(({matrixClient: cli, user, room,
|
|||
{buttonOrSpinner}
|
||||
</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
|
||||
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 ?
|
||||
(<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 = (
|
||||
<div className="mx_UserInfo_container">
|
||||
<h3>{ _t("Security") }</h3>
|
||||
<p>{ text }</p>
|
||||
<AccessibleButton className="mx_UserInfo_verify" onClick={() => verifyDevice(user.userId, null)}>{_t("Verify")}</AccessibleButton>
|
||||
{verifyButton}
|
||||
{ devicesSection }
|
||||
</div>
|
||||
);
|
||||
|
@ -1316,7 +1337,7 @@ const UserInfo = withLegacyMatrixClient(({matrixClient: cli, user, groupId, room
|
|||
</AutoHideScrollbar>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
UserInfo.propTypes = {
|
||||
user: PropTypes.oneOfType([
|
||||
|
|
|
@ -15,6 +15,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import EditableItemList from "../elements/EditableItemList";
|
||||
|
||||
const React = require('react');
|
||||
import PropTypes from 'prop-types';
|
||||
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||
|
@ -22,8 +24,33 @@ const sdk = require("../../../index");
|
|||
import { _t } from '../../../languageHandler';
|
||||
import Field from "../elements/Field";
|
||||
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
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 {
|
||||
static propTypes = {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
|
@ -47,7 +74,6 @@ export default class AliasSettings extends React.Component {
|
|||
remoteDomains: [], // [ domain.com, foobar.com ]
|
||||
canonicalAlias: null, // #canonical:domain.com
|
||||
updatingCanonicalAlias: false,
|
||||
newItem: "",
|
||||
};
|
||||
|
||||
const localDomain = MatrixClientPeg.get().getDomain();
|
||||
|
@ -181,7 +207,6 @@ export default class AliasSettings extends React.Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
const EditableItemList = sdk.getComponent("elements.EditableItemList");
|
||||
const localDomain = MatrixClientPeg.get().getDomain();
|
||||
|
||||
let found = false;
|
||||
|
@ -233,7 +258,7 @@ export default class AliasSettings extends React.Component {
|
|||
return (
|
||||
<div className='mx_AliasSettings'>
|
||||
{canonicalAliasSection}
|
||||
<EditableItemList
|
||||
<EditableAliasesList
|
||||
id="roomAliases"
|
||||
className={"mx_RoomSettings_localAliases"}
|
||||
items={this.state.domainToAliases[localDomain] || []}
|
||||
|
@ -248,6 +273,7 @@ export default class AliasSettings extends React.Component {
|
|||
placeholder={_t(
|
||||
'New address (e.g. #foo:%(localDomain)s)', {localDomain: localDomain},
|
||||
)}
|
||||
domain={localDomain}
|
||||
/>
|
||||
{remoteAliasesSection}
|
||||
</div>
|
||||
|
|
|
@ -16,11 +16,12 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {MatrixEvent, MatrixClient} from 'matrix-js-sdk';
|
||||
import {MatrixEvent} from 'matrix-js-sdk';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import Modal from '../../../Modal';
|
||||
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
const GROUP_ID_REGEX = /\+\S+:\S+/;
|
||||
|
||||
|
@ -31,9 +32,7 @@ export default class RelatedGroupSettings extends React.Component {
|
|||
relatedGroupsEvent: PropTypes.instanceOf(MatrixEvent),
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||
};
|
||||
static contextType = MatrixClientContext;
|
||||
|
||||
static defaultProps = {
|
||||
canSetRelatedGroups: false,
|
||||
|
@ -49,7 +48,7 @@ export default class RelatedGroupSettings extends React.Component {
|
|||
}
|
||||
|
||||
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,
|
||||
}, '').catch((err) => {
|
||||
console.error(err);
|
||||
|
@ -99,7 +98,7 @@ export default class RelatedGroupSettings extends React.Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
const localDomain = this.context.matrixClient.getDomain();
|
||||
const localDomain = this.context.getDomain();
|
||||
const EditableItemList = sdk.getComponent('elements.EditableItemList');
|
||||
return <div>
|
||||
<EditableItemList
|
||||
|
|
|
@ -34,11 +34,11 @@ import {parsePlainTextMessage} from '../../../editor/deserialize';
|
|||
import {renderModel} from '../../../editor/render';
|
||||
import {Room} from 'matrix-js-sdk';
|
||||
import TypingStore from "../../../stores/TypingStore";
|
||||
import EMOJIBASE from 'emojibase-data/en/compact.json';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import EMOTICON_REGEX from 'emojibase-regex/emoticon';
|
||||
import sdk from '../../../index';
|
||||
import {Key} from "../../../Keyboard";
|
||||
import {EMOTICON_TO_EMOJI} from "../../../emoji";
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
autoComplete: null,
|
||||
};
|
||||
|
@ -108,7 +108,8 @@ export default class BasicMessageEditor extends React.Component {
|
|||
const emoticonMatch = REGEX_EMOTICON_WHITESPACE.exec(range.text);
|
||||
if (emoticonMatch) {
|
||||
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) {
|
||||
const {partCreator} = model;
|
||||
const hasPrecedingSpace = emoticonMatch[0][0] === " ";
|
||||
|
|
|
@ -26,11 +26,11 @@ import {findEditableEvent} from '../../../utils/EventUtils';
|
|||
import {parseEvent} from '../../../editor/deserialize';
|
||||
import {PartCreator} from '../../../editor/parts';
|
||||
import EditorStateTransfer from '../../../utils/EditorStateTransfer';
|
||||
import {MatrixClient} from 'matrix-js-sdk';
|
||||
import classNames from 'classnames';
|
||||
import {EventStatus} from 'matrix-js-sdk';
|
||||
import BasicMessageComposer from "./BasicMessageComposer";
|
||||
import {Key} from "../../../Keyboard";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
function _isReply(mxEvent) {
|
||||
const relatesTo = mxEvent.getContent()["m.relates_to"];
|
||||
|
@ -105,12 +105,10 @@ export default class EditMessageComposer extends React.Component {
|
|||
editState: PropTypes.instanceOf(EditorStateTransfer).isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||
};
|
||||
static contextType = MatrixClientContext;
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.model = null;
|
||||
this._editorRef = null;
|
||||
|
||||
|
@ -124,7 +122,7 @@ export default class EditMessageComposer extends React.Component {
|
|||
};
|
||||
|
||||
_getRoom() {
|
||||
return this.context.matrixClient.getRoom(this.props.editState.getEvent().getRoomId());
|
||||
return this.context.getRoom(this.props.editState.getEvent().getRoomId());
|
||||
}
|
||||
|
||||
_onKeyDown = (event) => {
|
||||
|
@ -190,7 +188,7 @@ export default class EditMessageComposer extends React.Component {
|
|||
if (this._isContentModified(newContent)) {
|
||||
const roomId = editedEvent.getRoomId();
|
||||
this._cancelPreviousPendingEdit();
|
||||
this.context.matrixClient.sendMessage(roomId, editContent);
|
||||
this.context.sendMessage(roomId, editContent);
|
||||
}
|
||||
|
||||
// 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.NOT_SENT
|
||||
)) {
|
||||
this.context.matrixClient.cancelPendingEvent(previousEdit);
|
||||
this.context.cancelPendingEvent(previousEdit);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -232,7 +230,7 @@ export default class EditMessageComposer extends React.Component {
|
|||
_createEditorModel() {
|
||||
const {editState} = this.props;
|
||||
const room = this._getRoom();
|
||||
const partCreator = new PartCreator(room, this.context.matrixClient);
|
||||
const partCreator = new PartCreator(room, this.context);
|
||||
let parts;
|
||||
if (editState.hasEditorState()) {
|
||||
// if restoring state from a previous editor,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 New Vector Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
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");
|
||||
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';
|
||||
const classNames = require("classnames");
|
||||
import { _t, _td } from '../../../languageHandler';
|
||||
const Modal = require('../../../Modal');
|
||||
|
||||
const sdk = require('../../../index');
|
||||
const TextForEvent = require('../../../TextForEvent');
|
||||
|
||||
import dis from '../../../dispatcher';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import {EventStatus, MatrixClient} from 'matrix-js-sdk';
|
||||
import {EventStatus} from 'matrix-js-sdk';
|
||||
import {formatTime} from "../../../DateUtils";
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import {ALL_RULE_TYPES} from "../../../mjolnir/BanList";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
const ObjectUtils = require('../../../ObjectUtils');
|
||||
|
||||
|
@ -222,8 +222,8 @@ module.exports = createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
contextTypes: {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||
statics: {
|
||||
contextType: MatrixClientContext,
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
|
@ -237,7 +237,7 @@ module.exports = createReactClass({
|
|||
|
||||
componentDidMount: function() {
|
||||
this._suppressReadReceiptAnimation = false;
|
||||
const client = this.context.matrixClient;
|
||||
const client = this.context;
|
||||
client.on("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||
this.props.mxEvent.on("Event.decrypted", this._onDecrypted);
|
||||
if (this.props.showReactions) {
|
||||
|
@ -262,7 +262,7 @@ module.exports = createReactClass({
|
|||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
const client = this.context.matrixClient;
|
||||
const client = this.context;
|
||||
client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||
this.props.mxEvent.removeListener("Event.decrypted", this._onDecrypted);
|
||||
if (this.props.showReactions) {
|
||||
|
@ -291,7 +291,7 @@ module.exports = createReactClass({
|
|||
return;
|
||||
}
|
||||
|
||||
const verified = await this.context.matrixClient.isEventSenderVerified(mxEvent);
|
||||
const verified = await this.context.isEventSenderVerified(mxEvent);
|
||||
this.setState({
|
||||
verified: verified,
|
||||
}, () => {
|
||||
|
@ -349,11 +349,11 @@ module.exports = createReactClass({
|
|||
},
|
||||
|
||||
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; }
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
this.setState({
|
||||
// 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
|
||||
// is received for the request with the required keys, the event could be
|
||||
// decrypted successfully.
|
||||
this.context.matrixClient.cancelAndResendEventRoomKeyRequest(this.props.mxEvent);
|
||||
this.context.cancelAndResendEventRoomKeyRequest(this.props.mxEvent);
|
||||
},
|
||||
|
||||
onPermalinkClicked: function(e) {
|
||||
|
@ -478,11 +469,10 @@ module.exports = createReactClass({
|
|||
|
||||
_renderE2EPadlock: function() {
|
||||
const ev = this.props.mxEvent;
|
||||
const props = {onClick: this.onCryptoClick};
|
||||
|
||||
// event could not be decrypted
|
||||
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
|
||||
|
@ -490,11 +480,11 @@ module.exports = createReactClass({
|
|||
if (this.state.verified) {
|
||||
return; // no icon for verified
|
||||
} else {
|
||||
return (<E2ePadlockUnverified {...props} />);
|
||||
return (<E2ePadlockUnverified />);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.context.matrixClient.isRoomEncrypted(ev.getRoomId())) {
|
||||
if (this.context.isRoomEncrypted(ev.getRoomId())) {
|
||||
// else if room is encrypted
|
||||
// and event is being encrypted or is not_sent (Unknown Devices/Network Error)
|
||||
if (ev.status === EventStatus.ENCRYPTING) {
|
||||
|
@ -507,7 +497,7 @@ module.exports = createReactClass({
|
|||
return; // we expect this to be unencrypted
|
||||
}
|
||||
// if the event is not encrypted, but it's an e2e room, show the open padlock
|
||||
return <E2ePadlockUnencrypted {...props} />;
|
||||
return <E2ePadlockUnencrypted />;
|
||||
}
|
||||
|
||||
// no padlock needed
|
||||
|
@ -741,7 +731,7 @@ module.exports = createReactClass({
|
|||
|
||||
switch (this.props.tileShape) {
|
||||
case 'notif': {
|
||||
const room = this.context.matrixClient.getRoom(this.props.mxEvent.getRoomId());
|
||||
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className="mx_EventTile_roomName">
|
||||
|
@ -919,7 +909,6 @@ class E2ePadlock extends React.Component {
|
|||
static propTypes = {
|
||||
icon: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
|
@ -930,10 +919,6 @@ class E2ePadlock extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
onClick = (e) => {
|
||||
if (this.props.onClick) this.props.onClick(e);
|
||||
};
|
||||
|
||||
onHoverStart = () => {
|
||||
this.setState({hover: true});
|
||||
};
|
||||
|
|
|
@ -23,6 +23,8 @@ import classNames from 'classnames';
|
|||
export default class MemberDeviceInfo extends React.Component {
|
||||
render() {
|
||||
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({
|
||||
mx_MemberDeviceInfo_icon: true,
|
||||
mx_MemberDeviceInfo_icon_blacklisted: this.props.device.isBlocked(),
|
||||
|
|
|
@ -31,7 +31,6 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import classNames from 'classnames';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import dis from '../../../dispatcher';
|
||||
import Modal from '../../../Modal';
|
||||
import sdk from '../../../index';
|
||||
|
@ -48,7 +47,7 @@ import SettingsStore from "../../../settings/SettingsStore";
|
|||
import E2EIcon from "./E2EIcon";
|
||||
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import {EventTimeline} from "matrix-js-sdk";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
module.exports = createReactClass({
|
||||
displayName: 'MemberInfo',
|
||||
|
@ -76,13 +75,13 @@ module.exports = createReactClass({
|
|||
};
|
||||
},
|
||||
|
||||
contextTypes: {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||
statics: {
|
||||
contextType: MatrixClientContext,
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this._cancelDeviceList = null;
|
||||
const cli = this.context.matrixClient;
|
||||
const cli = this.context;
|
||||
|
||||
// only display the devices list if our client supports E2E
|
||||
this._enableDevices = cli.isCryptoEnabled();
|
||||
|
@ -112,7 +111,7 @@ module.exports = createReactClass({
|
|||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
const client = this.context.matrixClient;
|
||||
const client = this.context;
|
||||
if (client) {
|
||||
client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
||||
client.removeListener("Room", this.onRoom);
|
||||
|
@ -131,7 +130,7 @@ module.exports = createReactClass({
|
|||
},
|
||||
|
||||
_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});
|
||||
},
|
||||
|
||||
|
@ -163,7 +162,7 @@ module.exports = createReactClass({
|
|||
|
||||
// Promise.resolve to handle transition from static result to promise; can be removed
|
||||
// in future
|
||||
Promise.resolve(this.context.matrixClient.getStoredDevicesForUser(userId)).then((devices) => {
|
||||
Promise.resolve(this.context.getStoredDevicesForUser(userId)).then((devices) => {
|
||||
this.setState({
|
||||
devices: devices,
|
||||
e2eStatus: this._getE2EStatus(devices),
|
||||
|
@ -197,7 +196,7 @@ module.exports = createReactClass({
|
|||
onRoomReceipt: function(receiptEvent, room) {
|
||||
// because if we read a notification, it will affect notification count
|
||||
// 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();
|
||||
}
|
||||
},
|
||||
|
@ -242,7 +241,7 @@ module.exports = createReactClass({
|
|||
let cancelled = false;
|
||||
this._cancelDeviceList = function() { cancelled = true; };
|
||||
|
||||
const client = this.context.matrixClient;
|
||||
const client = this.context;
|
||||
const self = this;
|
||||
client.downloadKeys([member.userId], true).then(() => {
|
||||
return client.getStoredDevicesForUser(member.userId);
|
||||
|
@ -267,7 +266,7 @@ module.exports = createReactClass({
|
|||
},
|
||||
|
||||
onIgnoreToggle: function() {
|
||||
const ignoredUsers = this.context.matrixClient.getIgnoredUsers();
|
||||
const ignoredUsers = this.context.getIgnoredUsers();
|
||||
if (this.state.isIgnoring) {
|
||||
const index = ignoredUsers.indexOf(this.props.member.userId);
|
||||
if (index !== -1) ignoredUsers.splice(index, 1);
|
||||
|
@ -275,7 +274,7 @@ module.exports = createReactClass({
|
|||
ignoredUsers.push(this.props.member.userId);
|
||||
}
|
||||
|
||||
this.context.matrixClient.setIgnoredUsers(ignoredUsers).then(() => {
|
||||
this.context.setIgnoredUsers(ignoredUsers).then(() => {
|
||||
return this.setState({isIgnoring: !this.state.isIgnoring});
|
||||
});
|
||||
},
|
||||
|
@ -293,7 +292,7 @@ module.exports = createReactClass({
|
|||
if (!proceed) return;
|
||||
|
||||
this.setState({ updating: this.state.updating + 1 });
|
||||
this.context.matrixClient.kick(
|
||||
this.context.kick(
|
||||
this.props.member.roomId, this.props.member.userId,
|
||||
reason || undefined,
|
||||
).then(function() {
|
||||
|
@ -329,11 +328,11 @@ module.exports = createReactClass({
|
|||
this.setState({ updating: this.state.updating + 1 });
|
||||
let promise;
|
||||
if (this.props.member.membership === 'ban') {
|
||||
promise = this.context.matrixClient.unban(
|
||||
promise = this.context.unban(
|
||||
this.props.member.roomId, this.props.member.userId,
|
||||
);
|
||||
} else {
|
||||
promise = this.context.matrixClient.ban(
|
||||
promise = this.context.ban(
|
||||
this.props.member.roomId, this.props.member.userId,
|
||||
reason || undefined,
|
||||
);
|
||||
|
@ -360,7 +359,7 @@ module.exports = createReactClass({
|
|||
|
||||
onRedactAllMessages: async function() {
|
||||
const {roomId, userId} = this.props.member;
|
||||
const room = this.context.matrixClient.getRoom(roomId);
|
||||
const room = this.context.getRoom(roomId);
|
||||
if (!room) {
|
||||
return;
|
||||
}
|
||||
|
@ -414,7 +413,7 @@ module.exports = createReactClass({
|
|||
console.info(`Started redacting recent ${count} messages for ${user} in ${roomId}`);
|
||||
await Promise.all(eventsToRedact.map(async event => {
|
||||
try {
|
||||
await this.context.matrixClient.redactEvent(roomId, event.getId());
|
||||
await this.context.redactEvent(roomId, event.getId());
|
||||
} catch (err) {
|
||||
// log and swallow errors
|
||||
console.error("Could not redact", event.getId());
|
||||
|
@ -446,11 +445,11 @@ module.exports = createReactClass({
|
|||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
const roomId = this.props.member.roomId;
|
||||
const target = this.props.member.userId;
|
||||
const room = this.context.matrixClient.getRoom(roomId);
|
||||
const room = this.context.getRoom(roomId);
|
||||
if (!room) return;
|
||||
|
||||
// if muting self, warn as it may be irreversible
|
||||
if (target === this.context.matrixClient.getUserId()) {
|
||||
if (target === this.context.getUserId()) {
|
||||
try {
|
||||
if (!(await this._warnSelfDemote())) return;
|
||||
} catch (e) {
|
||||
|
@ -478,7 +477,7 @@ module.exports = createReactClass({
|
|||
|
||||
if (!isNaN(level)) {
|
||||
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() {
|
||||
// NO-OP; rely on the m.room.member event coming down else we could
|
||||
// get out of sync if we force setState here!
|
||||
|
@ -500,13 +499,13 @@ module.exports = createReactClass({
|
|||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
const roomId = this.props.member.roomId;
|
||||
const target = this.props.member.userId;
|
||||
const room = this.context.matrixClient.getRoom(roomId);
|
||||
const room = this.context.getRoom(roomId);
|
||||
if (!room) return;
|
||||
|
||||
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
|
||||
if (!powerLevelEvent) return;
|
||||
|
||||
const me = room.getMember(this.context.matrixClient.credentials.userId);
|
||||
const me = room.getMember(this.context.credentials.userId);
|
||||
if (!me) return;
|
||||
|
||||
const defaultLevel = powerLevelEvent.getContent().users_default;
|
||||
|
@ -515,7 +514,7 @@ module.exports = createReactClass({
|
|||
// toggle the level
|
||||
const newLevel = this.state.isTargetMod ? defaultLevel : modLevel;
|
||||
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() {
|
||||
// NO-OP; rely on the m.room.member event coming down else we could
|
||||
// get out of sync if we force setState here!
|
||||
|
@ -550,7 +549,7 @@ module.exports = createReactClass({
|
|||
danger: true,
|
||||
onFinished: (accepted) => {
|
||||
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(e);
|
||||
|
||||
|
@ -566,7 +565,7 @@ module.exports = createReactClass({
|
|||
|
||||
_applyPowerChange: function(roomId, target, powerLevel, powerLevelEvent) {
|
||||
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() {
|
||||
// NO-OP; rely on the m.room.member event coming down else we could
|
||||
// get out of sync if we force setState here!
|
||||
|
@ -587,7 +586,7 @@ module.exports = createReactClass({
|
|||
onPowerChange: async function(powerLevel) {
|
||||
const roomId = this.props.member.roomId;
|
||||
const target = this.props.member.userId;
|
||||
const room = this.context.matrixClient.getRoom(roomId);
|
||||
const room = this.context.getRoom(roomId);
|
||||
if (!room) return;
|
||||
|
||||
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
|
||||
|
@ -598,7 +597,7 @@ module.exports = createReactClass({
|
|||
return;
|
||||
}
|
||||
|
||||
const myUserId = this.context.matrixClient.getUserId();
|
||||
const myUserId = this.context.getUserId();
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
|
||||
// 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) {
|
||||
let canDeactivate = false;
|
||||
if (this.context.matrixClient) {
|
||||
if (this.context) {
|
||||
try {
|
||||
canDeactivate = await this.context.matrixClient.isSynapseAdministrator();
|
||||
canDeactivate = await this.context.isSynapseAdministrator();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
@ -665,13 +664,13 @@ module.exports = createReactClass({
|
|||
},
|
||||
muted: false,
|
||||
};
|
||||
const room = this.context.matrixClient.getRoom(member.roomId);
|
||||
const room = this.context.getRoom(member.roomId);
|
||||
if (!room) return defaultPerms;
|
||||
|
||||
const powerLevels = room.currentState.getStateEvents("m.room.power_levels", "");
|
||||
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;
|
||||
|
||||
const them = member;
|
||||
|
@ -738,7 +737,7 @@ module.exports = createReactClass({
|
|||
const avatarUrl = member.getMxcAvatarUrl();
|
||||
if (!avatarUrl) return;
|
||||
|
||||
const httpUrl = this.context.matrixClient.mxcUrlToHttp(avatarUrl);
|
||||
const httpUrl = this.context.mxcUrlToHttp(avatarUrl);
|
||||
const ImageView = sdk.getComponent("elements.ImageView");
|
||||
const params = {
|
||||
src: httpUrl,
|
||||
|
@ -797,7 +796,7 @@ module.exports = createReactClass({
|
|||
},
|
||||
|
||||
_renderUserOptions: function() {
|
||||
const cli = this.context.matrixClient;
|
||||
const cli = this.context;
|
||||
const member = this.props.member;
|
||||
|
||||
let ignoreButton = null;
|
||||
|
@ -905,9 +904,9 @@ module.exports = createReactClass({
|
|||
let synapseDeactivateButton;
|
||||
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
|
||||
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.
|
||||
// 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
|
||||
|
@ -918,7 +917,7 @@ module.exports = createReactClass({
|
|||
|
||||
const tiles = [];
|
||||
for (const roomId of dmRooms) {
|
||||
const room = this.context.matrixClient.getRoom(roomId);
|
||||
const room = this.context.getRoom(roomId);
|
||||
if (room) {
|
||||
const myMembership = room.getMyMembership();
|
||||
// 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 powerLevelUsersDefault = powerLevelEvent ? powerLevelEvent.getContent().users_default : 0;
|
||||
|
||||
const enablePresenceByHsUrl = SdkConfig.get()["enable_presence_by_hs_url"];
|
||||
const hsUrl = this.context.matrixClient.baseUrl;
|
||||
const hsUrl = this.context.baseUrl;
|
||||
let showPresence = true;
|
||||
if (enablePresenceByHsUrl && enablePresenceByHsUrl[hsUrl] !== undefined) {
|
||||
showPresence = enablePresenceByHsUrl[hsUrl];
|
||||
|
@ -1108,7 +1107,7 @@ module.exports = createReactClass({
|
|||
</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) {
|
||||
e2eIconElement = (<E2EIcon status={this.state.e2eStatus} isUser={true} />);
|
||||
}
|
||||
|
@ -1117,7 +1116,7 @@ module.exports = createReactClass({
|
|||
const avatarUrl = this.props.member.getMxcAvatarUrl();
|
||||
let avatarElement;
|
||||
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">
|
||||
<img src={httpUrl} />
|
||||
</div>;
|
||||
|
|
|
@ -32,6 +32,10 @@ const INITIAL_LOAD_NUM_MEMBERS = 30;
|
|||
const INITIAL_LOAD_NUM_INVITED = 5;
|
||||
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({
|
||||
displayName: 'MemberList',
|
||||
|
||||
|
@ -336,10 +340,13 @@ module.exports = createReactClass({
|
|||
}
|
||||
|
||||
// Fourth by name (alphabetical)
|
||||
const nameA = memberA.name[0] === '@' ? memberA.name.substr(1) : memberA.name;
|
||||
const nameB = memberB.name[0] === '@' ? memberB.name.substr(1) : memberB.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).replace(SORT_REGEX, "");
|
||||
// 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) {
|
||||
|
|
|
@ -107,8 +107,8 @@ class UploadButton extends React.Component {
|
|||
roomId: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onUploadClick = this.onUploadClick.bind(this);
|
||||
this.onUploadFileInputChange = this.onUploadFileInputChange.bind(this);
|
||||
|
||||
|
@ -165,8 +165,8 @@ class UploadButton extends React.Component {
|
|||
}
|
||||
|
||||
export default class MessageComposer extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onInputStateChanged = this.onInputStateChanged.bind(this);
|
||||
this.onEvent = this.onEvent.bind(this);
|
||||
this._onRoomStateEvents = this._onRoomStateEvents.bind(this);
|
||||
|
|
|
@ -48,7 +48,6 @@ import Markdown from '../../../Markdown';
|
|||
import MessageComposerStore from '../../../stores/MessageComposerStore';
|
||||
import ContentMessages from '../../../ContentMessages';
|
||||
|
||||
import EMOJIBASE from 'emojibase-data/en/compact.json';
|
||||
import EMOTICON_REGEX from 'emojibase-regex/emoticon';
|
||||
|
||||
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
||||
|
@ -61,6 +60,7 @@ import AccessibleButton from '../elements/AccessibleButton';
|
|||
import {findEditableEvent} from '../../../utils/EventUtils';
|
||||
import SlateComposerHistoryManager from "../../../SlateComposerHistoryManager";
|
||||
import TypingStore from "../../../stores/TypingStore";
|
||||
import {EMOTICON_TO_EMOJI} from "../../../emoji";
|
||||
|
||||
const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s$');
|
||||
|
||||
|
@ -141,8 +141,8 @@ export default class MessageComposerInput extends React.Component {
|
|||
autocomplete: Autocomplete;
|
||||
historyManager: SlateComposerHistoryManager;
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
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));
|
||||
if (emoticonMatch) {
|
||||
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
|
||||
if (data) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue