Merge branches 'develop' and 't3chguy/kbd' of github.com:matrix-org/matrix-react-sdk into t3chguy/kbd

pull/21833/head
Michael Telatynski 2020-03-18 20:41:13 +00:00
commit d593a76f28
40 changed files with 541 additions and 730 deletions

View File

@ -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",

View File

@ -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.

View File

@ -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";

View File

@ -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;
} }

View File

@ -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

View File

@ -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.

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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;
}

View File

@ -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 {

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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
}; };

View File

@ -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}

View File

@ -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>
); );

View File

@ -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();

View File

@ -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.",

View File

@ -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}

View File

@ -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) {

View File

@ -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;

View File

@ -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>;
}, },
}); });

View File

@ -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 }

View File

@ -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>
); );
}, },

View File

@ -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} />

View File

@ -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: {},
};

View File

@ -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;

View File

@ -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>
); );
}, },

View File

@ -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>
); );

View File

@ -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>
); );
}, },

View File

@ -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>
); );

View File

@ -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) {

View File

@ -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");

View File

@ -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 }

View File

@ -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;

View File

@ -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",

View File

@ -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,

View File

@ -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"