Merge branch 'develop' into jaywink/hosting-provider-iframe-minimize-wip
This commit is contained in:
commit
5fe3c83f27
26 changed files with 568 additions and 133 deletions
|
@ -107,6 +107,7 @@
|
||||||
@import "./views/elements/_AddressTile.scss";
|
@import "./views/elements/_AddressTile.scss";
|
||||||
@import "./views/elements/_DesktopBuildsNotice.scss";
|
@import "./views/elements/_DesktopBuildsNotice.scss";
|
||||||
@import "./views/elements/_DirectorySearchBox.scss";
|
@import "./views/elements/_DirectorySearchBox.scss";
|
||||||
|
@import "./views/elements/_DesktopCapturerSourcePicker.scss";
|
||||||
@import "./views/elements/_Dropdown.scss";
|
@import "./views/elements/_Dropdown.scss";
|
||||||
@import "./views/elements/_EditableItemList.scss";
|
@import "./views/elements/_EditableItemList.scss";
|
||||||
@import "./views/elements/_ErrorBoundary.scss";
|
@import "./views/elements/_ErrorBoundary.scss";
|
||||||
|
|
|
@ -134,7 +134,7 @@ limitations under the License.
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
mask-size: contain;
|
mask-size: contain;
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
mask-image: url('$(res)/img/feather-customised/widget/maximise.svg');
|
mask-image: url('$(res)/img/feather-customised/maximise.svg');
|
||||||
background: $muted-fg-color;
|
background: $muted-fg-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,28 +64,23 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomDirectory_table {
|
.mx_RoomDirectory_table {
|
||||||
font-size: $font-12px;
|
|
||||||
color: $primary-fg-color;
|
color: $primary-fg-color;
|
||||||
width: 100%;
|
display: grid;
|
||||||
|
font-size: $font-12px;
|
||||||
|
grid-template-columns: max-content auto max-content max-content max-content;
|
||||||
|
row-gap: 24px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
table-layout: fixed;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomDirectory_roomAvatar {
|
.mx_RoomDirectory_roomAvatar {
|
||||||
width: 32px;
|
padding: 2px 14px 0 0;
|
||||||
padding-right: 14px;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomDirectory_roomDescription {
|
|
||||||
padding-bottom: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomDirectory_roomMemberCount {
|
.mx_RoomDirectory_roomMemberCount {
|
||||||
|
align-self: center;
|
||||||
color: $light-fg-color;
|
color: $light-fg-color;
|
||||||
width: 60px;
|
padding: 3px 10px 0;
|
||||||
padding: 0 10px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
background-color: $light-fg-color;
|
background-color: $light-fg-color;
|
||||||
|
@ -105,8 +100,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomDirectory_join, .mx_RoomDirectory_preview {
|
.mx_RoomDirectory_join, .mx_RoomDirectory_preview {
|
||||||
width: 80px;
|
align-self: center;
|
||||||
text-align: center;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
72
res/css/views/elements/_DesktopCapturerSourcePicker.scss
Normal file
72
res/css/views/elements/_DesktopCapturerSourcePicker.scss
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
|
||||||
|
|
||||||
|
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_desktopCapturerSourcePicker {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_desktopCapturerSourcePicker_tabLabels {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 0 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_desktopCapturerSourcePicker_tabLabel,
|
||||||
|
.mx_desktopCapturerSourcePicker_tabLabel_selected {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 0;
|
||||||
|
font-size: $font-13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_desktopCapturerSourcePicker_tabLabel_selected {
|
||||||
|
background-color: $tab-label-active-bg-color;
|
||||||
|
color: $tab-label-active-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_desktopCapturerSourcePicker_panel {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
height: 500px;
|
||||||
|
overflow: overlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_desktopCapturerSourcePicker_stream_button {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_desktopCapturerSourcePicker_stream_button:hover,
|
||||||
|
.mx_desktopCapturerSourcePicker_stream_button:focus {
|
||||||
|
background: $roomtile-selected-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_desktopCapturerSourcePicker_stream_thumbnail {
|
||||||
|
margin: 4px;
|
||||||
|
width: 312px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_desktopCapturerSourcePicker_stream_name {
|
||||||
|
margin: 0 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 312px;
|
||||||
|
}
|
|
@ -35,13 +35,13 @@ limitations under the License.
|
||||||
mask-size: auto 12px;
|
mask-size: auto 12px;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
background-color: $accent-color;
|
background-color: $accent-color;
|
||||||
mask-image: url('$(res)/img/feather-customised/widget/maximise.svg');
|
mask-image: url('$(res)/img/feather-customised/maximise.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mx_ViewSourceEvent_expanded .mx_ViewSourceEvent_toggle {
|
&.mx_ViewSourceEvent_expanded .mx_ViewSourceEvent_toggle {
|
||||||
mask-position: 0 bottom;
|
mask-position: 0 bottom;
|
||||||
margin-bottom: 7px;
|
margin-bottom: 7px;
|
||||||
mask-image: url('$(res)/img/feather-customised/widget/minimise.svg');
|
mask-image: url('$(res)/img/feather-customised/minimise.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover .mx_ViewSourceEvent_toggle {
|
&:hover .mx_ViewSourceEvent_toggle {
|
||||||
|
|
|
@ -491,7 +491,6 @@ $left-gutter: 64px;
|
||||||
// https://github.com/vector-im/vector-web/issues/754
|
// https://github.com/vector-im/vector-web/issues/754
|
||||||
overflow-x: overlay;
|
overflow-x: overlay;
|
||||||
overflow-y: visible;
|
overflow-y: visible;
|
||||||
max-height: 30vh;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
|
@ -500,6 +499,22 @@ $left-gutter: 64px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_lineNumbers {
|
||||||
|
float: left;
|
||||||
|
margin: 0 0.5em 0 -1.5em;
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_lineNumber {
|
||||||
|
text-align: right;
|
||||||
|
display: block;
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_collapsedCodeBlock {
|
||||||
|
max-height: 30vh;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_EventTile:hover .mx_EventTile_body pre,
|
.mx_EventTile:hover .mx_EventTile_body pre,
|
||||||
.mx_EventTile.focus-visible:focus-within .mx_EventTile_body pre {
|
.mx_EventTile.focus-visible:focus-within .mx_EventTile_body pre {
|
||||||
border: 1px solid #e5e5e5; // deliberate constant as we're behind an invert filter
|
border: 1px solid #e5e5e5; // deliberate constant as we're behind an invert filter
|
||||||
|
@ -511,7 +526,7 @@ $left-gutter: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inserted adjacent to <pre> blocks, (See TextualBody)
|
// Inserted adjacent to <pre> blocks, (See TextualBody)
|
||||||
.mx_EventTile_copyButton {
|
.mx_EventTile_button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
|
@ -520,12 +535,33 @@ $left-gutter: 64px;
|
||||||
right: 6px;
|
right: 6px;
|
||||||
width: 19px;
|
width: 19px;
|
||||||
height: 19px;
|
height: 19px;
|
||||||
mask-image: url($copy-button-url);
|
|
||||||
background-color: $message-action-bar-fg-color;
|
background-color: $message-action-bar-fg-color;
|
||||||
}
|
}
|
||||||
|
.mx_EventTile_buttonBottom {
|
||||||
|
top: 31px;
|
||||||
|
}
|
||||||
|
.mx_EventTile_copyButton {
|
||||||
|
mask-image: url($copy-button-url);
|
||||||
|
}
|
||||||
|
.mx_EventTile_collapseButton {
|
||||||
|
mask-size: 75%;
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-image: url($collapse-button-url);
|
||||||
|
}
|
||||||
|
.mx_EventTile_expandButton {
|
||||||
|
mask-size: 75%;
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-image: url($expand-button-url);
|
||||||
|
}
|
||||||
|
|
||||||
.mx_EventTile_body .mx_EventTile_pre_container:focus-within .mx_EventTile_copyButton,
|
.mx_EventTile_body .mx_EventTile_pre_container:focus-within .mx_EventTile_copyButton,
|
||||||
.mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_copyButton {
|
.mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_copyButton,
|
||||||
|
.mx_EventTile_body .mx_EventTile_pre_container:focus-within .mx_EventTile_collapseButton,
|
||||||
|
.mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_collapseButton,
|
||||||
|
.mx_EventTile_body .mx_EventTile_pre_container:focus-within .mx_EventTile_expandButton,
|
||||||
|
.mx_EventTile_body .mx_EventTile_pre_container:hover .mx_EventTile_expandButton {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_UserNotifSettings_notifTable {
|
.mx_UserNotifSettings_notifTable {
|
||||||
display: table;
|
display: table;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_UserNotifSettings_notifTable .mx_Spinner {
|
.mx_UserNotifSettings_notifTable .mx_Spinner {
|
||||||
|
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
@ -237,7 +237,8 @@ $event-redacted-border-color: #cccccc;
|
||||||
$event-timestamp-color: #acacac;
|
$event-timestamp-color: #acacac;
|
||||||
|
|
||||||
$copy-button-url: "$(res)/img/feather-customised/clipboard.svg";
|
$copy-button-url: "$(res)/img/feather-customised/clipboard.svg";
|
||||||
|
$collapse-button-url: "$(res)/img/feather-customised/minimise.svg";
|
||||||
|
$expand-button-url: "$(res)/img/feather-customised/maximise.svg";
|
||||||
|
|
||||||
// e2e
|
// e2e
|
||||||
$e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color
|
$e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color
|
||||||
|
|
|
@ -237,6 +237,8 @@ $event-redacted-border-color: #cccccc;
|
||||||
$event-timestamp-color: #acacac;
|
$event-timestamp-color: #acacac;
|
||||||
|
|
||||||
$copy-button-url: "$(res)/img/feather-customised/clipboard.svg";
|
$copy-button-url: "$(res)/img/feather-customised/clipboard.svg";
|
||||||
|
$collapse-button-url: "$(res)/img/feather-customised/minimise.svg";
|
||||||
|
$expand-button-url: "$(res)/img/feather-customised/maximise.svg";
|
||||||
|
|
||||||
// e2e
|
// e2e
|
||||||
$e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color
|
$e2e-verified-color: #76cfa5; // N.B. *NOT* the same as $accent-color
|
||||||
|
|
|
@ -82,6 +82,7 @@ import CountlyAnalytics from "./CountlyAnalytics";
|
||||||
import {UIFeature} from "./settings/UIFeature";
|
import {UIFeature} from "./settings/UIFeature";
|
||||||
import { CallError } from "matrix-js-sdk/src/webrtc/call";
|
import { CallError } from "matrix-js-sdk/src/webrtc/call";
|
||||||
import { logger } from 'matrix-js-sdk/src/logger';
|
import { logger } from 'matrix-js-sdk/src/logger';
|
||||||
|
import DesktopCapturerSourcePicker from "./components/views/elements/DesktopCapturerSourcePicker"
|
||||||
import { Action } from './dispatcher/actions';
|
import { Action } from './dispatcher/actions';
|
||||||
import { roomForVirtualRoom, getOrCreateVirtualRoomForRoom } from './VoipUserMapper';
|
import { roomForVirtualRoom, getOrCreateVirtualRoomForRoom } from './VoipUserMapper';
|
||||||
import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid';
|
import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid';
|
||||||
|
@ -572,9 +573,17 @@ export default class CallHandler {
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
call.placeScreenSharingCall(remoteElement, localElement);
|
|
||||||
|
call.placeScreenSharingCall(
|
||||||
|
remoteElement,
|
||||||
|
localElement,
|
||||||
|
async () : Promise<DesktopCapturerSource> => {
|
||||||
|
const {finished} = Modal.createDialog(DesktopCapturerSourcePicker);
|
||||||
|
const [source] = await finished;
|
||||||
|
return source;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error("Unknown conf call type: %s", type);
|
console.error("Unknown conf call type: " + type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -608,7 +617,7 @@ export default class CallHandler {
|
||||||
|
|
||||||
const room = MatrixClientPeg.get().getRoom(payload.room_id);
|
const room = MatrixClientPeg.get().getRoom(payload.room_id);
|
||||||
if (!room) {
|
if (!room) {
|
||||||
console.error("Room %s does not exist.", payload.room_id);
|
console.error(`Room ${payload.room_id} does not exist.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -619,7 +628,7 @@ export default class CallHandler {
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
} else if (members.length === 2) {
|
} else if (members.length === 2) {
|
||||||
console.info("Place %s call in %s", payload.type, payload.room_id);
|
console.info(`Place ${payload.type} call in ${payload.room_id}`);
|
||||||
|
|
||||||
this.placeCall(payload.room_id, payload.type, payload.local_element, payload.remote_element);
|
this.placeCall(payload.room_id, payload.type, payload.local_element, payload.remote_element);
|
||||||
} else { // > 2
|
} else { // > 2
|
||||||
|
@ -634,17 +643,17 @@ export default class CallHandler {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'place_conference_call':
|
case 'place_conference_call':
|
||||||
console.info("Place conference call in %s", payload.room_id);
|
console.info("Place conference call in " + payload.room_id);
|
||||||
Analytics.trackEvent('voip', 'placeConferenceCall');
|
Analytics.trackEvent('voip', 'placeConferenceCall');
|
||||||
CountlyAnalytics.instance.trackStartCall(payload.room_id, payload.type === PlaceCallType.Video, true);
|
CountlyAnalytics.instance.trackStartCall(payload.room_id, payload.type === PlaceCallType.Video, true);
|
||||||
this.startCallApp(payload.room_id, payload.type);
|
this.startCallApp(payload.room_id, payload.type);
|
||||||
break;
|
break;
|
||||||
case 'end_conference':
|
case 'end_conference':
|
||||||
console.info("Terminating conference call in %s", payload.room_id);
|
console.info("Terminating conference call in " + payload.room_id);
|
||||||
this.terminateCallApp(payload.room_id);
|
this.terminateCallApp(payload.room_id);
|
||||||
break;
|
break;
|
||||||
case 'hangup_conference':
|
case 'hangup_conference':
|
||||||
console.info("Leaving conference call in %s", payload.room_id);
|
console.info("Leaving conference call in "+ payload.room_id);
|
||||||
this.hangupCallApp(payload.room_id);
|
this.hangupCallApp(payload.room_id);
|
||||||
break;
|
break;
|
||||||
case 'incoming_call':
|
case 'incoming_call':
|
||||||
|
|
|
@ -202,12 +202,13 @@ function setRoomNotifsStateUnmuted(roomId, newState) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function findOverrideMuteRule(roomId) {
|
function findOverrideMuteRule(roomId) {
|
||||||
if (!MatrixClientPeg.get().pushRules ||
|
const cli = MatrixClientPeg.get();
|
||||||
!MatrixClientPeg.get().pushRules['global'] ||
|
if (!cli.pushRules ||
|
||||||
!MatrixClientPeg.get().pushRules['global'].override) {
|
!cli.pushRules['global'] ||
|
||||||
|
!cli.pushRules['global'].override) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
for (const rule of MatrixClientPeg.get().pushRules['global'].override) {
|
for (const rule of cli.pushRules['global'].override) {
|
||||||
if (isRuleForRoom(roomId, rule)) {
|
if (isRuleForRoom(roomId, rule)) {
|
||||||
if (isMuteRule(rule) && rule.enabled) {
|
if (isMuteRule(rule) && rule.enabled) {
|
||||||
return rule;
|
return rule;
|
||||||
|
|
|
@ -755,6 +755,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
break;
|
break;
|
||||||
case 'on_logged_in':
|
case 'on_logged_in':
|
||||||
if (
|
if (
|
||||||
|
// Skip this handling for token login as that always calls onLoggedIn itself
|
||||||
|
!this.tokenLogin &&
|
||||||
!Lifecycle.isSoftLogout() &&
|
!Lifecycle.isSoftLogout() &&
|
||||||
this.state.view !== Views.LOGIN &&
|
this.state.view !== Views.LOGIN &&
|
||||||
this.state.view !== Views.REGISTER &&
|
this.state.view !== Views.REGISTER &&
|
||||||
|
@ -1373,6 +1375,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
cli.on('Session.logged_out', function(errObj) {
|
cli.on('Session.logged_out', function(errObj) {
|
||||||
if (Lifecycle.isLoggingOut()) return;
|
if (Lifecycle.isLoggingOut()) return;
|
||||||
|
|
||||||
|
// A modal might have been open when we were logged out by the server
|
||||||
|
Modal.closeCurrentModal('Session.logged_out');
|
||||||
|
|
||||||
if (errObj.httpStatus === 401 && errObj.data && errObj.data['soft_logout']) {
|
if (errObj.httpStatus === 401 && errObj.data && errObj.data['soft_logout']) {
|
||||||
console.warn("Soft logout issued by server - avoiding data deletion");
|
console.warn("Soft logout issued by server - avoiding data deletion");
|
||||||
Lifecycle.softLogout();
|
Lifecycle.softLogout();
|
||||||
|
@ -1383,6 +1388,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
title: _t('Signed Out'),
|
title: _t('Signed Out'),
|
||||||
description: _t('For security, this session has been signed out. Please sign in again.'),
|
description: _t('For security, this session has been signed out. Please sign in again.'),
|
||||||
});
|
});
|
||||||
|
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'logout',
|
action: 'logout',
|
||||||
});
|
});
|
||||||
|
@ -1652,10 +1658,16 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
// TODO: Handle encoded room/event IDs: https://github.com/vector-im/element-web/issues/9149
|
// TODO: Handle encoded room/event IDs: https://github.com/vector-im/element-web/issues/9149
|
||||||
|
|
||||||
let threepidInvite: IThreepidInvite;
|
let threepidInvite: IThreepidInvite;
|
||||||
|
// if we landed here from a 3PID invite, persist it
|
||||||
if (params.signurl && params.email) {
|
if (params.signurl && params.email) {
|
||||||
threepidInvite = ThreepidInviteStore.instance
|
threepidInvite = ThreepidInviteStore.instance
|
||||||
.storeInvite(roomString, params as IThreepidInviteWireFormat);
|
.storeInvite(roomString, params as IThreepidInviteWireFormat);
|
||||||
}
|
}
|
||||||
|
// otherwise check that this room doesn't already have a known invite
|
||||||
|
if (!threepidInvite) {
|
||||||
|
const invites = ThreepidInviteStore.instance.getInvites();
|
||||||
|
threepidInvite = invites.find(invite => invite.roomId === roomString);
|
||||||
|
}
|
||||||
|
|
||||||
// on our URLs there might be a ?via=matrix.org or similar to help
|
// on our URLs there might be a ?via=matrix.org or similar to help
|
||||||
// joins to the room succeed. We'll pass these through as an array
|
// joins to the room succeed. We'll pass these through as an array
|
||||||
|
|
|
@ -229,7 +229,7 @@ export default class MessagePanel extends React.Component {
|
||||||
|
|
||||||
onAction = (payload) => {
|
onAction = (payload) => {
|
||||||
switch (payload.action) {
|
switch (payload.action) {
|
||||||
case "message_sent":
|
case "scroll_to_bottom":
|
||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -477,7 +477,7 @@ export default class RoomDirectory extends React.Component {
|
||||||
dis.dispatch(payload);
|
dis.dispatch(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
getRow(room) {
|
createRoomCells(room) {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const clientRoom = client.getRoom(room.room_id);
|
const clientRoom = client.getRoom(room.room_id);
|
||||||
const hasJoinedRoom = clientRoom && clientRoom.getMyMembership() === "join";
|
const hasJoinedRoom = clientRoom && clientRoom.getMyMembership() === "join";
|
||||||
|
@ -523,31 +523,56 @@ export default class RoomDirectory extends React.Component {
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
MatrixClientPeg.get().getHomeserverUrl(),
|
||||||
room.avatar_url, 32, 32, "crop",
|
room.avatar_url, 32, 32, "crop",
|
||||||
);
|
);
|
||||||
return (
|
return [
|
||||||
<tr key={ room.room_id }
|
<div key={ `${room.room_id}_avatar` }
|
||||||
onClick={(ev) => this.onRoomClicked(room, ev)}
|
onClick={(ev) => this.onRoomClicked(room, ev)}
|
||||||
// cancel onMouseDown otherwise shift-clicking highlights text
|
// cancel onMouseDown otherwise shift-clicking highlights text
|
||||||
onMouseDown={(ev) => {ev.preventDefault();}}
|
onMouseDown={(ev) => {ev.preventDefault();}}
|
||||||
|
className="mx_RoomDirectory_roomAvatar"
|
||||||
>
|
>
|
||||||
<td className="mx_RoomDirectory_roomAvatar">
|
<BaseAvatar width={32} height={32} resizeMethod='crop'
|
||||||
<BaseAvatar width={32} height={32} resizeMethod='crop'
|
name={ name } idName={ name }
|
||||||
name={ name } idName={ name }
|
url={ avatarUrl }
|
||||||
url={ avatarUrl } />
|
/>
|
||||||
</td>
|
</div>,
|
||||||
<td className="mx_RoomDirectory_roomDescription">
|
<div key={ `${room.room_id}_description` }
|
||||||
<div className="mx_RoomDirectory_name">{ name }</div>
|
onClick={(ev) => this.onRoomClicked(room, ev)}
|
||||||
<div className="mx_RoomDirectory_topic"
|
// cancel onMouseDown otherwise shift-clicking highlights text
|
||||||
onClick={ (ev) => { ev.stopPropagation(); } }
|
onMouseDown={(ev) => {ev.preventDefault();}}
|
||||||
dangerouslySetInnerHTML={{ __html: topic }} />
|
className="mx_RoomDirectory_roomDescription"
|
||||||
<div className="mx_RoomDirectory_alias">{ get_display_alias_for_room(room) }</div>
|
>
|
||||||
</td>
|
<div className="mx_RoomDirectory_name">{ name }</div>
|
||||||
<td className="mx_RoomDirectory_roomMemberCount">
|
<div className="mx_RoomDirectory_topic"
|
||||||
{ room.num_joined_members }
|
onClick={ (ev) => { ev.stopPropagation(); } }
|
||||||
</td>
|
dangerouslySetInnerHTML={{ __html: topic }}
|
||||||
<td className="mx_RoomDirectory_preview">{previewButton}</td>
|
/>
|
||||||
<td className="mx_RoomDirectory_join">{joinOrViewButton}</td>
|
<div className="mx_RoomDirectory_alias">{ get_display_alias_for_room(room) }</div>
|
||||||
</tr>
|
</div>,
|
||||||
);
|
<div key={ `${room.room_id}_memberCount` }
|
||||||
|
onClick={(ev) => this.onRoomClicked(room, ev)}
|
||||||
|
// cancel onMouseDown otherwise shift-clicking highlights text
|
||||||
|
onMouseDown={(ev) => {ev.preventDefault();}}
|
||||||
|
className="mx_RoomDirectory_roomMemberCount"
|
||||||
|
>
|
||||||
|
{ room.num_joined_members }
|
||||||
|
</div>,
|
||||||
|
<div key={ `${room.room_id}_preview` }
|
||||||
|
onClick={(ev) => this.onRoomClicked(room, ev)}
|
||||||
|
// cancel onMouseDown otherwise shift-clicking highlights text
|
||||||
|
onMouseDown={(ev) => {ev.preventDefault();}}
|
||||||
|
className="mx_RoomDirectory_preview"
|
||||||
|
>
|
||||||
|
{previewButton}
|
||||||
|
</div>,
|
||||||
|
<div key={ `${room.room_id}_join` }
|
||||||
|
onClick={(ev) => this.onRoomClicked(room, ev)}
|
||||||
|
// cancel onMouseDown otherwise shift-clicking highlights text
|
||||||
|
onMouseDown={(ev) => {ev.preventDefault();}}
|
||||||
|
className="mx_RoomDirectory_join"
|
||||||
|
>
|
||||||
|
{joinOrViewButton}
|
||||||
|
</div>,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
collectScrollPanel = (element) => {
|
collectScrollPanel = (element) => {
|
||||||
|
@ -606,7 +631,8 @@ export default class RoomDirectory extends React.Component {
|
||||||
} else if (this.state.protocolsLoading) {
|
} else if (this.state.protocolsLoading) {
|
||||||
content = <Loader />;
|
content = <Loader />;
|
||||||
} else {
|
} else {
|
||||||
const rows = (this.state.publicRooms || []).map(room => this.getRow(room));
|
const cells = (this.state.publicRooms || [])
|
||||||
|
.reduce((cells, room) => cells.concat(this.createRoomCells(room)), [],);
|
||||||
// we still show the scrollpanel, at least for now, because
|
// we still show the scrollpanel, at least for now, because
|
||||||
// otherwise we don't fetch more because we don't get a fill
|
// otherwise we don't fetch more because we don't get a fill
|
||||||
// request from the scrollpanel because there isn't one
|
// request from the scrollpanel because there isn't one
|
||||||
|
@ -617,14 +643,12 @@ export default class RoomDirectory extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
let scrollpanel_content;
|
let scrollpanel_content;
|
||||||
if (rows.length === 0 && !this.state.loading) {
|
if (cells.length === 0 && !this.state.loading) {
|
||||||
scrollpanel_content = <i>{ _t('No rooms to show') }</i>;
|
scrollpanel_content = <i>{ _t('No rooms to show') }</i>;
|
||||||
} else {
|
} else {
|
||||||
scrollpanel_content = <table className="mx_RoomDirectory_table">
|
scrollpanel_content = <div className="mx_RoomDirectory_table">
|
||||||
<tbody>
|
{ cells }
|
||||||
{ rows }
|
</div>;
|
||||||
</tbody>
|
|
||||||
</table>;
|
|
||||||
}
|
}
|
||||||
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
|
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
|
||||||
content = <ScrollPanel ref={this.collectScrollPanel}
|
content = <ScrollPanel ref={this.collectScrollPanel}
|
||||||
|
|
166
src/components/views/elements/DesktopCapturerSourcePicker.tsx
Normal file
166
src/components/views/elements/DesktopCapturerSourcePicker.tsx
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import BaseDialog from "..//dialogs/BaseDialog"
|
||||||
|
import AccessibleButton from './AccessibleButton';
|
||||||
|
import {getDesktopCapturerSources} from "matrix-js-sdk/src/webrtc/call";
|
||||||
|
|
||||||
|
export interface DesktopCapturerSource {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
thumbnailURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Tabs {
|
||||||
|
Screens = "screens",
|
||||||
|
Windows = "windows",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DesktopCapturerSourceIProps {
|
||||||
|
source: DesktopCapturerSource;
|
||||||
|
onSelect(source: DesktopCapturerSource): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExistingSource extends React.Component<DesktopCapturerSourceIProps> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick = (ev) => {
|
||||||
|
this.props.onSelect(this.props.source);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<AccessibleButton
|
||||||
|
className="mx_desktopCapturerSourcePicker_stream_button"
|
||||||
|
title={this.props.source.name}
|
||||||
|
onClick={this.onClick} >
|
||||||
|
<img
|
||||||
|
className="mx_desktopCapturerSourcePicker_stream_thumbnail"
|
||||||
|
src={this.props.source.thumbnailURL}
|
||||||
|
/>
|
||||||
|
<span className="mx_desktopCapturerSourcePicker_stream_name">{this.props.source.name}</span>
|
||||||
|
</AccessibleButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DesktopCapturerSourcePickerIState {
|
||||||
|
selectedTab: Tabs;
|
||||||
|
sources: Array<DesktopCapturerSource>;
|
||||||
|
}
|
||||||
|
export interface DesktopCapturerSourcePickerIProps {
|
||||||
|
onFinished(source: DesktopCapturerSource): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class DesktopCapturerSourcePicker extends React.Component<
|
||||||
|
DesktopCapturerSourcePickerIProps,
|
||||||
|
DesktopCapturerSourcePickerIState
|
||||||
|
> {
|
||||||
|
interval;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
selectedTab: Tabs.Screens,
|
||||||
|
sources: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
// We update the sources every 500ms to get newer thumbnails
|
||||||
|
this.interval = setInterval(async () => {
|
||||||
|
this.setState({
|
||||||
|
sources: await getDesktopCapturerSources(),
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
clearInterval(this.interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelect = (source) => {
|
||||||
|
this.props.onFinished(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
onScreensClick = (ev) => {
|
||||||
|
this.setState({selectedTab: Tabs.Screens});
|
||||||
|
}
|
||||||
|
|
||||||
|
onWindowsClick = (ev) => {
|
||||||
|
this.setState({selectedTab: Tabs.Windows});
|
||||||
|
}
|
||||||
|
|
||||||
|
onCloseClick = (ev) => {
|
||||||
|
this.props.onFinished(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let sources;
|
||||||
|
if (this.state.selectedTab === Tabs.Screens) {
|
||||||
|
sources = this.state.sources
|
||||||
|
.filter((source) => {
|
||||||
|
return source.id.startsWith("screen");
|
||||||
|
})
|
||||||
|
.map((source) => {
|
||||||
|
return <ExistingSource source={source} onSelect={this.onSelect} key={source.id} />;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
sources = this.state.sources
|
||||||
|
.filter((source) => {
|
||||||
|
return source.id.startsWith("window");
|
||||||
|
})
|
||||||
|
.map((source) => {
|
||||||
|
return <ExistingSource source={source} onSelect={this.onSelect} key={source.id} />;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttonStyle = "mx_desktopCapturerSourcePicker_tabLabel";
|
||||||
|
const screensButtonStyle = buttonStyle + ((this.state.selectedTab === Tabs.Screens) ? "_selected" : "");
|
||||||
|
const windowsButtonStyle = buttonStyle + ((this.state.selectedTab === Tabs.Windows) ? "_selected" : "");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseDialog
|
||||||
|
className="mx_desktopCapturerSourcePicker"
|
||||||
|
onFinished={this.onCloseClick}
|
||||||
|
title={_t("Share your screen")}
|
||||||
|
>
|
||||||
|
<div className="mx_desktopCapturerSourcePicker_tabLabels">
|
||||||
|
<AccessibleButton
|
||||||
|
className={screensButtonStyle}
|
||||||
|
onClick={this.onScreensClick}
|
||||||
|
>
|
||||||
|
{_t("Screens")}
|
||||||
|
</AccessibleButton>
|
||||||
|
<AccessibleButton
|
||||||
|
className={windowsButtonStyle}
|
||||||
|
onClick={this.onWindowsClick}
|
||||||
|
>
|
||||||
|
{_t("Windows")}
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
<div className="mx_desktopCapturerSourcePicker_panel">
|
||||||
|
{ sources }
|
||||||
|
</div>
|
||||||
|
</BaseDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -81,6 +81,7 @@ export default class TextualBody extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_applyFormatting() {
|
_applyFormatting() {
|
||||||
|
const showLineNumbers = SettingsStore.getValue("showCodeLineNumbers");
|
||||||
this.activateSpoilers([this._content.current]);
|
this.activateSpoilers([this._content.current]);
|
||||||
|
|
||||||
// pillifyLinks BEFORE linkifyElement because plain room/user URLs in the composer
|
// pillifyLinks BEFORE linkifyElement because plain room/user URLs in the composer
|
||||||
|
@ -91,29 +92,136 @@ export default class TextualBody extends React.Component {
|
||||||
this.calculateUrlPreview();
|
this.calculateUrlPreview();
|
||||||
|
|
||||||
if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") {
|
if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") {
|
||||||
const blocks = ReactDOM.findDOMNode(this).getElementsByTagName("code");
|
// Handle expansion and add buttons
|
||||||
if (blocks.length > 0) {
|
const pres = ReactDOM.findDOMNode(this).getElementsByTagName("pre");
|
||||||
// Do this asynchronously: parsing code takes time and we don't
|
if (pres.length > 0) {
|
||||||
// need to block the DOM update on it.
|
for (let i = 0; i < pres.length; i++) {
|
||||||
setTimeout(() => {
|
// Wrap a div around <pre> so that the copy button can be correctly positioned
|
||||||
if (this._unmounted) return;
|
// when the <pre> overflows and is scrolled horizontally.
|
||||||
for (let i = 0; i < blocks.length; i++) {
|
const div = this._wrapInDiv(pres[i]);
|
||||||
if (SettingsStore.getValue("enableSyntaxHighlightLanguageDetection")) {
|
this._handleCodeBlockExpansion(pres[i]);
|
||||||
highlight.highlightBlock(blocks[i]);
|
this._addCodeExpansionButton(div, pres[i]);
|
||||||
} else {
|
this._addCodeCopyButton(div);
|
||||||
// Only syntax highlight if there's a class starting with language-
|
if (showLineNumbers) {
|
||||||
const classes = blocks[i].className.split(/\s+/).filter(function(cl) {
|
this._addLineNumbers(pres[i]);
|
||||||
return cl.startsWith('language-') && !cl.startsWith('language-_');
|
|
||||||
});
|
|
||||||
|
|
||||||
if (classes.length != 0) {
|
|
||||||
highlight.highlightBlock(blocks[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, 10);
|
}
|
||||||
|
}
|
||||||
|
// Highlight code
|
||||||
|
const codes = ReactDOM.findDOMNode(this).getElementsByTagName("code");
|
||||||
|
if (codes.length > 0) {
|
||||||
|
for (let i = 0; i < codes.length; i++) {
|
||||||
|
// Do this asynchronously: parsing code takes time and we don't
|
||||||
|
// need to block the DOM update on it.
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this._unmounted) return;
|
||||||
|
for (let i = 0; i < pres.length; i++) {
|
||||||
|
this._highlightCode(codes[i]);
|
||||||
|
}
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_addCodeExpansionButton(div, pre) {
|
||||||
|
// Calculate how many percent does the pre element take up.
|
||||||
|
// If it's less than 30% we don't add the expansion button.
|
||||||
|
const percentageOfViewport = pre.offsetHeight / window.innerHeight * 100;
|
||||||
|
if (percentageOfViewport < 30) return;
|
||||||
|
|
||||||
|
const button = document.createElement("span");
|
||||||
|
button.className = "mx_EventTile_button ";
|
||||||
|
if (pre.className == "mx_EventTile_collapsedCodeBlock") {
|
||||||
|
button.className += "mx_EventTile_expandButton";
|
||||||
|
} else {
|
||||||
|
button.className += "mx_EventTile_collapseButton";
|
||||||
|
}
|
||||||
|
|
||||||
|
button.onclick = async () => {
|
||||||
|
button.className = "mx_EventTile_button ";
|
||||||
|
if (pre.className == "mx_EventTile_collapsedCodeBlock") {
|
||||||
|
pre.className = "";
|
||||||
|
button.className += "mx_EventTile_collapseButton";
|
||||||
|
} else {
|
||||||
|
pre.className = "mx_EventTile_collapsedCodeBlock";
|
||||||
|
button.className += "mx_EventTile_expandButton";
|
||||||
|
}
|
||||||
|
|
||||||
|
// By expanding/collapsing we changed
|
||||||
|
// the height, therefore we call this
|
||||||
|
this.props.onHeightChanged();
|
||||||
|
};
|
||||||
|
|
||||||
|
div.appendChild(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
_addCodeCopyButton(div) {
|
||||||
|
const button = document.createElement("span");
|
||||||
|
button.className = "mx_EventTile_button mx_EventTile_copyButton ";
|
||||||
|
|
||||||
|
// Check if expansion button exists. If so
|
||||||
|
// we put the copy button to the bottom
|
||||||
|
const expansionButtonExists = div.getElementsByClassName("mx_EventTile_button");
|
||||||
|
if (expansionButtonExists.length > 0) button.className += "mx_EventTile_buttonBottom";
|
||||||
|
|
||||||
|
button.onclick = async () => {
|
||||||
|
const copyCode = button.parentNode.getElementsByTagName("code")[0];
|
||||||
|
const successful = await copyPlaintext(copyCode.textContent);
|
||||||
|
|
||||||
|
const buttonRect = button.getBoundingClientRect();
|
||||||
|
const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
|
||||||
|
const {close} = ContextMenu.createMenu(GenericTextContextMenu, {
|
||||||
|
...toRightOf(buttonRect, 2),
|
||||||
|
message: successful ? _t('Copied!') : _t('Failed to copy'),
|
||||||
|
});
|
||||||
|
button.onmouseleave = close;
|
||||||
|
};
|
||||||
|
|
||||||
|
div.appendChild(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
_wrapInDiv(pre) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.className = "mx_EventTile_pre_container";
|
||||||
|
|
||||||
|
// Insert containing div in place of <pre> block
|
||||||
|
pre.parentNode.replaceChild(div, pre);
|
||||||
|
// Append <pre> block and copy button to container
|
||||||
|
div.appendChild(pre);
|
||||||
|
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleCodeBlockExpansion(pre) {
|
||||||
|
if (!SettingsStore.getValue("expandCodeByDefault")) {
|
||||||
|
pre.className = "mx_EventTile_collapsedCodeBlock";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_addLineNumbers(pre) {
|
||||||
|
pre.innerHTML = '<span class="mx_EventTile_lineNumbers"></span>' + pre.innerHTML + '<span></span>';
|
||||||
|
const lineNumbers = pre.getElementsByClassName("mx_EventTile_lineNumbers")[0];
|
||||||
|
// Calculate number of lines in pre
|
||||||
|
const number = pre.innerHTML.split(/\n/).length;
|
||||||
|
// Iterate through lines starting with 1 (number of the first line is 1)
|
||||||
|
for (let i = 1; i < number; i++) {
|
||||||
|
lineNumbers.innerHTML += '<span class="mx_EventTile_lineNumber">' + i + '</span>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_highlightCode(code) {
|
||||||
|
if (SettingsStore.getValue("enableSyntaxHighlightLanguageDetection")) {
|
||||||
|
highlight.highlightBlock(code);
|
||||||
|
} else {
|
||||||
|
// Only syntax highlight if there's a class starting with language-
|
||||||
|
const classes = code.className.split(/\s+/).filter(function(cl) {
|
||||||
|
return cl.startsWith('language-') && !cl.startsWith('language-_');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (classes.length != 0) {
|
||||||
|
highlight.highlightBlock(code);
|
||||||
}
|
}
|
||||||
this._addCodeCopyButton();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,38 +362,6 @@ export default class TextualBody extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_addCodeCopyButton() {
|
|
||||||
// Add 'copy' buttons to pre blocks
|
|
||||||
Array.from(ReactDOM.findDOMNode(this).querySelectorAll('.mx_EventTile_body pre')).forEach((p) => {
|
|
||||||
const button = document.createElement("span");
|
|
||||||
button.className = "mx_EventTile_copyButton";
|
|
||||||
button.onclick = async () => {
|
|
||||||
const copyCode = button.parentNode.getElementsByTagName("pre")[0];
|
|
||||||
const successful = await copyPlaintext(copyCode.textContent);
|
|
||||||
|
|
||||||
const buttonRect = button.getBoundingClientRect();
|
|
||||||
const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
|
|
||||||
const {close} = ContextMenu.createMenu(GenericTextContextMenu, {
|
|
||||||
...toRightOf(buttonRect, 2),
|
|
||||||
message: successful ? _t('Copied!') : _t('Failed to copy'),
|
|
||||||
});
|
|
||||||
button.onmouseleave = close;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Wrap a div around <pre> so that the copy button can be correctly positioned
|
|
||||||
// when the <pre> overflows and is scrolled horizontally.
|
|
||||||
const div = document.createElement("div");
|
|
||||||
div.className = "mx_EventTile_pre_container";
|
|
||||||
|
|
||||||
// Insert containing div in place of <pre> block
|
|
||||||
p.parentNode.replaceChild(div, p);
|
|
||||||
|
|
||||||
// Append <pre> block and copy button to container
|
|
||||||
div.appendChild(p);
|
|
||||||
div.appendChild(button);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onCancelClick = event => {
|
onCancelClick = event => {
|
||||||
this.setState({ widgetHidden: true });
|
this.setState({ widgetHidden: true });
|
||||||
// FIXME: persist this somewhere smarter than local storage
|
// FIXME: persist this somewhere smarter than local storage
|
||||||
|
|
|
@ -69,19 +69,24 @@ export default class RoomProfileSettings extends React.Component {
|
||||||
// clear file upload field so same file can be selected
|
// clear file upload field so same file can be selected
|
||||||
this._avatarUpload.current.value = "";
|
this._avatarUpload.current.value = "";
|
||||||
this.setState({
|
this.setState({
|
||||||
avatarUrl: undefined,
|
avatarUrl: null,
|
||||||
avatarFile: undefined,
|
avatarFile: null,
|
||||||
enableProfileSave: true,
|
enableProfileSave: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_clearProfile = async (e) => {
|
_cancelProfileChanges = async (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!this.state.enableProfileSave) return;
|
if (!this.state.enableProfileSave) return;
|
||||||
this._removeAvatar();
|
this.setState({
|
||||||
this.setState({enableProfileSave: false, displayName: this.state.originalDisplayName});
|
enableProfileSave: false,
|
||||||
|
displayName: this.state.originalDisplayName,
|
||||||
|
topic: this.state.originalTopic,
|
||||||
|
avatarUrl: this.state.originalAvatarUrl,
|
||||||
|
avatarFile: null,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_saveProfile = async (e) => {
|
_saveProfile = async (e) => {
|
||||||
|
@ -108,7 +113,7 @@ export default class RoomProfileSettings extends React.Component {
|
||||||
newState.originalAvatarUrl = newState.avatarUrl;
|
newState.originalAvatarUrl = newState.avatarUrl;
|
||||||
newState.avatarFile = null;
|
newState.avatarFile = null;
|
||||||
} else if (this.state.originalAvatarUrl !== this.state.avatarUrl) {
|
} else if (this.state.originalAvatarUrl !== this.state.avatarUrl) {
|
||||||
await client.sendStateEvent(this.props.roomId, 'm.room.avatar', {url: undefined}, '');
|
await client.sendStateEvent(this.props.roomId, 'm.room.avatar', {}, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.originalTopic !== this.state.topic) {
|
if (this.state.originalTopic !== this.state.topic) {
|
||||||
|
@ -164,11 +169,15 @@ export default class RoomProfileSettings extends React.Component {
|
||||||
const AvatarSetting = sdk.getComponent('settings.AvatarSetting');
|
const AvatarSetting = sdk.getComponent('settings.AvatarSetting');
|
||||||
|
|
||||||
let profileSettingsButtons;
|
let profileSettingsButtons;
|
||||||
if (this.state.canSetTopic && this.state.canSetName) {
|
if (
|
||||||
|
this.state.canSetName ||
|
||||||
|
this.state.canSetTopic ||
|
||||||
|
this.state.canSetAvatar
|
||||||
|
) {
|
||||||
profileSettingsButtons = (
|
profileSettingsButtons = (
|
||||||
<div className="mx_ProfileSettings_buttons">
|
<div className="mx_ProfileSettings_buttons">
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
onClick={this._clearProfile}
|
onClick={this._cancelProfileChanges}
|
||||||
kind="link"
|
kind="link"
|
||||||
disabled={!this.state.enableProfileSave}
|
disabled={!this.state.enableProfileSave}
|
||||||
>
|
>
|
||||||
|
|
|
@ -426,7 +426,8 @@ export default class MessageComposer extends React.Component {
|
||||||
<EmojiButton key="emoji_button" addEmoji={this.addEmoji} />,
|
<EmojiButton key="emoji_button" addEmoji={this.addEmoji} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (SettingsStore.getValue(UIFeature.Widgets)) {
|
if (SettingsStore.getValue(UIFeature.Widgets) &&
|
||||||
|
SettingsStore.getValue("MessageComposerInput.showStickersButton")) {
|
||||||
controls.push(<Stickerpicker key="stickerpicker_controls_button" room={this.props.room} />);
|
controls.push(<Stickerpicker key="stickerpicker_controls_button" room={this.props.room} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -111,7 +111,7 @@ export default class RoomBreadcrumbs extends React.PureComponent<IProps, IState>
|
||||||
appear={true} in={this.state.doAnimation} timeout={640}
|
appear={true} in={this.state.doAnimation} timeout={640}
|
||||||
classNames='mx_RoomBreadcrumbs'
|
classNames='mx_RoomBreadcrumbs'
|
||||||
>
|
>
|
||||||
<Toolbar className='mx_RoomBreadcrumbs'>
|
<Toolbar className='mx_RoomBreadcrumbs' aria-label={_t("Recently visited rooms")}>
|
||||||
{tiles.slice(this.state.skipFirst ? 1 : 0)}
|
{tiles.slice(this.state.skipFirst ? 1 : 0)}
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</CSSTransition>
|
</CSSTransition>
|
||||||
|
|
|
@ -403,6 +403,7 @@ export default class SendMessageComposer extends React.Component {
|
||||||
this._editorRef.clearUndoHistory();
|
this._editorRef.clearUndoHistory();
|
||||||
this._editorRef.focus();
|
this._editorRef.focus();
|
||||||
this._clearStoredEditorState();
|
this._clearStoredEditorState();
|
||||||
|
dis.dispatch({action: "scroll_to_bottom"});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
|
|
@ -52,19 +52,23 @@ export default class ProfileSettings extends React.Component {
|
||||||
// clear file upload field so same file can be selected
|
// clear file upload field so same file can be selected
|
||||||
this._avatarUpload.current.value = "";
|
this._avatarUpload.current.value = "";
|
||||||
this.setState({
|
this.setState({
|
||||||
avatarUrl: undefined,
|
avatarUrl: null,
|
||||||
avatarFile: undefined,
|
avatarFile: null,
|
||||||
enableProfileSave: true,
|
enableProfileSave: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_clearProfile = async (e) => {
|
_cancelProfileChanges = async (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!this.state.enableProfileSave) return;
|
if (!this.state.enableProfileSave) return;
|
||||||
this._removeAvatar();
|
this.setState({
|
||||||
this.setState({enableProfileSave: false, displayName: this.state.originalDisplayName});
|
enableProfileSave: false,
|
||||||
|
displayName: this.state.originalDisplayName,
|
||||||
|
avatarUrl: this.state.originalAvatarUrl,
|
||||||
|
avatarFile: null,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_saveProfile = async (e) => {
|
_saveProfile = async (e) => {
|
||||||
|
@ -186,7 +190,7 @@ export default class ProfileSettings extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_ProfileSettings_buttons">
|
<div className="mx_ProfileSettings_buttons">
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
onClick={this._clearProfile}
|
onClick={this._cancelProfileChanges}
|
||||||
kind="link"
|
kind="link"
|
||||||
disabled={!this.state.enableProfileSave}
|
disabled={!this.state.enableProfileSave}
|
||||||
>
|
>
|
||||||
|
|
|
@ -34,6 +34,7 @@ export default class PreferencesUserSettingsTab extends React.Component {
|
||||||
'MessageComposerInput.suggestEmoji',
|
'MessageComposerInput.suggestEmoji',
|
||||||
'sendTypingNotifications',
|
'sendTypingNotifications',
|
||||||
'MessageComposerInput.ctrlEnterToSend',
|
'MessageComposerInput.ctrlEnterToSend',
|
||||||
|
'MessageComposerInput.showStickersButton',
|
||||||
];
|
];
|
||||||
|
|
||||||
static TIMELINE_SETTINGS = [
|
static TIMELINE_SETTINGS = [
|
||||||
|
@ -46,6 +47,8 @@ export default class PreferencesUserSettingsTab extends React.Component {
|
||||||
'alwaysShowTimestamps',
|
'alwaysShowTimestamps',
|
||||||
'showRedactions',
|
'showRedactions',
|
||||||
'enableSyntaxHighlightLanguageDetection',
|
'enableSyntaxHighlightLanguageDetection',
|
||||||
|
'expandCodeByDefault',
|
||||||
|
'showCodeLineNumbers',
|
||||||
'showJoinLeaves',
|
'showJoinLeaves',
|
||||||
'showAvatarChanges',
|
'showAvatarChanges',
|
||||||
'showDisplaynameChanges',
|
'showDisplaynameChanges',
|
||||||
|
|
|
@ -795,6 +795,7 @@
|
||||||
"Font size": "Font size",
|
"Font size": "Font size",
|
||||||
"Use custom size": "Use custom size",
|
"Use custom size": "Use custom size",
|
||||||
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
|
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
|
||||||
|
"Show stickers button": "Show stickers button",
|
||||||
"Use a more compact ‘Modern’ layout": "Use a more compact ‘Modern’ layout",
|
"Use a more compact ‘Modern’ layout": "Use a more compact ‘Modern’ layout",
|
||||||
"Show a placeholder for removed messages": "Show a placeholder for removed messages",
|
"Show a placeholder for removed messages": "Show a placeholder for removed messages",
|
||||||
"Show join/leave messages (invites/kicks/bans unaffected)": "Show join/leave messages (invites/kicks/bans unaffected)",
|
"Show join/leave messages (invites/kicks/bans unaffected)": "Show join/leave messages (invites/kicks/bans unaffected)",
|
||||||
|
@ -805,6 +806,8 @@
|
||||||
"Always show message timestamps": "Always show message timestamps",
|
"Always show message timestamps": "Always show message timestamps",
|
||||||
"Autoplay GIFs and videos": "Autoplay GIFs and videos",
|
"Autoplay GIFs and videos": "Autoplay GIFs and videos",
|
||||||
"Enable automatic language detection for syntax highlighting": "Enable automatic language detection for syntax highlighting",
|
"Enable automatic language detection for syntax highlighting": "Enable automatic language detection for syntax highlighting",
|
||||||
|
"Expand code blocks by default": "Expand code blocks by default",
|
||||||
|
"Show line numbers in code blocks": "Show line numbers in code blocks",
|
||||||
"Show avatars in user and room mentions": "Show avatars in user and room mentions",
|
"Show avatars in user and room mentions": "Show avatars in user and room mentions",
|
||||||
"Enable big emoji in chat": "Enable big emoji in chat",
|
"Enable big emoji in chat": "Enable big emoji in chat",
|
||||||
"Send typing notifications": "Send typing notifications",
|
"Send typing notifications": "Send typing notifications",
|
||||||
|
@ -1462,6 +1465,7 @@
|
||||||
"Seen by %(displayName)s (%(userName)s) at %(dateTime)s": "Seen by %(displayName)s (%(userName)s) at %(dateTime)s",
|
"Seen by %(displayName)s (%(userName)s) at %(dateTime)s": "Seen by %(displayName)s (%(userName)s) at %(dateTime)s",
|
||||||
"Replying": "Replying",
|
"Replying": "Replying",
|
||||||
"Room %(name)s": "Room %(name)s",
|
"Room %(name)s": "Room %(name)s",
|
||||||
|
"Recently visited rooms": "Recently visited rooms",
|
||||||
"No recently visited rooms": "No recently visited rooms",
|
"No recently visited rooms": "No recently visited rooms",
|
||||||
"No rooms to show": "No rooms to show",
|
"No rooms to show": "No rooms to show",
|
||||||
"Unnamed room": "Unnamed room",
|
"Unnamed room": "Unnamed room",
|
||||||
|
@ -1851,6 +1855,9 @@
|
||||||
"Use the <a>Desktop app</a> to search encrypted messages": "Use the <a>Desktop app</a> to search encrypted messages",
|
"Use the <a>Desktop app</a> to search encrypted messages": "Use the <a>Desktop app</a> to search encrypted messages",
|
||||||
"This version of %(brand)s does not support viewing some encrypted files": "This version of %(brand)s does not support viewing some encrypted files",
|
"This version of %(brand)s does not support viewing some encrypted files": "This version of %(brand)s does not support viewing some encrypted files",
|
||||||
"This version of %(brand)s does not support searching encrypted messages": "This version of %(brand)s does not support searching encrypted messages",
|
"This version of %(brand)s does not support searching encrypted messages": "This version of %(brand)s does not support searching encrypted messages",
|
||||||
|
"Share your screen": "Share your screen",
|
||||||
|
"Screens": "Screens",
|
||||||
|
"Windows": "Windows",
|
||||||
"Join": "Join",
|
"Join": "Join",
|
||||||
"No results": "No results",
|
"No results": "No results",
|
||||||
"Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.": "Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.",
|
"Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.": "Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.",
|
||||||
|
|
|
@ -240,6 +240,11 @@ export const SETTINGS: {[setting: string]: ISetting} = {
|
||||||
default: true,
|
default: true,
|
||||||
invertedSettingName: 'MessageComposerInput.dontSuggestEmoji',
|
invertedSettingName: 'MessageComposerInput.dontSuggestEmoji',
|
||||||
},
|
},
|
||||||
|
"MessageComposerInput.showStickersButton": {
|
||||||
|
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||||
|
displayName: _td('Show stickers button'),
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
// TODO: Wire up appropriately to UI (FTUE notifications)
|
// TODO: Wire up appropriately to UI (FTUE notifications)
|
||||||
"Notifications.alwaysShowBadgeCounts": {
|
"Notifications.alwaysShowBadgeCounts": {
|
||||||
supportedLevels: LEVELS_ROOM_OR_ACCOUNT,
|
supportedLevels: LEVELS_ROOM_OR_ACCOUNT,
|
||||||
|
@ -300,6 +305,16 @@ export const SETTINGS: {[setting: string]: ISetting} = {
|
||||||
displayName: _td('Enable automatic language detection for syntax highlighting'),
|
displayName: _td('Enable automatic language detection for syntax highlighting'),
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
"expandCodeByDefault": {
|
||||||
|
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||||
|
displayName: _td('Expand code blocks by default'),
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
"showCodeLineNumbers": {
|
||||||
|
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||||
|
displayName: _td('Show line numbers in code blocks'),
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
"Pill.shouldShowPillAvatar": {
|
"Pill.shouldShowPillAvatar": {
|
||||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||||
displayName: _td('Show avatars in user and room mentions'),
|
displayName: _td('Show avatars in user and room mentions'),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue