Merge branches 'develop' and 't3chguy/kbd' of github.com:matrix-org/matrix-react-sdk into t3chguy/kbd
commit
d593a76f28
|
@ -72,7 +72,6 @@
|
||||||
"flux": "2.1.1",
|
"flux": "2.1.1",
|
||||||
"focus-visible": "^5.0.2",
|
"focus-visible": "^5.0.2",
|
||||||
"fuse.js": "^2.2.0",
|
"fuse.js": "^2.2.0",
|
||||||
"gemini-scrollbar": "github:matrix-org/gemini-scrollbar#91e1e566",
|
|
||||||
"gfm.css": "^1.1.1",
|
"gfm.css": "^1.1.1",
|
||||||
"glob-to-regexp": "^0.4.1",
|
"glob-to-regexp": "^0.4.1",
|
||||||
"highlight.js": "^9.15.8",
|
"highlight.js": "^9.15.8",
|
||||||
|
@ -93,7 +92,6 @@
|
||||||
"react-beautiful-dnd": "^4.0.1",
|
"react-beautiful-dnd": "^4.0.1",
|
||||||
"react-dom": "^16.9.0",
|
"react-dom": "^16.9.0",
|
||||||
"react-focus-lock": "^2.2.1",
|
"react-focus-lock": "^2.2.1",
|
||||||
"react-gemini-scrollbar": "github:matrix-org/react-gemini-scrollbar#9cf17f63b7c0b0ec5f31df27da0f82f7238dc594",
|
|
||||||
"resize-observer-polyfill": "^1.5.0",
|
"resize-observer-polyfill": "^1.5.0",
|
||||||
"sanitize-html": "^1.18.4",
|
"sanitize-html": "^1.18.4",
|
||||||
"text-encoding-utf-8": "^1.0.1",
|
"text-encoding-utf-8": "^1.0.1",
|
||||||
|
|
|
@ -207,37 +207,6 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
|
||||||
transition: opacity 0.2s ease-in-out;
|
transition: opacity 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* XXX: critical hack to GeminiScrollbar to allow them to work in FF 42 and Chrome 48.
|
|
||||||
Stop the scrollbar view from pushing out the container's overall sizing, which causes
|
|
||||||
flexbox to adapt to the new size and cause the view to keep growing.
|
|
||||||
*/
|
|
||||||
.gm-scrollbar-container .gm-scroll-view {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Expand thumbs on hoverover */
|
|
||||||
.gm-scrollbar {
|
|
||||||
border-radius: 5px !important;
|
|
||||||
}
|
|
||||||
.gm-scrollbar.-vertical {
|
|
||||||
width: 6px;
|
|
||||||
transition: width 120ms ease-out !important;
|
|
||||||
}
|
|
||||||
.gm-scrollbar.-vertical:hover,
|
|
||||||
.gm-scrollbar.-vertical:active {
|
|
||||||
width: 8px;
|
|
||||||
transition: width 120ms ease-out !important;
|
|
||||||
}
|
|
||||||
.gm-scrollbar.-horizontal {
|
|
||||||
height: 6px;
|
|
||||||
transition: height 120ms ease-out !important;
|
|
||||||
}
|
|
||||||
.gm-scrollbar.-horizontal:hover,
|
|
||||||
.gm-scrollbar.-horizontal:active {
|
|
||||||
height: 8px;
|
|
||||||
transition: height 120ms ease-out !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// These are magic constants which are excluded from tinting, to let themes
|
// These are magic constants which are excluded from tinting, to let themes
|
||||||
// (which only have CSS, unlike skins) tell the app what their non-tinted
|
// (which only have CSS, unlike skins) tell the app what their non-tinted
|
||||||
// colourscheme is by inspecting the stylesheet DOM.
|
// colourscheme is by inspecting the stylesheet DOM.
|
||||||
|
|
|
@ -178,7 +178,6 @@
|
||||||
@import "./views/rooms/_RoomTile.scss";
|
@import "./views/rooms/_RoomTile.scss";
|
||||||
@import "./views/rooms/_RoomUpgradeWarningBar.scss";
|
@import "./views/rooms/_RoomUpgradeWarningBar.scss";
|
||||||
@import "./views/rooms/_SearchBar.scss";
|
@import "./views/rooms/_SearchBar.scss";
|
||||||
@import "./views/rooms/_SearchableEntityList.scss";
|
|
||||||
@import "./views/rooms/_SendMessageComposer.scss";
|
@import "./views/rooms/_SendMessageComposer.scss";
|
||||||
@import "./views/rooms/_Stickers.scss";
|
@import "./views/rooms/_Stickers.scss";
|
||||||
@import "./views/rooms/_TopUnreadMessagesBar.scss";
|
@import "./views/rooms/_TopUnreadMessagesBar.scss";
|
||||||
|
|
|
@ -180,10 +180,6 @@ limitations under the License.
|
||||||
line-height: 2em;
|
line-height: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_GroupView > .mx_MainSplit {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_GroupView_body {
|
.mx_GroupView_body {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
@ -341,8 +337,8 @@ limitations under the License.
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_GroupView_body .gm-scroll-view > * {
|
.mx_GroupView_body .mx_AutoHideScrollbar_offset > * {
|
||||||
margin: 11px 50px 0px 68px;
|
margin: 11px 50px 50px 68px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_GroupView_groupDesc textarea {
|
.mx_GroupView_groupDesc textarea {
|
||||||
|
@ -370,7 +366,7 @@ limitations under the License.
|
||||||
padding: 40px 20px;
|
padding: 40px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_GroupView .mx_MemberInfo .gm-scroll-view > :not(.mx_MemberInfo_avatar) {
|
.mx_GroupView .mx_MemberInfo .mx_AutoHideScrollbar_offset > :not(.mx_MemberInfo_avatar) {
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
padding-right: 16px;
|
padding-right: 16px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
// move hit area 5px to the right so it doesn't overlap with the timeline scrollbar
|
// move hit area 5px to the right so it doesn't overlap with the timeline scrollbar
|
||||||
|
|
|
@ -76,13 +76,6 @@ limitations under the License.
|
||||||
flex: 1 1 0;
|
flex: 1 1 0;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
||||||
/* Experimental fix for https://github.com/vector-im/vector-web/issues/947
|
|
||||||
and https://github.com/vector-im/vector-web/issues/946.
|
|
||||||
Empirically this stops the MessagePanel's width exploding outwards when
|
|
||||||
gemini is in 'prevented' mode
|
|
||||||
*/
|
|
||||||
overflow-x: auto;
|
|
||||||
|
|
||||||
/* To fix https://github.com/vector-im/riot-web/issues/3298 where Safari
|
/* To fix https://github.com/vector-im/riot-web/issues/3298 where Safari
|
||||||
needed height 100% all the way down to the HomePage. Height does not
|
needed height 100% all the way down to the HomePage. Height does not
|
||||||
have to be auto, empirically.
|
have to be auto, empirically.
|
||||||
|
|
|
@ -67,9 +67,6 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.mx_MyGroups_headerCard_header {
|
.mx_MyGroups_headerCard_header {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
@ -98,6 +95,11 @@ limitations under the License.
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_MyGroups_scrollable {
|
||||||
|
overflow-y: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MyGroups_placeholder {
|
.mx_MyGroups_placeholder {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -45,9 +46,8 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomDirectory_listheader {
|
.mx_RoomDirectory_listheader {
|
||||||
display: flex;
|
display: block;
|
||||||
margin-top: 12px;
|
margin-top: 13px;
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomDirectory_searchbox {
|
.mx_RoomDirectory_searchbox {
|
||||||
|
@ -64,7 +64,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomDirectory_table {
|
.mx_RoomDirectory_table {
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
color: $primary-fg-color;
|
color: $primary-fg-color;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
@ -112,6 +112,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_RoomDirectory_name {
|
.mx_RoomDirectory_name {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,8 +149,8 @@ limitations under the License.
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomDirectory p {
|
.mx_RoomDirectory > span {
|
||||||
font-size: 14px;
|
font-size: 15px;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
|
||||||
.mx_AccessibleButton {
|
.mx_AccessibleButton {
|
||||||
|
|
|
@ -23,6 +23,7 @@ limitations under the License.
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_TagPanel_items_selected {
|
.mx_TagPanel_items_selected {
|
||||||
|
@ -57,6 +58,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_TagPanel .mx_TagPanel_scroller {
|
.mx_TagPanel .mx_TagPanel_scroller {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_TagPanel .mx_TagPanel_tagTileContainer {
|
.mx_TagPanel .mx_TagPanel_tagTileContainer {
|
||||||
|
|
|
@ -14,14 +14,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// CSS voodoo to support a gemini-scrollbar for the contents of the dialog
|
|
||||||
.mx_Dialog_unknownDevice .mx_Dialog {
|
|
||||||
// ideally we'd shrink the height to fit when needed, but in practice this
|
|
||||||
// is a pain in the ass. plus might as well make the dialog big given how
|
|
||||||
// important it is.
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_UnknownDeviceDialog {
|
.mx_UnknownDeviceDialog {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -44,6 +36,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_UnknownDeviceDialog .mx_Dialog_content {
|
.mx_UnknownDeviceDialog .mx_Dialog_content {
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_UnknownDeviceDialog_deviceList > li {
|
.mx_UnknownDeviceDialog_deviceList > li {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -15,70 +15,149 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_NetworkDropdown {
|
.mx_NetworkDropdown {
|
||||||
|
height: 32px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
width: max-content;
|
||||||
|
padding-right: 32px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 9px;
|
||||||
|
margin-top: 12px;
|
||||||
|
|
||||||
.mx_NetworkDropdown_input {
|
.mx_AccessibleButton {
|
||||||
position: relative;
|
width: max-content;
|
||||||
border-radius: 3px;
|
}
|
||||||
border: 1px solid $strong-input-border-color;
|
|
||||||
font-weight: 300;
|
|
||||||
font-size: 13px;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_NetworkDropdown_arrow {
|
|
||||||
border-color: $primary-fg-color transparent transparent;
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 5px 5px 0;
|
|
||||||
display: block;
|
|
||||||
height: 0;
|
|
||||||
position: absolute;
|
|
||||||
right: 10px;
|
|
||||||
top: 16px;
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_NetworkDropdown_networkoption {
|
|
||||||
height: 37px;
|
|
||||||
line-height: 37px;
|
|
||||||
padding-left: 8px;
|
|
||||||
padding-right: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_NetworkDropdown_networkoption img {
|
|
||||||
margin: 5px;
|
|
||||||
width: 25px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
input.mx_NetworkDropdown_networkoption, input.mx_NetworkDropdown_networkoption:focus {
|
|
||||||
border: 0;
|
|
||||||
padding-top: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_NetworkDropdown_menu {
|
.mx_NetworkDropdown_menu {
|
||||||
position: absolute;
|
min-width: 204px;
|
||||||
left: -1px;
|
|
||||||
right: -1px;
|
|
||||||
top: 100%;
|
|
||||||
z-index: 2;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0px;
|
box-sizing: border-box;
|
||||||
border-radius: 3px;
|
border-radius: 4px;
|
||||||
border: 1px solid $accent-color;
|
border: 1px solid $dialog-close-fg-color;
|
||||||
background-color: $primary-bg-color;
|
background-color: $primary-bg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_NetworkDropdown_menu .mx_NetworkDropdown_networkoption:hover {
|
|
||||||
background-color: $focus-bg-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_NetworkDropdown_menu_network {
|
.mx_NetworkDropdown_menu_network {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_NetworkDropdown_server {
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid $input-darker-fg-color;
|
||||||
|
|
||||||
|
.mx_NetworkDropdown_server_title {
|
||||||
|
padding: 0 10px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 20px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
|
||||||
|
// remove server button
|
||||||
|
.mx_AccessibleButton {
|
||||||
|
position: absolute;
|
||||||
|
display: inline;
|
||||||
|
right: 12px;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
margin-top: 4px;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: contain;
|
||||||
|
mask-image: url('$(res)/img/feather-customised/x.svg');
|
||||||
|
background-color: $notice-primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_NetworkDropdown_server_subtitle {
|
||||||
|
padding: 0 10px;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 14px;
|
||||||
|
margin-top: -4px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
color: $muted-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_NetworkDropdown_server_network {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
&[aria-checked=true]::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
right: 10px;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: contain;
|
||||||
|
mask-image: url('$(res)/img/feather-customised/check.svg');
|
||||||
|
background-color: $input-valid-border-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_NetworkDropdown_server_add,
|
||||||
|
.mx_NetworkDropdown_server_network {
|
||||||
|
&:hover {
|
||||||
|
background-color: $header-panel-bg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_NetworkDropdown_server_add {
|
||||||
|
padding: 16px 10px 16px 32px;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 0 0 4px 4px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
left: 7px;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: contain;
|
||||||
|
mask-image: url('$(res)/img/feather-customised/plus.svg');
|
||||||
|
background-color: $muted-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_NetworkDropdown_handle {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
right: -28px; // - (24 + 4)
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: contain;
|
||||||
|
mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
|
||||||
|
background-color: $primary-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_NetworkDropdown_handle_server {
|
||||||
|
color: $muted-fg-color;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_NetworkDropdown_dialog .mx_Dialog {
|
||||||
|
width: 45vw;
|
||||||
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ limitations under the License.
|
||||||
display: flex;
|
display: flex;
|
||||||
padding-left: 9px;
|
padding-left: 9px;
|
||||||
padding-right: 9px;
|
padding-right: 9px;
|
||||||
margin: 0 5px 0 0 !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_DirectorySearchBox_joinButton {
|
.mx_DirectorySearchBox_joinButton {
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2016 OpenMarket 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_SearchableEntityList {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SearchableEntityList_query {
|
|
||||||
font-family: $font-family;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid $input-border-color;
|
|
||||||
padding: 9px;
|
|
||||||
color: $primary-fg-color;
|
|
||||||
background-color: $primary-bg-color;
|
|
||||||
margin-left: 3px;
|
|
||||||
font-size: 15px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
width: 189px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SearchableEntityList_query::-moz-placeholder {
|
|
||||||
color: $primary-fg-color;
|
|
||||||
opacity: 0.5;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SearchableEntityList_query::-webkit-input-placeholder {
|
|
||||||
color: $primary-fg-color;
|
|
||||||
opacity: 0.5;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SearchableEntityList_listWrapper {
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SearchableEntityList_list {
|
|
||||||
display: table;
|
|
||||||
table-layout: fixed;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SearchableEntityList_list .mx_EntityTile_chevron {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SearchableEntityList_hrWrapper {
|
|
||||||
width: 100%;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SearchableEntityList hr {
|
|
||||||
height: 1px;
|
|
||||||
border: 0px;
|
|
||||||
color: $primary-fg-color;
|
|
||||||
background-color: $primary-fg-color;
|
|
||||||
margin-right: 15px;
|
|
||||||
margin-top: 11px;
|
|
||||||
margin-bottom: 11px;
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6 9L12 15L18 9" stroke="#2E2F32" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 195 B |
|
@ -221,10 +221,6 @@ $user-tile-hover-bg-color: $header-panel-bg-color;
|
||||||
filter: invert(1);
|
filter: invert(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.gm-scrollbar .thumb {
|
|
||||||
filter: invert(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// markdown overrides:
|
// markdown overrides:
|
||||||
.mx_EventTile_content .markdown-body pre:hover {
|
.mx_EventTile_content .markdown-body pre:hover {
|
||||||
border-color: #808080 !important; // inverted due to rules below
|
border-color: #808080 !important; // inverted due to rules below
|
||||||
|
|
|
@ -350,7 +350,7 @@ export const ContextMenuButton = ({ label, isExpanded, children, ...props }) =>
|
||||||
};
|
};
|
||||||
ContextMenuButton.propTypes = {
|
ContextMenuButton.propTypes = {
|
||||||
...AccessibleButton.propTypes,
|
...AccessibleButton.propTypes,
|
||||||
label: PropTypes.string.isRequired,
|
label: PropTypes.string,
|
||||||
isExpanded: PropTypes.bool.isRequired, // whether or not the context menu is currently open
|
isExpanded: PropTypes.bool.isRequired, // whether or not the context menu is currently open
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -377,7 +377,6 @@ export const MenuGroup = ({children, label, ...props}) => {
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
MenuGroup.propTypes = {
|
MenuGroup.propTypes = {
|
||||||
...AccessibleButton.propTypes,
|
|
||||||
label: PropTypes.string.isRequired,
|
label: PropTypes.string.isRequired,
|
||||||
className: PropTypes.string, // optional
|
className: PropTypes.string, // optional
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,11 +23,11 @@ import PropTypes from 'prop-types';
|
||||||
import request from 'browser-request';
|
import request from 'browser-request';
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import sanitizeHtml from 'sanitize-html';
|
import sanitizeHtml from 'sanitize-html';
|
||||||
import * as sdk from '../../index';
|
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher';
|
||||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
|
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||||
|
|
||||||
export default class EmbeddedPage extends React.PureComponent {
|
export default class EmbeddedPage extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -117,10 +117,9 @@ export default class EmbeddedPage extends React.PureComponent {
|
||||||
</div>;
|
</div>;
|
||||||
|
|
||||||
if (this.props.scrollbar) {
|
if (this.props.scrollbar) {
|
||||||
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
return <AutoHideScrollbar className={classes}>
|
||||||
return <GeminiScrollbarWrapper autoshow={true} className={classes}>
|
|
||||||
{content}
|
{content}
|
||||||
</GeminiScrollbarWrapper>;
|
</AutoHideScrollbar>;
|
||||||
} else {
|
} else {
|
||||||
return <div className={classes}>
|
return <div className={classes}>
|
||||||
{content}
|
{content}
|
||||||
|
|
|
@ -39,6 +39,7 @@ import {makeGroupPermalink, makeUserPermalink} from "../../utils/permalinks/Perm
|
||||||
import {Group} from "matrix-js-sdk";
|
import {Group} from "matrix-js-sdk";
|
||||||
import {allSettled, sleep} from "../../utils/promise";
|
import {allSettled, sleep} from "../../utils/promise";
|
||||||
import RightPanelStore from "../../stores/RightPanelStore";
|
import RightPanelStore from "../../stores/RightPanelStore";
|
||||||
|
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||||
|
|
||||||
const LONG_DESC_PLACEHOLDER = _td(
|
const LONG_DESC_PLACEHOLDER = _td(
|
||||||
`<h1>HTML for your community's page</h1>
|
`<h1>HTML for your community's page</h1>
|
||||||
|
@ -1173,7 +1174,6 @@ export default createReactClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
const GroupAvatar = sdk.getComponent("avatars.GroupAvatar");
|
const GroupAvatar = sdk.getComponent("avatars.GroupAvatar");
|
||||||
const Spinner = sdk.getComponent("elements.Spinner");
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
|
||||||
|
|
||||||
if (this.state.summaryLoading && this.state.error === null || this.state.saving) {
|
if (this.state.summaryLoading && this.state.error === null || this.state.saving) {
|
||||||
return <Spinner />;
|
return <Spinner />;
|
||||||
|
@ -1332,10 +1332,10 @@ export default createReactClass({
|
||||||
<GroupHeaderButtons />
|
<GroupHeaderButtons />
|
||||||
</div>
|
</div>
|
||||||
<MainSplit panel={rightPanel}>
|
<MainSplit panel={rightPanel}>
|
||||||
<GeminiScrollbarWrapper className="mx_GroupView_body">
|
<AutoHideScrollbar className="mx_GroupView_body">
|
||||||
{ this._getMembershipSection() }
|
{ this._getMembershipSection() }
|
||||||
{ this._getGroupSection() }
|
{ this._getGroupSection() }
|
||||||
</GeminiScrollbarWrapper>
|
</AutoHideScrollbar>
|
||||||
</MainSplit>
|
</MainSplit>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
|
|
@ -600,9 +600,8 @@ export default createReactClass({
|
||||||
break;
|
break;
|
||||||
case 'view_room_directory': {
|
case 'view_room_directory': {
|
||||||
const RoomDirectory = sdk.getComponent("structures.RoomDirectory");
|
const RoomDirectory = sdk.getComponent("structures.RoomDirectory");
|
||||||
Modal.createTrackedDialog('Room directory', '', RoomDirectory, {
|
Modal.createTrackedDialog('Room directory', '', RoomDirectory, {},
|
||||||
config: this.props.config,
|
'mx_RoomDirectory_dialogWrapper', false, true);
|
||||||
}, 'mx_RoomDirectory_dialogWrapper');
|
|
||||||
|
|
||||||
// View the welcome or home page if we need something to look at
|
// View the welcome or home page if we need something to look at
|
||||||
this._viewSomethingBehindModal();
|
this._viewSomethingBehindModal();
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { _t } from '../../languageHandler';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher';
|
||||||
import AccessibleButton from '../views/elements/AccessibleButton';
|
import AccessibleButton from '../views/elements/AccessibleButton';
|
||||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
|
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||||
|
|
||||||
export default createReactClass({
|
export default createReactClass({
|
||||||
displayName: 'MyGroups',
|
displayName: 'MyGroups',
|
||||||
|
@ -62,8 +63,6 @@ export default createReactClass({
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
const Loader = sdk.getComponent("elements.Spinner");
|
||||||
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
|
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
|
||||||
const GroupTile = sdk.getComponent("groups.GroupTile");
|
const GroupTile = sdk.getComponent("groups.GroupTile");
|
||||||
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
|
||||||
|
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
let contentHeader;
|
let contentHeader;
|
||||||
|
@ -74,7 +73,7 @@ export default createReactClass({
|
||||||
});
|
});
|
||||||
contentHeader = groupNodes.length > 0 ? <h3>{ _t('Your Communities') }</h3> : <div />;
|
contentHeader = groupNodes.length > 0 ? <h3>{ _t('Your Communities') }</h3> : <div />;
|
||||||
content = groupNodes.length > 0 ?
|
content = groupNodes.length > 0 ?
|
||||||
<GeminiScrollbarWrapper>
|
<AutoHideScrollbar className="mx_MyGroups_scrollable">
|
||||||
<div className="mx_MyGroups_microcopy">
|
<div className="mx_MyGroups_microcopy">
|
||||||
<p>
|
<p>
|
||||||
{ _t(
|
{ _t(
|
||||||
|
@ -93,7 +92,7 @@ export default createReactClass({
|
||||||
<div className="mx_MyGroups_joinedGroups">
|
<div className="mx_MyGroups_joinedGroups">
|
||||||
{ groupNodes }
|
{ groupNodes }
|
||||||
</div>
|
</div>
|
||||||
</GeminiScrollbarWrapper> :
|
</AutoHideScrollbar> :
|
||||||
<div className="mx_MyGroups_placeholder">
|
<div className="mx_MyGroups_placeholder">
|
||||||
{ _t(
|
{ _t(
|
||||||
"You're not currently a member of any communities.",
|
"You're not currently a member of any communities.",
|
||||||
|
|
|
@ -28,6 +28,7 @@ import { _t } from '../../languageHandler';
|
||||||
import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils';
|
import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils';
|
||||||
import Analytics from '../../Analytics';
|
import Analytics from '../../Analytics';
|
||||||
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
|
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
|
||||||
|
import {ALL_ROOMS} from "../views/directory/NetworkDropdown";
|
||||||
|
|
||||||
const MAX_NAME_LENGTH = 80;
|
const MAX_NAME_LENGTH = 80;
|
||||||
const MAX_TOPIC_LENGTH = 160;
|
const MAX_TOPIC_LENGTH = 160;
|
||||||
|
@ -40,25 +41,17 @@ export default createReactClass({
|
||||||
displayName: 'RoomDirectory',
|
displayName: 'RoomDirectory',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
config: PropTypes.object,
|
|
||||||
onFinished: PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
config: {},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
publicRooms: [],
|
publicRooms: [],
|
||||||
loading: true,
|
loading: true,
|
||||||
protocolsLoading: true,
|
protocolsLoading: true,
|
||||||
error: null,
|
error: null,
|
||||||
instanceId: null,
|
instanceId: undefined,
|
||||||
includeAll: false,
|
roomServer: MatrixClientPeg.getHomeserverName(),
|
||||||
roomServer: null,
|
|
||||||
filterString: null,
|
filterString: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -98,6 +91,10 @@ export default createReactClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.refreshRoomList();
|
||||||
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
if (this.filterTimeout) {
|
if (this.filterTimeout) {
|
||||||
clearTimeout(this.filterTimeout);
|
clearTimeout(this.filterTimeout);
|
||||||
|
@ -130,10 +127,10 @@ export default createReactClass({
|
||||||
if (my_server != MatrixClientPeg.getHomeserverName()) {
|
if (my_server != MatrixClientPeg.getHomeserverName()) {
|
||||||
opts.server = my_server;
|
opts.server = my_server;
|
||||||
}
|
}
|
||||||
if (this.state.instanceId) {
|
if (this.state.instanceId === ALL_ROOMS) {
|
||||||
opts.third_party_instance_id = this.state.instanceId;
|
|
||||||
} else if (this.state.includeAll) {
|
|
||||||
opts.include_all_networks = true;
|
opts.include_all_networks = true;
|
||||||
|
} else if (this.state.instanceId) {
|
||||||
|
opts.third_party_instance_id = this.state.instanceId;
|
||||||
}
|
}
|
||||||
if (this.nextBatch) opts.since = this.nextBatch;
|
if (this.nextBatch) opts.since = this.nextBatch;
|
||||||
if (my_filter_string) opts.filter = { generic_search_term: my_filter_string };
|
if (my_filter_string) opts.filter = { generic_search_term: my_filter_string };
|
||||||
|
@ -247,7 +244,7 @@ export default createReactClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onOptionChange: function(server, instanceId, includeAll) {
|
onOptionChange: function(server, instanceId) {
|
||||||
// clear next batch so we don't try to load more rooms
|
// clear next batch so we don't try to load more rooms
|
||||||
this.nextBatch = null;
|
this.nextBatch = null;
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -257,7 +254,6 @@ export default createReactClass({
|
||||||
publicRooms: [],
|
publicRooms: [],
|
||||||
roomServer: server,
|
roomServer: server,
|
||||||
instanceId: instanceId,
|
instanceId: instanceId,
|
||||||
includeAll: includeAll,
|
|
||||||
error: null,
|
error: null,
|
||||||
}, this.refreshRoomList);
|
}, this.refreshRoomList);
|
||||||
// We also refresh the room list each time even though this
|
// We also refresh the room list each time even though this
|
||||||
|
@ -305,7 +301,7 @@ export default createReactClass({
|
||||||
|
|
||||||
onJoinFromSearchClick: function(alias) {
|
onJoinFromSearchClick: function(alias) {
|
||||||
// If we don't have a particular instance id selected, just show that rooms alias
|
// If we don't have a particular instance id selected, just show that rooms alias
|
||||||
if (!this.state.instanceId) {
|
if (!this.state.instanceId || this.state.instanceId === ALL_ROOMS) {
|
||||||
// If the user specified an alias without a domain, add on whichever server is selected
|
// If the user specified an alias without a domain, add on whichever server is selected
|
||||||
// in the dropdown
|
// in the dropdown
|
||||||
if (alias.indexOf(':') == -1) {
|
if (alias.indexOf(':') == -1) {
|
||||||
|
@ -593,7 +589,7 @@ export default createReactClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
let placeholder = _t('Find a room…');
|
let placeholder = _t('Find a room…');
|
||||||
if (!this.state.instanceId) {
|
if (!this.state.instanceId || this.state.instanceId === ALL_ROOMS) {
|
||||||
placeholder = _t("Find a room… (e.g. %(exampleRoom)s)", {exampleRoom: "#example:" + this.state.roomServer});
|
placeholder = _t("Find a room… (e.g. %(exampleRoom)s)", {exampleRoom: "#example:" + this.state.roomServer});
|
||||||
} else if (instance_expected_field_type) {
|
} else if (instance_expected_field_type) {
|
||||||
placeholder = instance_expected_field_type.placeholder;
|
placeholder = instance_expected_field_type.placeholder;
|
||||||
|
@ -610,10 +606,18 @@ export default createReactClass({
|
||||||
listHeader = <div className="mx_RoomDirectory_listheader">
|
listHeader = <div className="mx_RoomDirectory_listheader">
|
||||||
<DirectorySearchBox
|
<DirectorySearchBox
|
||||||
className="mx_RoomDirectory_searchbox"
|
className="mx_RoomDirectory_searchbox"
|
||||||
onChange={this.onFilterChange} onClear={this.onFilterClear} onJoinClick={this.onJoinFromSearchClick}
|
onChange={this.onFilterChange}
|
||||||
placeholder={placeholder} showJoinButton={showJoinButton}
|
onClear={this.onFilterClear}
|
||||||
|
onJoinClick={this.onJoinFromSearchClick}
|
||||||
|
placeholder={placeholder}
|
||||||
|
showJoinButton={showJoinButton}
|
||||||
|
/>
|
||||||
|
<NetworkDropdown
|
||||||
|
protocols={this.protocols}
|
||||||
|
onOptionChange={this.onOptionChange}
|
||||||
|
selectedServerName={this.state.roomServer}
|
||||||
|
selectedInstanceId={this.state.instanceId}
|
||||||
/>
|
/>
|
||||||
<NetworkDropdown config={this.props.config} protocols={this.protocols} onOptionChange={this.onOptionChange} />
|
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
const explanation =
|
const explanation =
|
||||||
|
@ -634,7 +638,7 @@ export default createReactClass({
|
||||||
title={_t("Explore rooms")}
|
title={_t("Explore rooms")}
|
||||||
>
|
>
|
||||||
<div className="mx_RoomDirectory">
|
<div className="mx_RoomDirectory">
|
||||||
<p>{explanation}</p>
|
{explanation}
|
||||||
<div className="mx_RoomDirectory_list">
|
<div className="mx_RoomDirectory_list">
|
||||||
{listHeader}
|
{listHeader}
|
||||||
{content}
|
{content}
|
||||||
|
|
|
@ -405,21 +405,6 @@ export default createReactClass({
|
||||||
this.onResize();
|
this.onResize();
|
||||||
|
|
||||||
document.addEventListener("keydown", this.onKeyDown);
|
document.addEventListener("keydown", this.onKeyDown);
|
||||||
|
|
||||||
// XXX: EVIL HACK to autofocus inviting on empty rooms.
|
|
||||||
// We use the setTimeout to avoid racing with focus_composer.
|
|
||||||
if (this.state.room &&
|
|
||||||
this.state.room.getJoinedMemberCount() == 1 &&
|
|
||||||
this.state.room.getLiveTimeline() &&
|
|
||||||
this.state.room.getLiveTimeline().getEvents() &&
|
|
||||||
this.state.room.getLiveTimeline().getEvents().length <= 6) {
|
|
||||||
const inviteBox = document.getElementById("mx_SearchableEntityList_query");
|
|
||||||
setTimeout(function() {
|
|
||||||
if (inviteBox) {
|
|
||||||
inviteBox.focus();
|
|
||||||
}
|
|
||||||
}, 50);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
shouldComponentUpdate: function(nextProps, nextState) {
|
shouldComponentUpdate: function(nextProps, nextState) {
|
||||||
|
|
|
@ -782,7 +782,7 @@ export default createReactClass({
|
||||||
if (!this._divScroll) {
|
if (!this._divScroll) {
|
||||||
// Likewise, we should have the ref by this point, but if not
|
// Likewise, we should have the ref by this point, but if not
|
||||||
// turn the NPE into something meaningful.
|
// turn the NPE into something meaningful.
|
||||||
throw new Error("ScrollPanel._getScrollNode called before gemini ref collected");
|
throw new Error("ScrollPanel._getScrollNode called before AutoHideScrollbar ref collected");
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._divScroll;
|
return this._divScroll;
|
||||||
|
|
|
@ -28,6 +28,7 @@ import { _t } from '../../languageHandler';
|
||||||
import { Droppable } from 'react-beautiful-dnd';
|
import { Droppable } from 'react-beautiful-dnd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
|
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||||
|
|
||||||
const TagPanel = createReactClass({
|
const TagPanel = createReactClass({
|
||||||
displayName: 'TagPanel',
|
displayName: 'TagPanel',
|
||||||
|
@ -106,7 +107,6 @@ const TagPanel = createReactClass({
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
const ActionButton = sdk.getComponent('elements.ActionButton');
|
const ActionButton = sdk.getComponent('elements.ActionButton');
|
||||||
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
||||||
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
|
||||||
|
|
||||||
const tags = this.state.orderedTags.map((tag, index) => {
|
const tags = this.state.orderedTags.map((tag, index) => {
|
||||||
return <DNDTagTile
|
return <DNDTagTile
|
||||||
|
@ -138,9 +138,8 @@ const TagPanel = createReactClass({
|
||||||
{ clearButton }
|
{ clearButton }
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_TagPanel_divider" />
|
<div className="mx_TagPanel_divider" />
|
||||||
<GeminiScrollbarWrapper
|
<AutoHideScrollbar
|
||||||
className="mx_TagPanel_scroller"
|
className="mx_TagPanel_scroller"
|
||||||
autoshow={true}
|
|
||||||
// XXX: Use onMouseDown as a workaround for https://github.com/atlassian/react-beautiful-dnd/issues/273
|
// XXX: Use onMouseDown as a workaround for https://github.com/atlassian/react-beautiful-dnd/issues/273
|
||||||
// instead of onClick. Otherwise we experience https://github.com/vector-im/riot-web/issues/6253
|
// instead of onClick. Otherwise we experience https://github.com/vector-im/riot-web/issues/6253
|
||||||
onMouseDown={this.onMouseDown}
|
onMouseDown={this.onMouseDown}
|
||||||
|
@ -166,7 +165,7 @@ const TagPanel = createReactClass({
|
||||||
</div>
|
</div>
|
||||||
) }
|
) }
|
||||||
</Droppable>
|
</Droppable>
|
||||||
</GeminiScrollbarWrapper>
|
</AutoHideScrollbar>
|
||||||
</div>;
|
</div>;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -33,6 +33,7 @@ export default createReactClass({
|
||||||
onFinished: PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
headerImage: PropTypes.string,
|
headerImage: PropTypes.string,
|
||||||
quitOnly: PropTypes.bool, // quitOnly doesn't show the cancel button just the quit [x].
|
quitOnly: PropTypes.bool, // quitOnly doesn't show the cancel button just the quit [x].
|
||||||
|
fixedWidth: PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -63,11 +64,14 @@ export default createReactClass({
|
||||||
primaryButtonClass = "danger";
|
primaryButtonClass = "danger";
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_QuestionDialog" onFinished={this.props.onFinished}
|
<BaseDialog
|
||||||
|
className="mx_QuestionDialog"
|
||||||
|
onFinished={this.props.onFinished}
|
||||||
title={this.props.title}
|
title={this.props.title}
|
||||||
contentId='mx_Dialog_content'
|
contentId='mx_Dialog_content'
|
||||||
headerImage={this.props.headerImage}
|
headerImage={this.props.headerImage}
|
||||||
hasCancel={this.props.hasCancelButton}
|
hasCancel={this.props.hasCancelButton}
|
||||||
|
fixedWidth={this.props.fixedWidth}
|
||||||
>
|
>
|
||||||
<div className="mx_Dialog_content" id='mx_Dialog_content'>
|
<div className="mx_Dialog_content" id='mx_Dialog_content'>
|
||||||
{ this.props.description }
|
{ this.props.description }
|
||||||
|
|
|
@ -18,6 +18,7 @@ import React, {createRef} from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
|
import Field from "../elements/Field";
|
||||||
|
|
||||||
export default createReactClass({
|
export default createReactClass({
|
||||||
displayName: 'TextInputDialog',
|
displayName: 'TextInputDialog',
|
||||||
|
@ -28,9 +29,13 @@ export default createReactClass({
|
||||||
PropTypes.string,
|
PropTypes.string,
|
||||||
]),
|
]),
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
button: PropTypes.string,
|
button: PropTypes.string,
|
||||||
focus: PropTypes.bool,
|
focus: PropTypes.bool,
|
||||||
onFinished: PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
|
hasCancel: PropTypes.bool,
|
||||||
|
validator: PropTypes.func, // result of withValidation
|
||||||
|
fixedWidth: PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -39,34 +44,70 @@ export default createReactClass({
|
||||||
value: "",
|
value: "",
|
||||||
description: "",
|
description: "",
|
||||||
focus: true,
|
focus: true,
|
||||||
|
hasCancel: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
value: this.props.value,
|
||||||
|
valid: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
UNSAFE_componentWillMount: function() {
|
UNSAFE_componentWillMount: function() {
|
||||||
this._textinput = createRef();
|
this._field = createRef();
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
if (this.props.focus) {
|
if (this.props.focus) {
|
||||||
// Set the cursor at the end of the text input
|
// Set the cursor at the end of the text input
|
||||||
this._textinput.current.value = this.props.value;
|
// this._field.current.value = this.props.value;
|
||||||
|
this._field.current.focus();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onOk: function() {
|
onOk: async function(ev) {
|
||||||
this.props.onFinished(true, this._textinput.current.value);
|
ev.preventDefault();
|
||||||
|
if (this.props.validator) {
|
||||||
|
await this._field.current.validate({ allowEmpty: false });
|
||||||
|
|
||||||
|
if (!this._field.current.state.valid) {
|
||||||
|
this._field.current.focus();
|
||||||
|
this._field.current.validate({ allowEmpty: false, focused: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.props.onFinished(true, this.state.value);
|
||||||
},
|
},
|
||||||
|
|
||||||
onCancel: function() {
|
onCancel: function() {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onChange: function(ev) {
|
||||||
|
this.setState({
|
||||||
|
value: ev.target.value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onValidate: async function(fieldState) {
|
||||||
|
const result = await this.props.validator(fieldState);
|
||||||
|
this.setState({
|
||||||
|
valid: result.valid,
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_TextInputDialog" onFinished={this.props.onFinished}
|
<BaseDialog
|
||||||
|
className="mx_TextInputDialog"
|
||||||
|
onFinished={this.props.onFinished}
|
||||||
title={this.props.title}
|
title={this.props.title}
|
||||||
|
fixedWidth={this.props.fixedWidth}
|
||||||
>
|
>
|
||||||
<form onSubmit={this.onOk}>
|
<form onSubmit={this.onOk}>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
|
@ -74,19 +115,26 @@ export default createReactClass({
|
||||||
<label htmlFor="textinput"> { this.props.description } </label>
|
<label htmlFor="textinput"> { this.props.description } </label>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input
|
<Field
|
||||||
id="textinput"
|
id="mx_TextInputDialog_field"
|
||||||
ref={this._textinput}
|
|
||||||
className="mx_TextInputDialog_input"
|
className="mx_TextInputDialog_input"
|
||||||
defaultValue={this.props.value}
|
ref={this._field}
|
||||||
autoFocus={this.props.focus}
|
type="text"
|
||||||
size="64" />
|
label={this.props.placeholder}
|
||||||
|
value={this.state.value}
|
||||||
|
onChange={this.onChange}
|
||||||
|
onValidate={this.props.validator ? this.onValidate : undefined}
|
||||||
|
size="64"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<DialogButtons primaryButton={this.props.button}
|
<DialogButtons
|
||||||
|
primaryButton={this.props.button}
|
||||||
onPrimaryButtonClick={this.onOk}
|
onPrimaryButtonClick={this.onOk}
|
||||||
onCancel={this.onCancel} />
|
onCancel={this.onCancel}
|
||||||
|
hasCancel={this.props.hasCancel}
|
||||||
|
/>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -122,7 +122,6 @@ export default createReactClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
|
||||||
if (this.props.devices === null) {
|
if (this.props.devices === null) {
|
||||||
const Spinner = sdk.getComponent("elements.Spinner");
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
return <Spinner />;
|
return <Spinner />;
|
||||||
|
@ -168,7 +167,7 @@ export default createReactClass({
|
||||||
title={_t('Room contains unknown sessions')}
|
title={_t('Room contains unknown sessions')}
|
||||||
contentId='mx_Dialog_content'
|
contentId='mx_Dialog_content'
|
||||||
>
|
>
|
||||||
<GeminiScrollbarWrapper autoshow={false} className="mx_Dialog_content" id='mx_Dialog_content'>
|
<div className="mx_Dialog_content" id='mx_Dialog_content'>
|
||||||
<h4>
|
<h4>
|
||||||
{ _t('"%(RoomName)s" contains sessions that you haven\'t seen before.', {RoomName: this.props.room.name}) }
|
{ _t('"%(RoomName)s" contains sessions that you haven\'t seen before.', {RoomName: this.props.room.name}) }
|
||||||
</h4>
|
</h4>
|
||||||
|
@ -176,7 +175,7 @@ export default createReactClass({
|
||||||
{ _t("Unknown sessions") }:
|
{ _t("Unknown sessions") }:
|
||||||
|
|
||||||
<UnknownDeviceList devices={this.props.devices} />
|
<UnknownDeviceList devices={this.props.devices} />
|
||||||
</GeminiScrollbarWrapper>
|
</div>
|
||||||
<DialogButtons primaryButton={sendButtonLabel}
|
<DialogButtons primaryButton={sendButtonLabel}
|
||||||
onPrimaryButtonClick={sendButtonOnClick}
|
onPrimaryButtonClick={sendButtonOnClick}
|
||||||
onCancel={this._onDismissClicked} />
|
onCancel={this._onDismissClicked} />
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 OpenMarket Ltd
|
Copyright 2016 OpenMarket Ltd
|
||||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -15,241 +16,275 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||||
import {instanceForInstanceId} from '../../../utils/DirectoryUtils';
|
import {instanceForInstanceId} from '../../../utils/DirectoryUtils';
|
||||||
|
import {
|
||||||
|
ContextMenu,
|
||||||
|
useContextMenu,
|
||||||
|
ContextMenuButton,
|
||||||
|
MenuItemRadio,
|
||||||
|
MenuItem,
|
||||||
|
MenuGroup,
|
||||||
|
} from "../../structures/ContextMenu";
|
||||||
|
import {_t} from "../../../languageHandler";
|
||||||
|
import SdkConfig from "../../../SdkConfig";
|
||||||
|
import {useSettingValue} from "../../../hooks/useSettings";
|
||||||
|
import * as sdk from "../../../index";
|
||||||
|
import Modal from "../../../Modal";
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import withValidation from "../elements/Validation";
|
||||||
|
|
||||||
const DEFAULT_ICON_URL = require("../../../../res/img/network-matrix.svg");
|
export const ALL_ROOMS = Symbol("ALL_ROOMS");
|
||||||
|
|
||||||
export default class NetworkDropdown extends React.Component {
|
const SETTING_NAME = "room_directory_servers";
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.dropdownRootElement = null;
|
const inPlaceOf = (elementRect) => ({
|
||||||
this.ignoreEvent = null;
|
right: window.innerWidth - elementRect.right,
|
||||||
|
top: elementRect.top,
|
||||||
|
chevronOffset: 0,
|
||||||
|
chevronFace: "none",
|
||||||
|
});
|
||||||
|
|
||||||
this.onInputClick = this.onInputClick.bind(this);
|
const validServer = withValidation({
|
||||||
this.onRootClick = this.onRootClick.bind(this);
|
rules: [
|
||||||
this.onDocumentClick = this.onDocumentClick.bind(this);
|
{
|
||||||
this.onMenuOptionClick = this.onMenuOptionClick.bind(this);
|
key: "required",
|
||||||
this.onInputKeyUp = this.onInputKeyUp.bind(this);
|
test: async ({ value }) => !!value,
|
||||||
this.collectRoot = this.collectRoot.bind(this);
|
invalid: () => _t("Enter a server name"),
|
||||||
this.collectInputTextBox = this.collectInputTextBox.bind(this);
|
}, {
|
||||||
|
key: "available",
|
||||||
|
final: true,
|
||||||
|
test: async ({ value }) => {
|
||||||
|
try {
|
||||||
|
const opts = {
|
||||||
|
limit: 1,
|
||||||
|
server: value,
|
||||||
|
};
|
||||||
|
// check if we can successfully load this server's room directory
|
||||||
|
await MatrixClientPeg.get().publicRooms(opts);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
valid: () => _t("Looks good"),
|
||||||
|
invalid: () => _t("Can't find this server or its room list"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
this.inputTextBox = null;
|
// This dropdown sources homeservers from three places:
|
||||||
|
// + your currently connected homeserver
|
||||||
|
// + homeservers in config.json["roomDirectory"]
|
||||||
|
// + homeservers in SettingsStore["room_directory_servers"]
|
||||||
|
// if a server exists in multiple, only keep the top-most entry.
|
||||||
|
|
||||||
const server = MatrixClientPeg.getHomeserverName();
|
const NetworkDropdown = ({onOptionChange, protocols = {}, selectedServerName, selectedInstanceId}) => {
|
||||||
this.state = {
|
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu();
|
||||||
expanded: false,
|
const _userDefinedServers = useSettingValue(SETTING_NAME);
|
||||||
selectedServer: server,
|
const [userDefinedServers, _setUserDefinedServers] = useState(_userDefinedServers);
|
||||||
selectedInstanceId: null,
|
|
||||||
includeAllNetworks: false,
|
const handlerFactory = (server, instanceId) => {
|
||||||
|
return () => {
|
||||||
|
onOptionChange(server, instanceId);
|
||||||
|
closeMenu();
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
componentWillMount() {
|
const setUserDefinedServers = servers => {
|
||||||
// Listen for all clicks on the document so we can close the
|
_setUserDefinedServers(servers);
|
||||||
// menu when the user clicks somewhere else
|
SettingsStore.setValue(SETTING_NAME, null, "account", servers);
|
||||||
document.addEventListener('click', this.onDocumentClick, false);
|
};
|
||||||
|
// keep local echo up to date with external changes
|
||||||
|
useEffect(() => {
|
||||||
|
_setUserDefinedServers(_userDefinedServers);
|
||||||
|
}, [_userDefinedServers]);
|
||||||
|
|
||||||
// fire this now so the defaults can be set up
|
// we either show the button or the dropdown in its place.
|
||||||
const {selectedServer, selectedInstanceId, includeAllNetworks} = this.state;
|
let content;
|
||||||
this.props.onOptionChange(selectedServer, selectedInstanceId, includeAllNetworks);
|
if (menuDisplayed) {
|
||||||
}
|
const config = SdkConfig.get();
|
||||||
|
const roomDirectory = config.roomDirectory || {};
|
||||||
|
|
||||||
componentWillUnmount() {
|
const hsName = MatrixClientPeg.getHomeserverName();
|
||||||
document.removeEventListener('click', this.onDocumentClick, false);
|
const configServers = new Set(roomDirectory.servers);
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
// configured servers take preference over user-defined ones, if one occurs in both ignore the latter one.
|
||||||
if (this.state.expanded && this.inputTextBox) {
|
const removableServers = new Set(userDefinedServers.filter(s => !configServers.has(s) && s !== hsName));
|
||||||
this.inputTextBox.focus();
|
const servers = [
|
||||||
}
|
// we always show our connected HS, this takes precedence over it being configured or user-defined
|
||||||
}
|
hsName,
|
||||||
|
...Array.from(configServers).filter(s => s !== hsName).sort(),
|
||||||
onDocumentClick(ev) {
|
...Array.from(removableServers).sort(),
|
||||||
// Close the dropdown if the user clicks anywhere that isn't
|
];
|
||||||
// within our root element
|
|
||||||
if (ev !== this.ignoreEvent) {
|
|
||||||
this.setState({
|
|
||||||
expanded: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onRootClick(ev) {
|
|
||||||
// This captures any clicks that happen within our elements,
|
|
||||||
// such that we can then ignore them when they're seen by the
|
|
||||||
// click listener on the document handler, ie. not close the
|
|
||||||
// dropdown immediately after opening it.
|
|
||||||
// NB. We can't just stopPropagation() because then the event
|
|
||||||
// doesn't reach the React onClick().
|
|
||||||
this.ignoreEvent = ev;
|
|
||||||
}
|
|
||||||
|
|
||||||
onInputClick(ev) {
|
|
||||||
this.setState({
|
|
||||||
expanded: !this.state.expanded,
|
|
||||||
});
|
|
||||||
ev.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
onMenuOptionClick(server, instance, includeAll) {
|
|
||||||
this.setState({
|
|
||||||
expanded: false,
|
|
||||||
selectedServer: server,
|
|
||||||
selectedInstanceId: instance ? instance.instance_id : null,
|
|
||||||
includeAllNetworks: includeAll,
|
|
||||||
});
|
|
||||||
this.props.onOptionChange(server, instance ? instance.instance_id : null, includeAll);
|
|
||||||
}
|
|
||||||
|
|
||||||
onInputKeyUp(e) {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
this.setState({
|
|
||||||
expanded: false,
|
|
||||||
selectedServer: e.target.value,
|
|
||||||
selectedNetwork: null,
|
|
||||||
includeAllNetworks: false,
|
|
||||||
});
|
|
||||||
this.props.onOptionChange(e.target.value, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
collectRoot(e) {
|
|
||||||
if (this.dropdownRootElement) {
|
|
||||||
this.dropdownRootElement.removeEventListener('click', this.onRootClick, false);
|
|
||||||
}
|
|
||||||
if (e) {
|
|
||||||
e.addEventListener('click', this.onRootClick, false);
|
|
||||||
}
|
|
||||||
this.dropdownRootElement = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
collectInputTextBox(e) {
|
|
||||||
this.inputTextBox = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
_getMenuOptions() {
|
|
||||||
const options = [];
|
|
||||||
const roomDirectory = this.props.config.roomDirectory || {};
|
|
||||||
|
|
||||||
let servers = [];
|
|
||||||
if (roomDirectory.servers) {
|
|
||||||
servers = servers.concat(roomDirectory.servers);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!servers.includes(MatrixClientPeg.getHomeserverName())) {
|
|
||||||
servers.unshift(MatrixClientPeg.getHomeserverName());
|
|
||||||
}
|
|
||||||
|
|
||||||
// For our own HS, we can use the instance_ids given in the third party protocols
|
// For our own HS, we can use the instance_ids given in the third party protocols
|
||||||
// response to get the server to filter the room list by network for us.
|
// response to get the server to filter the room list by network for us.
|
||||||
// We can't get thirdparty protocols for remote server yet though, so for those
|
// We can't get thirdparty protocols for remote server yet though, so for those
|
||||||
// we can only show the default room list.
|
// we can only show the default room list.
|
||||||
for (const server of servers) {
|
const options = servers.map(server => {
|
||||||
options.push(this._makeMenuOption(server, null, true));
|
const serverSelected = server === selectedServerName;
|
||||||
if (server === MatrixClientPeg.getHomeserverName()) {
|
const entries = [];
|
||||||
options.push(this._makeMenuOption(server, null, false));
|
|
||||||
if (this.props.protocols) {
|
|
||||||
for (const proto of Object.keys(this.props.protocols)) {
|
|
||||||
if (!this.props.protocols[proto].instances) continue;
|
|
||||||
|
|
||||||
const sortedInstances = this.props.protocols[proto].instances;
|
const protocolsList = server === hsName ? Object.values(protocols) : [];
|
||||||
sortedInstances.sort(function(x, y) {
|
if (protocolsList.length > 0) {
|
||||||
const a = x.desc;
|
// add a fake protocol with the ALL_ROOMS symbol
|
||||||
const b = y.desc;
|
protocolsList.push({
|
||||||
if (a < b) {
|
instances: [{
|
||||||
return -1;
|
instance_id: ALL_ROOMS,
|
||||||
} else if (a > b) {
|
desc: _t("All rooms"),
|
||||||
return 1;
|
}],
|
||||||
} else {
|
});
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const instance of sortedInstances) {
|
|
||||||
if (!instance.instance_id) continue;
|
|
||||||
options.push(this._makeMenuOption(server, instance, false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return options;
|
protocolsList.forEach(({instances=[]}) => {
|
||||||
}
|
[...instances].sort((b, a) => {
|
||||||
|
return a.desc.localeCompare(b.desc);
|
||||||
|
}).forEach(({desc, instance_id: instanceId}) => {
|
||||||
|
entries.push(
|
||||||
|
<MenuItemRadio
|
||||||
|
key={String(instanceId)}
|
||||||
|
active={serverSelected && instanceId === selectedInstanceId}
|
||||||
|
onClick={handlerFactory(server, instanceId)}
|
||||||
|
label={desc}
|
||||||
|
className="mx_NetworkDropdown_server_network"
|
||||||
|
>
|
||||||
|
{ desc }
|
||||||
|
</MenuItemRadio>);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
_makeMenuOption(server, instance, includeAll, handleClicks) {
|
let subtitle;
|
||||||
if (handleClicks === undefined) handleClicks = true;
|
if (server === hsName) {
|
||||||
|
subtitle = (
|
||||||
|
<div className="mx_NetworkDropdown_server_subtitle">
|
||||||
|
{_t("Your server")}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let icon;
|
let removeButton;
|
||||||
let name;
|
if (removableServers.has(server)) {
|
||||||
let key;
|
const onClick = async () => {
|
||||||
|
closeMenu();
|
||||||
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
|
const {finished} = Modal.createTrackedDialog("Network Dropdown", "Remove server", QuestionDialog, {
|
||||||
|
title: _t("Are you sure?"),
|
||||||
|
description: _t("Are you sure you want to remove <b>%(serverName)s</b>", {
|
||||||
|
serverName: server,
|
||||||
|
}, {
|
||||||
|
b: serverName => <b>{ serverName }</b>,
|
||||||
|
}),
|
||||||
|
button: _t("Remove"),
|
||||||
|
fixedWidth: false,
|
||||||
|
}, "mx_NetworkDropdown_dialog");
|
||||||
|
|
||||||
if (!instance && includeAll) {
|
const [ok] = await finished;
|
||||||
key = server;
|
if (!ok) return;
|
||||||
name = server;
|
|
||||||
} else if (!instance) {
|
|
||||||
key = server + '_all';
|
|
||||||
name = 'Matrix';
|
|
||||||
icon = <img src={require("../../../../res/img/network-matrix.svg")} />;
|
|
||||||
} else {
|
|
||||||
key = server + '_inst_' + instance.instance_id;
|
|
||||||
const imgUrl = instance.icon ?
|
|
||||||
MatrixClientPeg.get().mxcUrlToHttp(instance.icon, 25, 25, 'crop', true) :
|
|
||||||
DEFAULT_ICON_URL;
|
|
||||||
icon = <img src={imgUrl} />;
|
|
||||||
name = instance.desc;
|
|
||||||
}
|
|
||||||
|
|
||||||
const clickHandler = handleClicks ? this.onMenuOptionClick.bind(this, server, instance, includeAll) : null;
|
// delete from setting
|
||||||
|
setUserDefinedServers(servers.filter(s => s !== server));
|
||||||
|
|
||||||
return <div key={key} className="mx_NetworkDropdown_networkoption" onClick={clickHandler}>
|
// the selected server is being removed, reset to our HS
|
||||||
{icon}
|
if (serverSelected === server) {
|
||||||
<span className="mx_NetworkDropdown_menu_network">{name}</span>
|
onOptionChange(hsName, undefined);
|
||||||
</div>;
|
}
|
||||||
}
|
};
|
||||||
|
removeButton = <MenuItem onClick={onClick} label={_t("Remove server")} />;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
// ARIA: in actual fact the entire menu is one large radio group but for better screen reader support
|
||||||
let currentValue;
|
// we use group to notate server wrongly.
|
||||||
|
return (
|
||||||
|
<MenuGroup label={server} className="mx_NetworkDropdown_server" key={server}>
|
||||||
|
<div className="mx_NetworkDropdown_server_title">
|
||||||
|
{ server }
|
||||||
|
{ removeButton }
|
||||||
|
</div>
|
||||||
|
{ subtitle }
|
||||||
|
|
||||||
let menu;
|
<MenuItemRadio
|
||||||
if (this.state.expanded) {
|
active={serverSelected && !selectedInstanceId}
|
||||||
const menuOptions = this._getMenuOptions();
|
onClick={handlerFactory(server, undefined)}
|
||||||
menu = <div className="mx_NetworkDropdown_menu">
|
label={_t("Matrix")}
|
||||||
{menuOptions}
|
className="mx_NetworkDropdown_server_network"
|
||||||
</div>;
|
>
|
||||||
currentValue = <input type="text" className="mx_NetworkDropdown_networkoption"
|
{_t("Matrix")}
|
||||||
ref={this.collectInputTextBox} onKeyUp={this.onInputKeyUp}
|
</MenuItemRadio>
|
||||||
placeholder="matrix.org" // 'matrix.org' as an example of an HS name
|
{ entries }
|
||||||
/>;
|
</MenuGroup>
|
||||||
} else {
|
|
||||||
const instance = instanceForInstanceId(this.props.protocols, this.state.selectedInstanceId);
|
|
||||||
currentValue = this._makeMenuOption(
|
|
||||||
this.state.selectedServer, instance, this.state.includeAllNetworks, false,
|
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const onClick = async () => {
|
||||||
|
closeMenu();
|
||||||
|
const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog");
|
||||||
|
const { finished } = Modal.createTrackedDialog("Network Dropdown", "Add a new server", TextInputDialog, {
|
||||||
|
title: _t("Add a new server"),
|
||||||
|
description: _t("Enter the name of a new server you want to explore."),
|
||||||
|
button: _t("Add"),
|
||||||
|
hasCancel: false,
|
||||||
|
placeholder: _t("Server name"),
|
||||||
|
validator: validServer,
|
||||||
|
fixedWidth: false,
|
||||||
|
}, "mx_NetworkDropdown_dialog");
|
||||||
|
|
||||||
|
const [ok, newServer] = await finished;
|
||||||
|
if (!ok) return;
|
||||||
|
|
||||||
|
if (!userDefinedServers.includes(newServer)) {
|
||||||
|
setUserDefinedServers([...userDefinedServers, newServer]);
|
||||||
|
}
|
||||||
|
|
||||||
|
onOptionChange(newServer); // change filter to the new server
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonRect = handle.current.getBoundingClientRect();
|
||||||
|
content = <ContextMenu {...inPlaceOf(buttonRect)} onFinished={closeMenu}>
|
||||||
|
<div className="mx_NetworkDropdown_menu">
|
||||||
|
{options}
|
||||||
|
<MenuItem className="mx_NetworkDropdown_server_add" label={undefined} onClick={onClick}>
|
||||||
|
{_t("Add a new server...")}
|
||||||
|
</MenuItem>
|
||||||
|
</div>
|
||||||
|
</ContextMenu>;
|
||||||
|
} else {
|
||||||
|
let currentValue;
|
||||||
|
if (selectedInstanceId === ALL_ROOMS) {
|
||||||
|
currentValue = _t("All rooms");
|
||||||
|
} else if (selectedInstanceId) {
|
||||||
|
const instance = instanceForInstanceId(protocols, selectedInstanceId);
|
||||||
|
currentValue = _t("%(networkName)s rooms", {
|
||||||
|
networkName: instance.desc,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
currentValue = _t("Matrix rooms");
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="mx_NetworkDropdown" ref={this.collectRoot}>
|
content = <ContextMenuButton
|
||||||
<div className="mx_NetworkDropdown_input mx_no_textinput" onClick={this.onInputClick}>
|
className="mx_NetworkDropdown_handle"
|
||||||
|
onClick={openMenu}
|
||||||
|
isExpanded={menuDisplayed}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
{currentValue}
|
{currentValue}
|
||||||
<span className="mx_NetworkDropdown_arrow" />
|
</span> <span className="mx_NetworkDropdown_handle_server">
|
||||||
{menu}
|
({selectedServerName})
|
||||||
</div>
|
</span>
|
||||||
</div>;
|
</ContextMenuButton>;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return <div className="mx_NetworkDropdown" ref={handle}>
|
||||||
|
{content}
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
NetworkDropdown.propTypes = {
|
NetworkDropdown.propTypes = {
|
||||||
onOptionChange: PropTypes.func.isRequired,
|
onOptionChange: PropTypes.func.isRequired,
|
||||||
protocols: PropTypes.object,
|
protocols: PropTypes.object,
|
||||||
// The room directory config. May have a 'servers' key that is a list of server names to include in the dropdown
|
|
||||||
config: PropTypes.object,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
NetworkDropdown.defaultProps = {
|
export default NetworkDropdown;
|
||||||
protocols: {},
|
|
||||||
config: {},
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2018 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 GeminiScrollbar from 'react-gemini-scrollbar';
|
|
||||||
|
|
||||||
function GeminiScrollbarWrapper(props) {
|
|
||||||
const {wrappedRef, ...wrappedProps} = props;
|
|
||||||
|
|
||||||
// Enable forceGemini so that gemini is always enabled. This is
|
|
||||||
// to avoid future issues where a feature is implemented without
|
|
||||||
// doing QA on every OS/browser combination.
|
|
||||||
//
|
|
||||||
// By default GeminiScrollbar allows native scrollbars to be used
|
|
||||||
// on macOS. Use forceGemini to enable Gemini's non-native
|
|
||||||
// scrollbars on all OSs.
|
|
||||||
return <GeminiScrollbar ref={wrappedRef} forceGemini={true} {...wrappedProps}>
|
|
||||||
{ props.children }
|
|
||||||
</GeminiScrollbar>;
|
|
||||||
}
|
|
||||||
export default GeminiScrollbarWrapper;
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import { GroupMemberType } from '../../../groups';
|
||||||
import GroupStore from '../../../stores/GroupStore';
|
import GroupStore from '../../../stores/GroupStore';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||||
|
|
||||||
export default createReactClass({
|
export default createReactClass({
|
||||||
displayName: 'GroupMemberInfo',
|
displayName: 'GroupMemberInfo',
|
||||||
|
@ -182,10 +183,9 @@ export default createReactClass({
|
||||||
this.props.groupMember.displayname || this.props.groupMember.userId
|
this.props.groupMember.displayname || this.props.groupMember.userId
|
||||||
);
|
);
|
||||||
|
|
||||||
const GeminiScrollbarWrapper = sdk.getComponent('elements.GeminiScrollbarWrapper');
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_MemberInfo" role="tabpanel">
|
<div className="mx_MemberInfo" role="tabpanel">
|
||||||
<GeminiScrollbarWrapper autoshow={true}>
|
<AutoHideScrollbar>
|
||||||
<AccessibleButton className="mx_MemberInfo_cancel" onClick={this._onCancel}>
|
<AccessibleButton className="mx_MemberInfo_cancel" onClick={this._onCancel}>
|
||||||
<img src={require("../../../../res/img/cancel.svg")} width="18" height="18" className="mx_filterFlipColor" />
|
<img src={require("../../../../res/img/cancel.svg")} width="18" height="18" className="mx_filterFlipColor" />
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
|
@ -199,7 +199,7 @@ export default createReactClass({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ adminTools }
|
{ adminTools }
|
||||||
</GeminiScrollbarWrapper>
|
</AutoHideScrollbar>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -26,6 +26,7 @@ import { showGroupInviteDialog } from '../../../GroupAddressPicker';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import TintableSvg from '../elements/TintableSvg';
|
import TintableSvg from '../elements/TintableSvg';
|
||||||
import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases";
|
import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases";
|
||||||
|
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||||
|
|
||||||
const INITIAL_LOAD_NUM_MEMBERS = 30;
|
const INITIAL_LOAD_NUM_MEMBERS = 30;
|
||||||
|
|
||||||
|
@ -172,7 +173,6 @@ export default createReactClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
|
||||||
if (this.state.fetching || this.state.fetchingInvitedMembers) {
|
if (this.state.fetching || this.state.fetchingInvitedMembers) {
|
||||||
const Spinner = sdk.getComponent("elements.Spinner");
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
return (<div className="mx_MemberList">
|
return (<div className="mx_MemberList">
|
||||||
|
@ -225,10 +225,10 @@ export default createReactClass({
|
||||||
return (
|
return (
|
||||||
<div className="mx_MemberList" role="tabpanel">
|
<div className="mx_MemberList" role="tabpanel">
|
||||||
{ inviteButton }
|
{ inviteButton }
|
||||||
<GeminiScrollbarWrapper autoshow={true}>
|
<AutoHideScrollbar>
|
||||||
{ joined }
|
{ joined }
|
||||||
{ invited }
|
{ invited }
|
||||||
</GeminiScrollbarWrapper>
|
</AutoHideScrollbar>
|
||||||
{ inputBox }
|
{ inputBox }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -24,6 +24,7 @@ import * as sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import GroupStore from '../../../stores/GroupStore';
|
import GroupStore from '../../../stores/GroupStore';
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||||
|
|
||||||
export default createReactClass({
|
export default createReactClass({
|
||||||
displayName: 'GroupRoomInfo',
|
displayName: 'GroupRoomInfo',
|
||||||
|
@ -153,7 +154,6 @@ export default createReactClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
const InlineSpinner = sdk.getComponent('elements.InlineSpinner');
|
const InlineSpinner = sdk.getComponent('elements.InlineSpinner');
|
||||||
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
|
||||||
if (this.state.groupRoomRemoveLoading || !this.state.groupRoom) {
|
if (this.state.groupRoomRemoveLoading || !this.state.groupRoom) {
|
||||||
const Spinner = sdk.getComponent("elements.Spinner");
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
return <div className="mx_MemberInfo">
|
return <div className="mx_MemberInfo">
|
||||||
|
@ -216,7 +216,7 @@ export default createReactClass({
|
||||||
const groupRoomName = this.state.groupRoom.displayname;
|
const groupRoomName = this.state.groupRoom.displayname;
|
||||||
return (
|
return (
|
||||||
<div className="mx_MemberInfo" role="tabpanel">
|
<div className="mx_MemberInfo" role="tabpanel">
|
||||||
<GeminiScrollbarWrapper autoshow={true}>
|
<AutoHideScrollbar>
|
||||||
<AccessibleButton className="mx_MemberInfo_cancel" onClick={this._onCancel}>
|
<AccessibleButton className="mx_MemberInfo_cancel" onClick={this._onCancel}>
|
||||||
<img src={require("../../../../res/img/cancel.svg")} width="18" height="18" className="mx_filterFlipColor" />
|
<img src={require("../../../../res/img/cancel.svg")} width="18" height="18" className="mx_filterFlipColor" />
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
|
@ -231,7 +231,7 @@ export default createReactClass({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ adminTools }
|
{ adminTools }
|
||||||
</GeminiScrollbarWrapper>
|
</AutoHideScrollbar>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -22,6 +22,7 @@ import PropTypes from 'prop-types';
|
||||||
import { showGroupAddRoomDialog } from '../../../GroupAddressPicker';
|
import { showGroupAddRoomDialog } from '../../../GroupAddressPicker';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import TintableSvg from '../elements/TintableSvg';
|
import TintableSvg from '../elements/TintableSvg';
|
||||||
|
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||||
|
|
||||||
const INITIAL_LOAD_NUM_ROOMS = 30;
|
const INITIAL_LOAD_NUM_ROOMS = 30;
|
||||||
|
|
||||||
|
@ -150,17 +151,16 @@ export default createReactClass({
|
||||||
placeholder={_t('Filter community rooms')} autoComplete="off" />
|
placeholder={_t('Filter community rooms')} autoComplete="off" />
|
||||||
);
|
);
|
||||||
|
|
||||||
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
|
||||||
const TruncatedList = sdk.getComponent("elements.TruncatedList");
|
const TruncatedList = sdk.getComponent("elements.TruncatedList");
|
||||||
return (
|
return (
|
||||||
<div className="mx_GroupRoomList" role="tabpanel">
|
<div className="mx_GroupRoomList" role="tabpanel">
|
||||||
{ inviteButton }
|
{ inviteButton }
|
||||||
<GeminiScrollbarWrapper autoshow={true} className="mx_GroupRoomList_joined mx_GroupRoomList_outerWrapper">
|
<AutoHideScrollbar className="mx_GroupRoomList_joined mx_GroupRoomList_outerWrapper">
|
||||||
<TruncatedList className="mx_GroupRoomList_wrapper" truncateAt={this.state.truncateAt}
|
<TruncatedList className="mx_GroupRoomList_wrapper" truncateAt={this.state.truncateAt}
|
||||||
createOverflowElement={this._createOverflowTile}>
|
createOverflowElement={this._createOverflowTile}>
|
||||||
{ this.makeGroupRoomTiles(this.state.searchQuery) }
|
{ this.makeGroupRoomTiles(this.state.searchQuery) }
|
||||||
</TruncatedList>
|
</TruncatedList>
|
||||||
</GeminiScrollbarWrapper>
|
</AutoHideScrollbar>
|
||||||
{ inputBox }
|
{ inputBox }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -236,8 +236,7 @@ export default class AliasSettings extends React.Component {
|
||||||
// TODO: In future, we should probably be making sure that the alias actually belongs
|
// TODO: In future, we should probably be making sure that the alias actually belongs
|
||||||
// to this room. See https://github.com/vector-im/riot-web/issues/7353
|
// to this room. See https://github.com/vector-im/riot-web/issues/7353
|
||||||
MatrixClientPeg.get().deleteAlias(alias).then(() => {
|
MatrixClientPeg.get().deleteAlias(alias).then(() => {
|
||||||
const localAliases = this.state.localAliases.slice();
|
const localAliases = this.state.localAliases.filter(a => a !== alias);
|
||||||
localAliases.splice(index, 1);
|
|
||||||
this.setState({localAliases});
|
this.setState({localAliases});
|
||||||
|
|
||||||
if (this.state.canonicalAlias === alias) {
|
if (this.state.canonicalAlias === alias) {
|
||||||
|
|
|
@ -370,6 +370,16 @@ export default class BasicMessageEditor extends React.Component {
|
||||||
} else if (modKey && event.key === Key.GREATER_THAN) {
|
} else if (modKey && event.key === Key.GREATER_THAN) {
|
||||||
this._onFormatAction("quote");
|
this._onFormatAction("quote");
|
||||||
handled = true;
|
handled = true;
|
||||||
|
// redo
|
||||||
|
} else if ((!IS_MAC && modKey && event.key === Key.Y) ||
|
||||||
|
(IS_MAC && modKey && event.shiftKey && event.key === Key.Z)) {
|
||||||
|
if (this.historyManager.canRedo()) {
|
||||||
|
const {parts, caret} = this.historyManager.redo();
|
||||||
|
// pass matching inputType so historyManager doesn't push echo
|
||||||
|
// when invoked from rerender callback.
|
||||||
|
model.reset(parts, caret, "historyRedo");
|
||||||
|
}
|
||||||
|
handled = true;
|
||||||
// undo
|
// undo
|
||||||
} else if (modKey && event.key === Key.Z) {
|
} else if (modKey && event.key === Key.Z) {
|
||||||
if (this.historyManager.canUndo()) {
|
if (this.historyManager.canUndo()) {
|
||||||
|
@ -379,15 +389,6 @@ export default class BasicMessageEditor extends React.Component {
|
||||||
model.reset(parts, caret, "historyUndo");
|
model.reset(parts, caret, "historyUndo");
|
||||||
}
|
}
|
||||||
handled = true;
|
handled = true;
|
||||||
// redo
|
|
||||||
} else if (modKey && event.key === Key.Y) {
|
|
||||||
if (this.historyManager.canRedo()) {
|
|
||||||
const {parts, caret} = this.historyManager.redo();
|
|
||||||
// pass matching inputType so historyManager doesn't push echo
|
|
||||||
// when invoked from rerender callback.
|
|
||||||
model.reset(parts, caret, "historyRedo");
|
|
||||||
}
|
|
||||||
handled = true;
|
|
||||||
// insert newline on Shift+Enter
|
// insert newline on Shift+Enter
|
||||||
} else if (event.key === Key.ENTER && (event.shiftKey || (IS_MAC && event.altKey))) {
|
} else if (event.key === Key.ENTER && (event.shiftKey || (IS_MAC && event.altKey))) {
|
||||||
this._insertText("\n");
|
this._insertText("\n");
|
||||||
|
|
|
@ -24,7 +24,7 @@ export default (props) => {
|
||||||
}
|
}
|
||||||
return (<div className="mx_JumpToBottomButton">
|
return (<div className="mx_JumpToBottomButton">
|
||||||
<AccessibleButton className="mx_JumpToBottomButton_scrollDown"
|
<AccessibleButton className="mx_JumpToBottomButton_scrollDown"
|
||||||
title={_t("Scroll to bottom of page")}
|
title={_t("Scroll to most recent messages")}
|
||||||
onClick={props.onScrollToBottomClick}>
|
onClick={props.onScrollToBottomClick}>
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
{ badge }
|
{ badge }
|
||||||
|
|
|
@ -1,186 +0,0 @@
|
||||||
/*
|
|
||||||
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.
|
|
||||||
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 createReactClass from 'create-react-class';
|
|
||||||
import * as sdk from "../../../index";
|
|
||||||
import { _t } from '../../../languageHandler';
|
|
||||||
|
|
||||||
// A list capable of displaying entities which conform to the SearchableEntity
|
|
||||||
// interface which is an object containing getJsx(): Jsx and matches(query: string): boolean
|
|
||||||
const SearchableEntityList = createReactClass({
|
|
||||||
displayName: 'SearchableEntityList',
|
|
||||||
|
|
||||||
propTypes: {
|
|
||||||
emptyQueryShowsAll: PropTypes.bool,
|
|
||||||
showInputBox: PropTypes.bool,
|
|
||||||
onQueryChanged: PropTypes.func, // fn(inputText)
|
|
||||||
onSubmit: PropTypes.func, // fn(inputText)
|
|
||||||
entities: PropTypes.array,
|
|
||||||
truncateAt: PropTypes.number,
|
|
||||||
},
|
|
||||||
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
showInputBox: true,
|
|
||||||
entities: [],
|
|
||||||
emptyQueryShowsAll: false,
|
|
||||||
onSubmit: function() {},
|
|
||||||
onQueryChanged: function(input) {},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
query: "",
|
|
||||||
focused: false,
|
|
||||||
truncateAt: this.props.truncateAt,
|
|
||||||
results: this.getSearchResults("", this.props.entities),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillReceiveProps: function(newProps) {
|
|
||||||
// recalculate the search results in case we got new entities
|
|
||||||
this.setState({
|
|
||||||
results: this.getSearchResults(this.state.query, newProps.entities),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
|
||||||
// pretend the query box was blanked out else filters could still be
|
|
||||||
// applied to other components which rely on onQueryChanged.
|
|
||||||
this.props.onQueryChanged("");
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Public-facing method to set the input query text to the given input.
|
|
||||||
* @param {string} input
|
|
||||||
*/
|
|
||||||
setQuery: function(input) {
|
|
||||||
this.setState({
|
|
||||||
query: input,
|
|
||||||
results: this.getSearchResults(input, this.props.entities),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onQueryChanged: function(ev) {
|
|
||||||
const q = ev.target.value;
|
|
||||||
this.setState({
|
|
||||||
query: q,
|
|
||||||
// reset truncation if they back out the entire text
|
|
||||||
truncateAt: (q.length === 0 ? this.props.truncateAt : this.state.truncateAt),
|
|
||||||
results: this.getSearchResults(q, this.props.entities),
|
|
||||||
}, () => {
|
|
||||||
// invoke the callback AFTER we've flushed the new state. We need to
|
|
||||||
// do this because onQueryChanged can result in new props being passed
|
|
||||||
// to this component, which will then try to recalculate the search
|
|
||||||
// list. If we do this without flushing, we'll recalc with the last
|
|
||||||
// search term and not the current one!
|
|
||||||
this.props.onQueryChanged(q);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onQuerySubmit: function(ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
this.props.onSubmit(this.state.query);
|
|
||||||
},
|
|
||||||
|
|
||||||
getSearchResults: function(query, entities) {
|
|
||||||
if (!query || query.length === 0) {
|
|
||||||
return this.props.emptyQueryShowsAll ? entities : [];
|
|
||||||
}
|
|
||||||
return entities.filter(function(e) {
|
|
||||||
return e.matches(query);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_showAll: function() {
|
|
||||||
this.setState({
|
|
||||||
truncateAt: -1,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_createOverflowEntity: function(overflowCount, totalCount) {
|
|
||||||
const EntityTile = sdk.getComponent("rooms.EntityTile");
|
|
||||||
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
|
||||||
const text = _t("and %(count)s others...", { count: overflowCount });
|
|
||||||
return (
|
|
||||||
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
|
|
||||||
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
|
||||||
} name={text} presenceState="online" suppressOnHover={true}
|
|
||||||
onClick={this._showAll} />
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
|
||||||
let inputBox;
|
|
||||||
|
|
||||||
if (this.props.showInputBox) {
|
|
||||||
inputBox = (
|
|
||||||
<form onSubmit={this.onQuerySubmit} autoComplete="off">
|
|
||||||
<input className="mx_SearchableEntityList_query" id="mx_SearchableEntityList_query" type="text"
|
|
||||||
onChange={this.onQueryChanged} value={this.state.query}
|
|
||||||
onFocus= {() => { this.setState({ focused: true }); }}
|
|
||||||
onBlur= {() => { this.setState({ focused: false }); }}
|
|
||||||
placeholder={_t("Search")} />
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let list;
|
|
||||||
if (this.state.results.length > 1 || this.state.focused) {
|
|
||||||
if (this.props.truncateAt) { // caller wants list truncated
|
|
||||||
const TruncatedList = sdk.getComponent("elements.TruncatedList");
|
|
||||||
list = (
|
|
||||||
<TruncatedList className="mx_SearchableEntityList_list"
|
|
||||||
truncateAt={this.state.truncateAt} // use state truncation as it may be expanded
|
|
||||||
createOverflowElement={this._createOverflowEntity}>
|
|
||||||
{ this.state.results.map((entity) => {
|
|
||||||
return entity.getJsx();
|
|
||||||
}) }
|
|
||||||
</TruncatedList>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
list = (
|
|
||||||
<div className="mx_SearchableEntityList_list">
|
|
||||||
{ this.state.results.map((entity) => {
|
|
||||||
return entity.getJsx();
|
|
||||||
}) }
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
|
||||||
list = (
|
|
||||||
<GeminiScrollbarWrapper autoshow={true}
|
|
||||||
className="mx_SearchableEntityList_listWrapper">
|
|
||||||
{ list }
|
|
||||||
</GeminiScrollbarWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={"mx_SearchableEntityList " + (list ? "mx_SearchableEntityList_expanded" : "")}>
|
|
||||||
{ inputBox }
|
|
||||||
{ list }
|
|
||||||
{ list ? <div className="mx_SearchableEntityList_hrWrapper"><hr /></div> : '' }
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default SearchableEntityList;
|
|
|
@ -961,7 +961,7 @@
|
||||||
"Encrypted by a deleted session": "Encrypted by a deleted session",
|
"Encrypted by a deleted session": "Encrypted by a deleted session",
|
||||||
"Please select the destination room for this message": "Please select the destination room for this message",
|
"Please select the destination room for this message": "Please select the destination room for this message",
|
||||||
"Invite only": "Invite only",
|
"Invite only": "Invite only",
|
||||||
"Scroll to bottom of page": "Scroll to bottom of page",
|
"Scroll to most recent messages": "Scroll to most recent messages",
|
||||||
"Close preview": "Close preview",
|
"Close preview": "Close preview",
|
||||||
"device id: ": "device id: ",
|
"device id: ": "device id: ",
|
||||||
"Disinvite": "Disinvite",
|
"Disinvite": "Disinvite",
|
||||||
|
@ -1450,6 +1450,20 @@
|
||||||
"And %(count)s more...|other": "And %(count)s more...",
|
"And %(count)s more...|other": "And %(count)s more...",
|
||||||
"ex. @bob:example.com": "ex. @bob:example.com",
|
"ex. @bob:example.com": "ex. @bob:example.com",
|
||||||
"Add User": "Add User",
|
"Add User": "Add User",
|
||||||
|
"Enter a server name": "Enter a server name",
|
||||||
|
"Looks good": "Looks good",
|
||||||
|
"Can't find this server or its room list": "Can't find this server or its room list",
|
||||||
|
"All rooms": "All rooms",
|
||||||
|
"Your server": "Your server",
|
||||||
|
"Are you sure you want to remove <b>%(serverName)s</b>": "Are you sure you want to remove <b>%(serverName)s</b>",
|
||||||
|
"Remove server": "Remove server",
|
||||||
|
"Matrix": "Matrix",
|
||||||
|
"Add a new server": "Add a new server",
|
||||||
|
"Enter the name of a new server you want to explore.": "Enter the name of a new server you want to explore.",
|
||||||
|
"Server name": "Server name",
|
||||||
|
"Add a new server...": "Add a new server...",
|
||||||
|
"%(networkName)s rooms": "%(networkName)s rooms",
|
||||||
|
"Matrix rooms": "Matrix rooms",
|
||||||
"Matrix ID": "Matrix ID",
|
"Matrix ID": "Matrix ID",
|
||||||
"Matrix Room ID": "Matrix Room ID",
|
"Matrix Room ID": "Matrix Room ID",
|
||||||
"email address": "email address",
|
"email address": "email address",
|
||||||
|
|
|
@ -330,6 +330,10 @@ export const SETTINGS = {
|
||||||
supportedLevels: ['account'],
|
supportedLevels: ['account'],
|
||||||
default: [],
|
default: [],
|
||||||
},
|
},
|
||||||
|
"room_directory_servers": {
|
||||||
|
supportedLevels: ['account'],
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
"integrationProvisioning": {
|
"integrationProvisioning": {
|
||||||
supportedLevels: ['account'],
|
supportedLevels: ['account'],
|
||||||
default: true,
|
default: true,
|
||||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -3907,10 +3907,6 @@ fuse.js@^2.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-2.7.4.tgz#96e420fde7ef011ac49c258a621314fe576536f9"
|
resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-2.7.4.tgz#96e420fde7ef011ac49c258a621314fe576536f9"
|
||||||
integrity sha1-luQg/efvARrEnCWKYhMU/ldlNvk=
|
integrity sha1-luQg/efvARrEnCWKYhMU/ldlNvk=
|
||||||
|
|
||||||
"gemini-scrollbar@github:matrix-org/gemini-scrollbar#91e1e566", gemini-scrollbar@matrix-org/gemini-scrollbar#91e1e566:
|
|
||||||
version "1.4.3"
|
|
||||||
resolved "https://codeload.github.com/matrix-org/gemini-scrollbar/tar.gz/91e1e566fa33324188f278801baf4a79f9f554ab"
|
|
||||||
|
|
||||||
gensync@^1.0.0-beta.1:
|
gensync@^1.0.0-beta.1:
|
||||||
version "1.0.0-beta.1"
|
version "1.0.0-beta.1"
|
||||||
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269"
|
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269"
|
||||||
|
@ -6968,12 +6964,6 @@ react-focus-lock@^2.2.1:
|
||||||
use-callback-ref "^1.2.1"
|
use-callback-ref "^1.2.1"
|
||||||
use-sidecar "^1.0.1"
|
use-sidecar "^1.0.1"
|
||||||
|
|
||||||
"react-gemini-scrollbar@github:matrix-org/react-gemini-scrollbar#9cf17f63b7c0b0ec5f31df27da0f82f7238dc594":
|
|
||||||
version "2.1.5"
|
|
||||||
resolved "https://codeload.github.com/matrix-org/react-gemini-scrollbar/tar.gz/9cf17f63b7c0b0ec5f31df27da0f82f7238dc594"
|
|
||||||
dependencies:
|
|
||||||
gemini-scrollbar matrix-org/gemini-scrollbar#91e1e566
|
|
||||||
|
|
||||||
react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6, react-is@^16.9.0:
|
react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6, react-is@^16.9.0:
|
||||||
version "16.13.0"
|
version "16.13.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.0.tgz#0f37c3613c34fe6b37cd7f763a0d6293ab15c527"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.0.tgz#0f37c3613c34fe6b37cd7f763a0d6293ab15c527"
|
||||||
|
|
Loading…
Reference in New Issue