Merge branch 'travis/moar-jitsi' into travis/addwidget-improvements
This commit is contained in:
commit
77bdde4021
33 changed files with 375 additions and 223 deletions
|
@ -90,7 +90,6 @@
|
|||
"qrcode-react": "^0.1.16",
|
||||
"qs": "^6.6.0",
|
||||
"react": "^16.9.0",
|
||||
"react-addons-css-transition-group": "15.6.2",
|
||||
"react-beautiful-dnd": "^4.0.1",
|
||||
"react-dom": "^16.9.0",
|
||||
"react-focus-lock": "^2.2.1",
|
||||
|
|
|
@ -94,6 +94,7 @@
|
|||
@import "./views/elements/_AccessibleButton.scss";
|
||||
@import "./views/elements/_AddressSelector.scss";
|
||||
@import "./views/elements/_AddressTile.scss";
|
||||
@import "./views/elements/_ButtonPlaceholder.scss";
|
||||
@import "./views/elements/_DirectorySearchBox.scss";
|
||||
@import "./views/elements/_Dropdown.scss";
|
||||
@import "./views/elements/_EditableItemList.scss";
|
||||
|
@ -133,6 +134,7 @@
|
|||
@import "./views/messages/_MNoticeBody.scss";
|
||||
@import "./views/messages/_MStickerBody.scss";
|
||||
@import "./views/messages/_MTextBody.scss";
|
||||
@import "./views/messages/_MVideoBody.scss";
|
||||
@import "./views/messages/_MessageActionBar.scss";
|
||||
@import "./views/messages/_MessageTimestamp.scss";
|
||||
@import "./views/messages/_MjolnirBody.scss";
|
||||
|
|
|
@ -44,6 +44,7 @@ limitations under the License.
|
|||
.mx_CompleteSecurity_actionRow {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: $font-28px;
|
||||
|
||||
.mx_AccessibleButton {
|
||||
margin-inline-start: 18px;
|
||||
|
|
24
res/css/views/elements/_ButtonPlaceholder.scss
Normal file
24
res/css/views/elements/_ButtonPlaceholder.scss
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
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_ButtonPlaceholder {
|
||||
font-size: $font-14px;
|
||||
font-weight: 600;
|
||||
padding: 7px 18px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
color: $authpage-secondary-color;
|
||||
}
|
22
res/css/views/messages/_MVideoBody.scss
Normal file
22
res/css/views/messages/_MVideoBody.scss
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
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.
|
||||
*/
|
||||
|
||||
span.mx_MVideoBody {
|
||||
video.mx_MVideoBody {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
|
@ -34,12 +34,17 @@ limitations under the License.
|
|||
background-color: $reaction-row-button-selected-bg-color;
|
||||
border-color: $reaction-row-button-selected-border-color;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_ReactionsRowButton_content {
|
||||
max-width: 100px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
padding-right: 4px;
|
||||
// ignore mouse events for all children, treat it as one entire hoverable entity
|
||||
* {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.mx_ReactionsRowButton_content {
|
||||
max-width: 100px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
padding-right: 4px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,27 +44,29 @@ limitations under the License.
|
|||
outline: none;
|
||||
overflow-x: hidden;
|
||||
|
||||
span.mx_UserPill, span.mx_RoomPill {
|
||||
padding-left: 21px;
|
||||
position: relative;
|
||||
&.mx_BasicMessageComposer_input_shouldShowPillAvatar {
|
||||
span.mx_UserPill, span.mx_RoomPill {
|
||||
padding-left: 21px;
|
||||
position: relative;
|
||||
|
||||
// avatar psuedo element
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
top: 2px;
|
||||
content: var(--avatar-letter);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: var(--avatar-background), $avatar-bg-color;
|
||||
color: $avatar-initial-color;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 16px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
font-weight: normal;
|
||||
line-height: $font-16px;
|
||||
font-size: $font-10-4px;
|
||||
// avatar psuedo element
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
top: 2px;
|
||||
content: var(--avatar-letter);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: var(--avatar-background), $avatar-bg-color;
|
||||
color: $avatar-initial-color;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 16px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
font-weight: normal;
|
||||
line-height: $font-16px;
|
||||
font-size: $font-10-4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -188,4 +188,8 @@ export default class BasePlatform {
|
|||
const callbackUrl = this.getSSOCallbackUrl(mxClient.getHomeserverUrl(), mxClient.getIdentityServerUrl());
|
||||
window.location.href = mxClient.getSsoLoginUrl(callbackUrl.toString(), loginType); // redirect to SSO
|
||||
}
|
||||
|
||||
onKeyDown(ev: KeyboardEvent): boolean {
|
||||
return false; // no shortcuts implemented
|
||||
}
|
||||
}
|
||||
|
|
|
@ -185,7 +185,7 @@ export async function promptForBackupPassphrase() {
|
|||
|
||||
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
|
||||
const { finished } = Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, {
|
||||
showSummary: false, keyCallback: k => key = k,
|
||||
showSummary: false, keyCallback: k => key = k,
|
||||
}, null, /* priority = */ false, /* static = */ true);
|
||||
|
||||
const success = await finished;
|
||||
|
|
|
@ -43,6 +43,8 @@ export const Key = {
|
|||
BACKTICK: "`",
|
||||
SPACE: " ",
|
||||
SLASH: "/",
|
||||
SQUARE_BRACKET_LEFT: "[",
|
||||
SQUARE_BRACKET_RIGHT: "]",
|
||||
A: "a",
|
||||
B: "b",
|
||||
C: "c",
|
||||
|
|
|
@ -172,6 +172,7 @@ Request:
|
|||
Response:
|
||||
[
|
||||
{
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111)
|
||||
type: "im.vector.modular.widgets",
|
||||
state_key: "wid1",
|
||||
content: {
|
||||
|
@ -190,6 +191,7 @@ Example:
|
|||
room_id: "!foo:bar",
|
||||
response: [
|
||||
{
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111)
|
||||
type: "im.vector.modular.widgets",
|
||||
state_key: "wid1",
|
||||
content: {
|
||||
|
|
|
@ -353,7 +353,7 @@ export const Commands = [
|
|||
return success(cli.setRoomTopic(roomId, args));
|
||||
}
|
||||
const room = cli.getRoom(roomId);
|
||||
if (!room) return reject('Bad room ID: ' + roomId);
|
||||
if (!room) return reject(_t("Failed to set topic"));
|
||||
|
||||
const topicEvents = room.currentState.getStateEvents('m.room.topic', '');
|
||||
const topic = topicEvents && topicEvents.getContent().topic;
|
||||
|
@ -724,9 +724,10 @@ export const Commands = [
|
|||
if (!isNaN(powerLevel)) {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const room = cli.getRoom(roomId);
|
||||
if (!room) return reject('Bad room ID: ' + roomId);
|
||||
if (!room) return reject(_t("Command failed"));
|
||||
|
||||
const powerLevelEvent = room.currentState.getStateEvents('m.room.power_levels', '');
|
||||
if (!powerLevelEvent.getContent().users[args]) return reject(_t("Could not find user in room"));
|
||||
return success(cli.setPowerLevel(roomId, userId, powerLevel, powerLevelEvent));
|
||||
}
|
||||
}
|
||||
|
@ -745,9 +746,10 @@ export const Commands = [
|
|||
if (matches) {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const room = cli.getRoom(roomId);
|
||||
if (!room) return reject('Bad room ID: ' + roomId);
|
||||
if (!room) return reject(_t("Command failed"));
|
||||
|
||||
const powerLevelEvent = room.currentState.getStateEvents('m.room.power_levels', '');
|
||||
if (!powerLevelEvent.getContent().users[args]) return reject(_t("Could not find user in room"));
|
||||
return success(cli.setPowerLevel(roomId, args, undefined, powerLevelEvent));
|
||||
}
|
||||
}
|
||||
|
@ -949,7 +951,7 @@ export const Commands = [
|
|||
// Command definitions for autocompletion ONLY:
|
||||
// /me is special because its not handled by SlashCommands.js and is instead done inside the Composer classes
|
||||
new Command({
|
||||
command: 'me',
|
||||
command: "me",
|
||||
args: '<message>',
|
||||
description: _td('Displays action'),
|
||||
category: CommandCategories.messages,
|
||||
|
@ -966,16 +968,7 @@ Commands.forEach(cmd => {
|
|||
});
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Process the given text for /commands and return a bound method to perform them.
|
||||
* @param {string} roomId The room in which the command was performed.
|
||||
* @param {string} input The raw text input by the user.
|
||||
* @return {null|function(): Object} Function returning an object with the property 'error' if there was an error
|
||||
* processing the command, or 'promise' if a request was sent out.
|
||||
* Returns null if the input didn't match a command.
|
||||
*/
|
||||
export function getCommand(roomId, input) {
|
||||
export function parseCommandString(input) {
|
||||
// trim any trailing whitespace, as it can confuse the parser for
|
||||
// IRC-style commands
|
||||
input = input.replace(/\s+$/, '');
|
||||
|
@ -991,6 +984,20 @@ export function getCommand(roomId, input) {
|
|||
cmd = input;
|
||||
}
|
||||
|
||||
return {cmd, args};
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the given text for /commands and return a bound method to perform them.
|
||||
* @param {string} roomId The room in which the command was performed.
|
||||
* @param {string} input The raw text input by the user.
|
||||
* @return {null|function(): Object} Function returning an object with the property 'error' if there was an error
|
||||
* processing the command, or 'promise' if a request was sent out.
|
||||
* Returns null if the input didn't match a command.
|
||||
*/
|
||||
export function getCommand(roomId, input) {
|
||||
const {cmd, args} = parseCommandString(input);
|
||||
|
||||
if (CommandMap.has(cmd)) {
|
||||
return () => CommandMap.get(cmd).run(roomId, args, cmd);
|
||||
}
|
||||
|
|
|
@ -603,6 +603,7 @@ const stateHandlers = {
|
|||
'm.room.guest_access': textForGuestAccessEvent,
|
||||
'm.room.related_groups': textForRelatedGroupsEvent,
|
||||
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111)
|
||||
'im.vector.modular.widgets': textForWidgetEvent,
|
||||
};
|
||||
|
||||
|
|
|
@ -70,6 +70,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
this._recoveryKey = null;
|
||||
this._recoveryKeyNode = null;
|
||||
this._setZxcvbnResultTimeout = null;
|
||||
this._backupKey = null;
|
||||
|
||||
this.state = {
|
||||
phase: PHASE_LOADING,
|
||||
|
@ -243,7 +244,15 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
createSecretStorageKey: async () => this._recoveryKey,
|
||||
keyBackupInfo: this.state.backupInfo,
|
||||
setupNewKeyBackup: !this.state.backupInfo && this.state.useKeyBackup,
|
||||
getKeyBackupPassphrase: promptForBackupPassphrase,
|
||||
getKeyBackupPassphrase: () => {
|
||||
// We may already have the backup key if we earlier went
|
||||
// through the restore backup path, so pass it along
|
||||
// rather than prompting again.
|
||||
if (this._backupKey) {
|
||||
return this._backupKey;
|
||||
}
|
||||
return promptForBackupPassphrase();
|
||||
},
|
||||
});
|
||||
}
|
||||
this.setState({
|
||||
|
@ -272,10 +281,18 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
}
|
||||
|
||||
_restoreBackup = async () => {
|
||||
// It's possible we'll need the backup key later on for bootstrapping,
|
||||
// so let's stash it here, rather than prompting for it twice.
|
||||
const keyCallback = k => this._backupKey = k;
|
||||
|
||||
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
|
||||
const { finished } = Modal.createTrackedDialog(
|
||||
'Restore Backup', '', RestoreKeyBackupDialog, {showSummary: false}, null,
|
||||
/* priority = */ false, /* static = */ false,
|
||||
'Restore Backup', '', RestoreKeyBackupDialog,
|
||||
{
|
||||
showSummary: false,
|
||||
keyCallback,
|
||||
},
|
||||
null, /* priority = */ false, /* static = */ false,
|
||||
);
|
||||
|
||||
await finished;
|
||||
|
|
|
@ -100,6 +100,8 @@ export default class EmojiProvider extends AutocompleteProvider {
|
|||
|
||||
// then sort by score (Infinity if matchedString not in shortname)
|
||||
sorters.push((c) => score(matchedString, c.shortname));
|
||||
// then sort by max score of all shortcodes, trim off the `:`
|
||||
sorters.push((c) => Math.min(...c.emoji.shortcodes.map(s => score(matchedString.substring(1), s))));
|
||||
// If the matchedString is not empty, sort by length of shortname. Example:
|
||||
// matchedString = ":bookmark"
|
||||
// completions = [":bookmark:", ":bookmark_tabs:", ...]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2017, 2018 New Vector Ltd
|
||||
Copyright 2017, 2018, 2020 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.
|
||||
|
@ -16,10 +16,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import React, {createRef} from 'react';
|
||||
import createReactClass from 'create-react-class';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as React from 'react';
|
||||
import * as PropTypes from 'prop-types';
|
||||
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
|
||||
import { DragDropContext } from 'react-beautiful-dnd';
|
||||
|
||||
import {Key, isOnlyCtrlOrCmdKeyEvent, isOnlyCtrlOrCmdIgnoreShiftKeyEvent} from '../../Keyboard';
|
||||
|
@ -29,7 +29,7 @@ import { fixupColorFonts } from '../../utils/FontManager';
|
|||
import * as sdk from '../../index';
|
||||
import dis from '../../dispatcher';
|
||||
import sessionStore from '../../stores/SessionStore';
|
||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||
import {MatrixClientPeg, MatrixClientCreds} from '../../MatrixClientPeg';
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import RoomListStore from "../../stores/RoomListStore";
|
||||
|
||||
|
@ -40,6 +40,8 @@ import {Resizer, CollapseDistributor} from '../../resizer';
|
|||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import * as KeyboardShortcuts from "../../accessibility/KeyboardShortcuts";
|
||||
import HomePage from "./HomePage";
|
||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||
import PlatformPeg from "../../PlatformPeg";
|
||||
// We need to fetch each pinned message individually (if we don't already have it)
|
||||
// so each pinned message may trigger a request. Limit the number per room for sanity.
|
||||
// NB. this is just for server notices rather than pinned messages in general.
|
||||
|
@ -52,6 +54,52 @@ function canElementReceiveInput(el) {
|
|||
!!el.getAttribute("contenteditable");
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
matrixClient: MatrixClient;
|
||||
onRegistered: (credentials: MatrixClientCreds) => Promise<MatrixClient>;
|
||||
viaServers?: string[];
|
||||
hideToSRUsers: boolean;
|
||||
resizeNotifier: ResizeNotifier;
|
||||
middleDisabled: boolean;
|
||||
initialEventPixelOffset: number;
|
||||
leftDisabled: boolean;
|
||||
rightDisabled: boolean;
|
||||
showCookieBar: boolean;
|
||||
hasNewVersion: boolean;
|
||||
userHasGeneratedPassword: boolean;
|
||||
showNotifierToolbar: boolean;
|
||||
page_type: string;
|
||||
autoJoin: boolean;
|
||||
thirdPartyInvite?: object;
|
||||
roomOobData?: object;
|
||||
currentRoomId: string;
|
||||
ConferenceHandler?: object;
|
||||
collapseLhs: boolean;
|
||||
checkingForUpdate: boolean;
|
||||
config: {
|
||||
piwik: {
|
||||
policyUrl: string;
|
||||
},
|
||||
[key: string]: any,
|
||||
};
|
||||
currentUserId?: string;
|
||||
currentGroupId?: string;
|
||||
currentGroupIsNew?: boolean;
|
||||
version?: string;
|
||||
newVersion?: string;
|
||||
newVersionReleaseNotes?: string;
|
||||
}
|
||||
interface IState {
|
||||
mouseDown?: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
syncErrorData: any;
|
||||
useCompactLayout: boolean;
|
||||
serverNoticeEvents: MatrixEvent[];
|
||||
userHasGeneratedPassword: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is what our MatrixChat shows when we are logged in. The precise view is
|
||||
* determined by the page_type property.
|
||||
|
@ -61,10 +109,10 @@ function canElementReceiveInput(el) {
|
|||
*
|
||||
* Components mounted below us can access the matrix client via the react context.
|
||||
*/
|
||||
const LoggedInView = createReactClass({
|
||||
displayName: 'LoggedInView',
|
||||
class LoggedInView extends React.PureComponent<IProps, IState> {
|
||||
static displayName = 'LoggedInView';
|
||||
|
||||
propTypes: {
|
||||
static propTypes = {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||
page_type: PropTypes.string.isRequired,
|
||||
onRoomCreated: PropTypes.func,
|
||||
|
@ -77,25 +125,28 @@ const LoggedInView = createReactClass({
|
|||
viaServers: PropTypes.arrayOf(PropTypes.string),
|
||||
|
||||
// and lots and lots of other stuff.
|
||||
},
|
||||
};
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
protected readonly _matrixClient: MatrixClient;
|
||||
protected readonly _roomView: React.RefObject<any>;
|
||||
protected readonly _resizeContainer: React.RefObject<ResizeHandle>;
|
||||
protected readonly _sessionStore: sessionStore;
|
||||
protected readonly _sessionStoreToken: { remove: () => void };
|
||||
protected resizer: Resizer;
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
mouseDown: undefined,
|
||||
syncErrorData: undefined,
|
||||
userHasGeneratedPassword: false,
|
||||
// use compact timeline view
|
||||
useCompactLayout: SettingsStore.getValue('useCompactLayout'),
|
||||
// any currently active server notice events
|
||||
serverNoticeEvents: [],
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.resizer = this._createResizer();
|
||||
this.resizer.attach();
|
||||
this._loadResizerPreferences();
|
||||
},
|
||||
|
||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
||||
UNSAFE_componentWillMount: function() {
|
||||
// stash the MatrixClient in case we log out before we are unmounted
|
||||
this._matrixClient = this.props.matrixClient;
|
||||
|
||||
|
@ -117,22 +168,29 @@ const LoggedInView = createReactClass({
|
|||
|
||||
fixupColorFonts();
|
||||
|
||||
this._roomView = createRef();
|
||||
},
|
||||
this._roomView = React.createRef();
|
||||
this._resizeContainer = React.createRef();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidMount() {
|
||||
this.resizer = this._createResizer();
|
||||
this.resizer.attach();
|
||||
this._loadResizerPreferences();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
// attempt to guess when a banner was opened or closed
|
||||
if (
|
||||
(prevProps.showCookieBar !== this.props.showCookieBar) ||
|
||||
(prevProps.hasNewVersion !== this.props.hasNewVersion) ||
|
||||
(prevProps.userHasGeneratedPassword !== this.props.userHasGeneratedPassword) ||
|
||||
(prevState.userHasGeneratedPassword !== this.state.userHasGeneratedPassword) ||
|
||||
(prevProps.showNotifierToolbar !== this.props.showNotifierToolbar)
|
||||
) {
|
||||
this.props.resizeNotifier.notifyBannersChanged();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
componentWillUnmount: function() {
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('keydown', this._onNativeKeyDown, false);
|
||||
this._matrixClient.removeListener("accountData", this.onAccountData);
|
||||
this._matrixClient.removeListener("sync", this.onSync);
|
||||
|
@ -141,7 +199,7 @@ const LoggedInView = createReactClass({
|
|||
this._sessionStoreToken.remove();
|
||||
}
|
||||
this.resizer.detach();
|
||||
},
|
||||
}
|
||||
|
||||
// Child components assume that the client peg will not be null, so give them some
|
||||
// sort of assurance here by only allowing a re-render if the client is truthy.
|
||||
|
@ -149,22 +207,22 @@ const LoggedInView = createReactClass({
|
|||
// This is required because `LoggedInView` maintains its own state and if this state
|
||||
// updates after the client peg has been made null (during logout), then it will
|
||||
// attempt to re-render and the children will throw errors.
|
||||
shouldComponentUpdate: function() {
|
||||
shouldComponentUpdate() {
|
||||
return Boolean(MatrixClientPeg.get());
|
||||
},
|
||||
}
|
||||
|
||||
canResetTimelineInRoom: function(roomId) {
|
||||
canResetTimelineInRoom = (roomId) => {
|
||||
if (!this._roomView.current) {
|
||||
return true;
|
||||
}
|
||||
return this._roomView.current.canResetTimeline();
|
||||
},
|
||||
};
|
||||
|
||||
_setStateFromSessionStore() {
|
||||
_setStateFromSessionStore = () => {
|
||||
this.setState({
|
||||
userHasGeneratedPassword: Boolean(this._sessionStore.getCachedPassword()),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
_createResizer() {
|
||||
const classNames = {
|
||||
|
@ -188,24 +246,22 @@ const LoggedInView = createReactClass({
|
|||
},
|
||||
};
|
||||
const resizer = new Resizer(
|
||||
this.resizeContainer,
|
||||
this._resizeContainer.current,
|
||||
CollapseDistributor,
|
||||
collapseConfig);
|
||||
resizer.setClassNames(classNames);
|
||||
return resizer;
|
||||
},
|
||||
}
|
||||
|
||||
_loadResizerPreferences() {
|
||||
let lhsSize = window.localStorage.getItem("mx_lhs_size");
|
||||
if (lhsSize !== null) {
|
||||
lhsSize = parseInt(lhsSize, 10);
|
||||
} else {
|
||||
let lhsSize = parseInt(window.localStorage.getItem("mx_lhs_size"), 10);
|
||||
if (isNaN(lhsSize)) {
|
||||
lhsSize = 350;
|
||||
}
|
||||
this.resizer.forHandleAt(0).resize(lhsSize);
|
||||
},
|
||||
}
|
||||
|
||||
onAccountData: function(event) {
|
||||
onAccountData = (event) => {
|
||||
if (event.getType() === "im.vector.web.settings") {
|
||||
this.setState({
|
||||
useCompactLayout: event.getContent().useCompactLayout,
|
||||
|
@ -214,9 +270,9 @@ const LoggedInView = createReactClass({
|
|||
if (event.getType() === "m.ignored_user_list") {
|
||||
dis.dispatch({action: "ignore_state_changed"});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
onSync: function(syncState, oldSyncState, data) {
|
||||
onSync = (syncState, oldSyncState, data) => {
|
||||
const oldErrCode = (
|
||||
this.state.syncErrorData &&
|
||||
this.state.syncErrorData.error &&
|
||||
|
@ -238,16 +294,16 @@ const LoggedInView = createReactClass({
|
|||
if (oldSyncState === 'PREPARED' && syncState === 'SYNCING') {
|
||||
this._updateServerNoticeEvents();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
onRoomStateEvents: function(ev, state) {
|
||||
onRoomStateEvents = (ev, state) => {
|
||||
const roomLists = RoomListStore.getRoomLists();
|
||||
if (roomLists['m.server_notice'] && roomLists['m.server_notice'].some(r => r.roomId === ev.getRoomId())) {
|
||||
this._updateServerNoticeEvents();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
_updateServerNoticeEvents: async function() {
|
||||
_updateServerNoticeEvents = async () => {
|
||||
const roomLists = RoomListStore.getRoomLists();
|
||||
if (!roomLists['m.server_notice']) return [];
|
||||
|
||||
|
@ -260,16 +316,16 @@ const LoggedInView = createReactClass({
|
|||
const pinnedEventIds = pinStateEvent.getContent().pinned.slice(0, MAX_PINNED_NOTICES_PER_ROOM);
|
||||
for (const eventId of pinnedEventIds) {
|
||||
const timeline = await this._matrixClient.getEventTimeline(room.getUnfilteredTimelineSet(), eventId, 0);
|
||||
const ev = timeline.getEvents().find(ev => ev.getId() === eventId);
|
||||
if (ev) pinnedEvents.push(ev);
|
||||
const event = timeline.getEvents().find(ev => ev.getId() === eventId);
|
||||
if (event) pinnedEvents.push(event);
|
||||
}
|
||||
}
|
||||
this.setState({
|
||||
serverNoticeEvents: pinnedEvents,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
_onPaste: function(ev) {
|
||||
_onPaste = (ev) => {
|
||||
let canReceiveInput = false;
|
||||
let element = ev.target;
|
||||
// test for all parents because the target can be a child of a contenteditable element
|
||||
|
@ -283,7 +339,7 @@ const LoggedInView = createReactClass({
|
|||
// so dispatch synchronously before paste happens
|
||||
dis.dispatch({action: 'focus_composer'}, true);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
SOME HACKERY BELOW:
|
||||
|
@ -307,22 +363,22 @@ const LoggedInView = createReactClass({
|
|||
We also listen with a native listener on the document to get keydown events when no element is focused.
|
||||
Bubbling is irrelevant here as the target is the body element.
|
||||
*/
|
||||
_onReactKeyDown: function(ev) {
|
||||
_onReactKeyDown = (ev) => {
|
||||
// events caught while bubbling up on the root element
|
||||
// of this component, so something must be focused.
|
||||
this._onKeyDown(ev);
|
||||
},
|
||||
};
|
||||
|
||||
_onNativeKeyDown: function(ev) {
|
||||
_onNativeKeyDown = (ev) => {
|
||||
// only pass this if there is no focused element.
|
||||
// if there is, _onKeyDown will be called by the
|
||||
// react keydown handler that respects the react bubbling order.
|
||||
if (ev.target === document.body) {
|
||||
this._onKeyDown(ev);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
_onKeyDown: function(ev) {
|
||||
_onKeyDown = (ev) => {
|
||||
/*
|
||||
// Remove this for now as ctrl+alt = alt-gr so this breaks keyboards which rely on alt-gr for numbers
|
||||
// Will need to find a better meta key if anyone actually cares about using this.
|
||||
|
@ -407,6 +463,11 @@ const LoggedInView = createReactClass({
|
|||
});
|
||||
handled = true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// if we do not have a handler for it, pass it to the platform which might
|
||||
handled = PlatformPeg.get().onKeyDown(ev);
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
|
@ -432,19 +493,19 @@ const LoggedInView = createReactClass({
|
|||
// that would prevent typing in the now-focussed composer
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* dispatch a page-up/page-down/etc to the appropriate component
|
||||
* @param {Object} ev The key event
|
||||
*/
|
||||
_onScrollKeyPressed: function(ev) {
|
||||
_onScrollKeyPressed = (ev) => {
|
||||
if (this._roomView.current) {
|
||||
this._roomView.current.handleScrollKey(ev);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
_onDragEnd: function(result) {
|
||||
_onDragEnd = (result) => {
|
||||
// Dragged to an invalid destination, not onto a droppable
|
||||
if (!result.destination) {
|
||||
return;
|
||||
|
@ -467,9 +528,9 @@ const LoggedInView = createReactClass({
|
|||
} else if (dest.startsWith('room-sub-list-droppable_')) {
|
||||
this._onRoomTileEndDrag(result);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
_onRoomTileEndDrag: function(result) {
|
||||
_onRoomTileEndDrag = (result) => {
|
||||
let newTag = result.destination.droppableId.split('_')[1];
|
||||
let prevTag = result.source.droppableId.split('_')[1];
|
||||
if (newTag === 'undefined') newTag = undefined;
|
||||
|
@ -486,9 +547,9 @@ const LoggedInView = createReactClass({
|
|||
prevTag, newTag,
|
||||
oldIndex, newIndex,
|
||||
), true);
|
||||
},
|
||||
};
|
||||
|
||||
_onMouseDown: function(ev) {
|
||||
_onMouseDown = (ev) => {
|
||||
// When the panels are disabled, clicking on them results in a mouse event
|
||||
// which bubbles to certain elements in the tree. When this happens, close
|
||||
// any settings page that is currently open (user/room/group).
|
||||
|
@ -507,9 +568,9 @@ const LoggedInView = createReactClass({
|
|||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
_onMouseUp: function(ev) {
|
||||
_onMouseUp = (ev) => {
|
||||
if (!this.state.mouseDown) return;
|
||||
|
||||
const deltaX = ev.pageX - this.state.mouseDown.x;
|
||||
|
@ -528,13 +589,9 @@ const LoggedInView = createReactClass({
|
|||
// Always clear the mouseDown state to ensure we don't accidentally
|
||||
// use stale values due to the mouseDown checks.
|
||||
this.setState({mouseDown: null});
|
||||
},
|
||||
};
|
||||
|
||||
_setResizeContainerRef(div) {
|
||||
this.resizeContainer = div;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
const LeftPanel = sdk.getComponent('structures.LeftPanel');
|
||||
const RoomView = sdk.getComponent('structures.RoomView');
|
||||
const UserView = sdk.getComponent('structures.UserView');
|
||||
|
@ -647,7 +704,7 @@ const LoggedInView = createReactClass({
|
|||
{ topBar }
|
||||
<ToastContainer />
|
||||
<DragDropContext onDragEnd={this._onDragEnd}>
|
||||
<div ref={this._setResizeContainerRef} className={bodyClasses}>
|
||||
<div ref={this._resizeContainer} className={bodyClasses}>
|
||||
<LeftPanel
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
collapsed={this.props.collapseLhs || false}
|
||||
|
@ -660,7 +717,7 @@ const LoggedInView = createReactClass({
|
|||
</div>
|
||||
</MatrixClientContext.Provider>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default LoggedInView;
|
|
@ -49,7 +49,6 @@ import RoomViewStore from '../../stores/RoomViewStore';
|
|||
import RoomScrollStateStore from '../../stores/RoomScrollStateStore';
|
||||
import WidgetEchoStore from '../../stores/WidgetEchoStore';
|
||||
import SettingsStore, {SettingLevel} from "../../settings/SettingsStore";
|
||||
import WidgetUtils from '../../utils/WidgetUtils';
|
||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||
import RightPanelStore from "../../stores/RightPanelStore";
|
||||
import {haveTileForEvent} from "../views/rooms/EventTile";
|
||||
|
@ -406,13 +405,9 @@ export default createReactClass({
|
|||
const hideWidgetDrawer = localStorage.getItem(
|
||||
room.roomId + "_hide_widget_drawer");
|
||||
|
||||
if (hideWidgetDrawer === "true") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const widgets = WidgetEchoStore.getEchoedRoomWidgets(room.roomId, WidgetUtils.getRoomWidgets(room));
|
||||
|
||||
return widgets.length > 0 || WidgetEchoStore.roomHasPendingWidgets(room.roomId, WidgetUtils.getRoomWidgets(room));
|
||||
// This is confusing, but it means to say that we default to the tray being
|
||||
// hidden unless the user clicked to open it.
|
||||
return hideWidgetDrawer === "false";
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
|
|
|
@ -108,14 +108,13 @@ export default class SetupEncryptionBody extends React.Component {
|
|||
member={MatrixClientPeg.get().getUser(this.state.verificationRequest.otherUserId)}
|
||||
/>;
|
||||
} else if (phase === PHASE_INTRO) {
|
||||
const InlineSpinner = sdk.getComponent('elements.InlineSpinner');
|
||||
const ButtonPlaceholder = sdk.getComponent("elements.ButtonPlaceholder");
|
||||
return (
|
||||
<div>
|
||||
<p>{_t(
|
||||
"Open an existing session & use it to verify this one, " +
|
||||
"Use an existing session to verify this one, " +
|
||||
"granting it access to encrypted messages.",
|
||||
)}</p>
|
||||
<p className="mx_CompleteSecurity_waiting"><InlineSpinner />{_t("Waiting…")}</p>
|
||||
<p>{_t(
|
||||
"If you can’t access one, <button>use your recovery key or passphrase.</button>",
|
||||
{}, {
|
||||
|
@ -133,6 +132,7 @@ export default class SetupEncryptionBody extends React.Component {
|
|||
>
|
||||
{_t("Skip")}
|
||||
</AccessibleButton>
|
||||
<ButtonPlaceholder>{_t("Use your other device to continue…")}</ButtonPlaceholder>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -66,7 +66,7 @@ export default createReactClass({
|
|||
}
|
||||
|
||||
if (!this.state.isPublic && SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||
createOpts.encryption = this.state.isEncrypted;
|
||||
opts.encryption = this.state.isEncrypted;
|
||||
}
|
||||
|
||||
return opts;
|
||||
|
|
19
src/components/views/elements/ButtonPlaceholder.js
Normal file
19
src/components/views/elements/ButtonPlaceholder.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
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.
|
||||
*/
|
||||
|
||||
export default function ButtonPlaceholder(props) {
|
||||
return <div class="mx_ButtonPlaceholder">{props.children}</div>;
|
||||
}
|
|
@ -81,12 +81,14 @@ export default createReactClass({
|
|||
const hideWidgetKey = this.props.room.roomId + '_hide_widget_drawer';
|
||||
switch (action.action) {
|
||||
case 'appsDrawer':
|
||||
// Note: these booleans are awkward because localstorage is fundamentally
|
||||
// string-based. We also do exact equality on the strings later on.
|
||||
if (action.show) {
|
||||
localStorage.removeItem(hideWidgetKey);
|
||||
localStorage.setItem(hideWidgetKey, "false");
|
||||
} else {
|
||||
// Store hidden state of widget
|
||||
// Don't show if previously hidden
|
||||
localStorage.setItem(hideWidgetKey, true);
|
||||
localStorage.setItem(hideWidgetKey, "true");
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
|
@ -39,6 +39,7 @@ import EMOTICON_REGEX from 'emojibase-regex/emoticon';
|
|||
import * as sdk from '../../../index';
|
||||
import {Key} from "../../../Keyboard";
|
||||
import {EMOTICON_TO_EMOJI} from "../../../emoji";
|
||||
import {CommandCategories, CommandMap, parseCommandString} from "../../../SlashCommands";
|
||||
|
||||
const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s$');
|
||||
|
||||
|
@ -84,6 +85,7 @@ export default class BasicMessageEditor extends React.Component {
|
|||
super(props);
|
||||
this.state = {
|
||||
autoComplete: null,
|
||||
showPillAvatar: SettingsStore.getValue("Pill.shouldShowPillAvatar"),
|
||||
};
|
||||
this._editorRef = null;
|
||||
this._autocompleteRef = null;
|
||||
|
@ -92,6 +94,7 @@ export default class BasicMessageEditor extends React.Component {
|
|||
this._isIMEComposing = false;
|
||||
this._hasTextSelected = false;
|
||||
this._emoticonSettingHandle = null;
|
||||
this._shouldShowPillAvatarSettingHandle = null;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
|
@ -162,7 +165,16 @@ export default class BasicMessageEditor extends React.Component {
|
|||
}
|
||||
this.setState({autoComplete: this.props.model.autoComplete});
|
||||
this.historyManager.tryPush(this.props.model, selection, inputType, diff);
|
||||
TypingStore.sharedInstance().setSelfTyping(this.props.room.roomId, !this.props.model.isEmpty);
|
||||
|
||||
let isTyping = !this.props.model.isEmpty;
|
||||
// If the user is entering a command, only consider them typing if it is one which sends a message into the room
|
||||
if (isTyping && this.props.model.parts[0].type === "command") {
|
||||
const {cmd} = parseCommandString(this.props.model.parts[0].text);
|
||||
if (!CommandMap.has(cmd) || CommandMap.get(cmd).category !== CommandCategories.messages) {
|
||||
isTyping = false;
|
||||
}
|
||||
}
|
||||
TypingStore.sharedInstance().setSelfTyping(this.props.room.roomId, isTyping);
|
||||
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange();
|
||||
|
@ -508,10 +520,15 @@ export default class BasicMessageEditor extends React.Component {
|
|||
this.setState({completionIndex});
|
||||
}
|
||||
|
||||
_configureEmoticonAutoReplace() {
|
||||
_configureEmoticonAutoReplace = () => {
|
||||
const shouldReplace = SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji');
|
||||
this.props.model.setTransformCallback(shouldReplace ? this._replaceEmoticon : null);
|
||||
}
|
||||
};
|
||||
|
||||
_configureShouldShowPillAvatar = () => {
|
||||
const showPillAvatar = SettingsStore.getValue("Pill.shouldShowPillAvatar");
|
||||
this.setState({ showPillAvatar });
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener("selectionchange", this._onSelectionChange);
|
||||
|
@ -519,15 +536,17 @@ export default class BasicMessageEditor extends React.Component {
|
|||
this._editorRef.removeEventListener("compositionstart", this._onCompositionStart, true);
|
||||
this._editorRef.removeEventListener("compositionend", this._onCompositionEnd, true);
|
||||
SettingsStore.unwatchSetting(this._emoticonSettingHandle);
|
||||
SettingsStore.unwatchSetting(this._shouldShowPillAvatarSettingHandle);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const model = this.props.model;
|
||||
model.setUpdateCallback(this._updateEditorState);
|
||||
this._emoticonSettingHandle = SettingsStore.watchSetting('MessageComposerInput.autoReplaceEmoji', null, () => {
|
||||
this._configureEmoticonAutoReplace();
|
||||
});
|
||||
this._emoticonSettingHandle = SettingsStore.watchSetting('MessageComposerInput.autoReplaceEmoji', null,
|
||||
this._configureEmoticonAutoReplace);
|
||||
this._configureEmoticonAutoReplace();
|
||||
this._shouldShowPillAvatarSettingHandle = SettingsStore.watchSetting("Pill.shouldShowPillAvatar", null,
|
||||
this._configureShouldShowPillAvatar);
|
||||
const partCreator = model.partCreator;
|
||||
// TODO: does this allow us to get rid of EditorStateTransfer?
|
||||
// not really, but we could not serialize the parts, and just change the autoCompleter
|
||||
|
@ -605,9 +624,12 @@ export default class BasicMessageEditor extends React.Component {
|
|||
/>
|
||||
</div>);
|
||||
}
|
||||
const classes = classNames("mx_BasicMessageComposer", {
|
||||
const wrapperClasses = classNames("mx_BasicMessageComposer", {
|
||||
"mx_BasicMessageComposer_input_error": this.state.showVisualBell,
|
||||
});
|
||||
const classes = classNames("mx_BasicMessageComposer_input", {
|
||||
"mx_BasicMessageComposer_input_shouldShowPillAvatar": this.state.showPillAvatar,
|
||||
});
|
||||
|
||||
const MessageComposerFormatBar = sdk.getComponent('rooms.MessageComposerFormatBar');
|
||||
const shortcuts = {
|
||||
|
@ -618,11 +640,11 @@ export default class BasicMessageEditor extends React.Component {
|
|||
|
||||
const {completionIndex} = this.state;
|
||||
|
||||
return (<div className={classes}>
|
||||
return (<div className={wrapperClasses}>
|
||||
{ autoComplete }
|
||||
<MessageComposerFormatBar ref={ref => this._formatBarRef = ref} onAction={this._onFormatAction} shortcuts={shortcuts} />
|
||||
<div
|
||||
className="mx_BasicMessageComposer_input"
|
||||
className={classes}
|
||||
contentEditable="true"
|
||||
tabIndex="0"
|
||||
onBlur={this._onBlur}
|
||||
|
|
|
@ -59,6 +59,7 @@ const stateEventTileTypes = {
|
|||
'm.room.power_levels': 'messages.TextualEvent',
|
||||
'm.room.pinned_events': 'messages.TextualEvent',
|
||||
'm.room.server_acl': 'messages.TextualEvent',
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111)
|
||||
'im.vector.modular.widgets': 'messages.TextualEvent',
|
||||
'm.room.tombstone': 'messages.TextualEvent',
|
||||
'm.room.join_rules': 'messages.TextualEvent',
|
||||
|
|
|
@ -139,7 +139,7 @@ export default class DevicesPanel extends React.Component {
|
|||
body: _t("Click the button below to confirm deleting these sessions.", {
|
||||
count: numDevices,
|
||||
}),
|
||||
continueText: _t("Delete sessions"),
|
||||
continueText: _t("Delete sessions", {count: numDevices}),
|
||||
continueKind: "danger",
|
||||
},
|
||||
};
|
||||
|
|
|
@ -33,6 +33,7 @@ const plEventsToLabels = {
|
|||
"m.room.tombstone": _td("Upgrade the room"),
|
||||
"m.room.encryption": _td("Enable room encryption"),
|
||||
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111)
|
||||
"im.vector.modular.widgets": _td("Modify widgets"),
|
||||
};
|
||||
|
||||
|
@ -47,6 +48,7 @@ const plEventsToShow = {
|
|||
"m.room.tombstone": {isState: true},
|
||||
"m.room.encryption": {isState: true},
|
||||
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111)
|
||||
"im.vector.modular.widgets": {isState: true},
|
||||
};
|
||||
|
||||
|
|
|
@ -20,8 +20,8 @@ import { _t } from '../../../languageHandler';
|
|||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import VerificationQRCode from "../elements/crypto/VerificationQRCode";
|
||||
import {VerificationRequest} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||
import Spinner from "../elements/Spinner";
|
||||
import {SCAN_QR_CODE_METHOD} from "matrix-js-sdk/src/crypto/verification/QRCode";
|
||||
|
||||
@replaceableComponent("views.verification.VerificationQREmojiOptions")
|
||||
export default class VerificationQREmojiOptions extends React.Component {
|
||||
|
@ -31,31 +31,17 @@ export default class VerificationQREmojiOptions extends React.Component {
|
|||
onStartEmoji: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
qrProps: null,
|
||||
};
|
||||
|
||||
this._prepareQrCode(props.request);
|
||||
}
|
||||
|
||||
async _prepareQrCode(request: VerificationRequest) {
|
||||
try {
|
||||
const props = await VerificationQRCode.getPropsForRequest(request);
|
||||
this.setState({qrProps: props});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
// We just won't show a QR code
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let qrCode = <div className='mx_VerificationQREmojiOptions_noQR'><Spinner /></div>;
|
||||
if (this.state.qrProps) {
|
||||
qrCode = <VerificationQRCode {...this.state.qrProps} />;
|
||||
const {request} = this.props;
|
||||
const showQR = request.otherPartySupportsMethod(SCAN_QR_CODE_METHOD);
|
||||
|
||||
let qrCode;
|
||||
if (showQR) {
|
||||
qrCode = <VerificationQRCode qrCodeData={request.qrCodeData} />;
|
||||
} else {
|
||||
qrCode = <div className='mx_VerificationQREmojiOptions_noQR'><Spinner /></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{_t("Verify this session by completing one of the following:")}
|
||||
|
|
|
@ -132,12 +132,12 @@ export default class VerificationShowSas extends React.Component {
|
|||
/>;
|
||||
} else {
|
||||
confirm = <React.Fragment>
|
||||
<AccessibleButton onClick={this.onMatchClick} kind="primary">
|
||||
{ _t("They match") }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton onClick={this.onDontMatchClick} kind="danger">
|
||||
{ _t("They don't match") }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton onClick={this.onMatchClick} kind="primary">
|
||||
{ _t("They match") }
|
||||
</AccessibleButton>
|
||||
</React.Fragment>;
|
||||
}
|
||||
|
||||
|
|
|
@ -177,6 +177,7 @@
|
|||
"Changes your avatar in this current room only": "Changes your avatar in this current room only",
|
||||
"Changes your avatar in all rooms": "Changes your avatar in all rooms",
|
||||
"Gets or sets the room topic": "Gets or sets the room topic",
|
||||
"Failed to set topic": "Failed to set topic",
|
||||
"This room has no topic.": "This room has no topic.",
|
||||
"Sets the room name": "Sets the room name",
|
||||
"Invites user with given id to current room": "Invites user with given id to current room",
|
||||
|
@ -196,6 +197,8 @@
|
|||
"Unignored user": "Unignored user",
|
||||
"You are no longer ignoring %(userId)s": "You are no longer ignoring %(userId)s",
|
||||
"Define the power level of a user": "Define the power level of a user",
|
||||
"Command failed": "Command failed",
|
||||
"Could not find user in room": "Could not find user in room",
|
||||
"Deops user with given id": "Deops user with given id",
|
||||
"Opens the Developer Tools dialog": "Opens the Developer Tools dialog",
|
||||
"Adds a custom widget by URL to the room": "Adds a custom widget by URL to the room",
|
||||
|
@ -619,7 +622,8 @@
|
|||
"Confirm deleting these sessions": "Confirm deleting these sessions",
|
||||
"Click the button below to confirm deleting these sessions.|other": "Click the button below to confirm deleting these sessions.",
|
||||
"Click the button below to confirm deleting these sessions.|one": "Click the button below to confirm deleting this session.",
|
||||
"Delete sessions": "Delete sessions",
|
||||
"Delete sessions|other": "Delete sessions",
|
||||
"Delete sessions|one": "Delete session",
|
||||
"Authentication": "Authentication",
|
||||
"Delete %(count)s sessions|other": "Delete %(count)s sessions",
|
||||
"Delete %(count)s sessions|one": "Delete %(count)s session",
|
||||
|
@ -2112,9 +2116,9 @@
|
|||
"You can now close this window or <a>log in</a> to your new account.": "You can now close this window or <a>log in</a> to your new account.",
|
||||
"Registration Successful": "Registration Successful",
|
||||
"Create your account": "Create your account",
|
||||
"Open an existing session & use it to verify this one, granting it access to encrypted messages.": "Open an existing session & use it to verify this one, granting it access to encrypted messages.",
|
||||
"Waiting…": "Waiting…",
|
||||
"Use an existing session to verify this one, granting it access to encrypted messages.": "Use an existing session to verify this one, granting it access to encrypted messages.",
|
||||
"If you can’t access one, <button>use your recovery key or passphrase.</button>": "If you can’t access one, <button>use your recovery key or passphrase.</button>",
|
||||
"Use your other device to continue…": "Use your other device to continue…",
|
||||
"Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.",
|
||||
"Your new session is now verified. Other users will see it as trusted.": "Your new session is now verified. Other users will see it as trusted.",
|
||||
"Without completing security on this session, it won’t have access to encrypted messages.": "Without completing security on this session, it won’t have access to encrypted messages.",
|
||||
|
|
|
@ -64,6 +64,7 @@ class ActiveWidgetStore extends EventEmitter {
|
|||
// Everything else relies on views listening for events and calling setters
|
||||
// on this class which is terrible. This store should just listen for events
|
||||
// and keep itself up to date.
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111)
|
||||
if (ev.getType() !== 'im.vector.modular.widgets') return;
|
||||
|
||||
if (ev.getStateKey() === this._persistentWidgetId) {
|
||||
|
|
|
@ -68,6 +68,7 @@ export default class WidgetUtils {
|
|||
return false;
|
||||
}
|
||||
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111)
|
||||
return room.currentState.maySendStateEvent('im.vector.modular.widgets', me);
|
||||
}
|
||||
|
||||
|
@ -182,6 +183,7 @@ export default class WidgetUtils {
|
|||
}
|
||||
|
||||
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111)
|
||||
const startingWidgetEvents = room.currentState.getStateEvents('im.vector.modular.widgets');
|
||||
if (eventsInIntendedState(startingWidgetEvents)) {
|
||||
resolve();
|
||||
|
@ -191,6 +193,7 @@ export default class WidgetUtils {
|
|||
function onRoomStateEvents(ev) {
|
||||
if (ev.getRoomId() !== roomId) return;
|
||||
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111)
|
||||
const currentWidgetEvents = room.currentState.getStateEvents('im.vector.modular.widgets');
|
||||
|
||||
if (eventsInIntendedState(currentWidgetEvents)) {
|
||||
|
@ -272,8 +275,7 @@ export default class WidgetUtils {
|
|||
WidgetEchoStore.setRoomWidgetEcho(roomId, widgetId, content);
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
// TODO - Room widgets need to be moved to 'm.widget' state events
|
||||
// https://docs.google.com/document/d/1uPF7XWY_dXTKVKV7jZQ2KmsI19wn9-kFRgQ1tFQP7wQ/edit?usp=sharing
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111)
|
||||
return client.sendStateEvent(roomId, "im.vector.modular.widgets", content, widgetId).then(() => {
|
||||
return WidgetUtils.waitForRoomWidget(widgetId, roomId, addingWidget);
|
||||
}).finally(() => {
|
||||
|
@ -287,6 +289,7 @@ export default class WidgetUtils {
|
|||
* @return {[object]} Array containing current / active room widgets
|
||||
*/
|
||||
static getRoomWidgets(room: Room) {
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111)
|
||||
const appsStateEvents = room.currentState.getStateEvents('im.vector.modular.widgets');
|
||||
if (!appsStateEvents) {
|
||||
return [];
|
||||
|
|
|
@ -61,15 +61,18 @@ function UntrustedDeviceDialog(props) {
|
|||
}
|
||||
|
||||
export async function verifyDevice(user, device) {
|
||||
if (!await enable4SIfNeeded()) {
|
||||
return;
|
||||
const cli = MatrixClientPeg.get();
|
||||
// if cross-signing is not explicitly disabled, check if it should be enabled first.
|
||||
if (cli.getCryptoTrustCrossSignedDevices()) {
|
||||
if (!await enable4SIfNeeded()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Modal.createTrackedDialog("Verification warning", "unverified session", UntrustedDeviceDialog, {
|
||||
user,
|
||||
device,
|
||||
onFinished: async (action) => {
|
||||
if (action === "sas") {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const verificationRequestPromise = cli.legacyDeviceVerification(
|
||||
user.userId,
|
||||
device.deviceId,
|
||||
|
@ -95,10 +98,13 @@ export async function verifyDevice(user, device) {
|
|||
}
|
||||
|
||||
export async function legacyVerifyUser(user) {
|
||||
if (!await enable4SIfNeeded()) {
|
||||
return;
|
||||
}
|
||||
const cli = MatrixClientPeg.get();
|
||||
// if cross-signing is not explicitly disabled, check if it should be enabled first.
|
||||
if (cli.getCryptoTrustCrossSignedDevices()) {
|
||||
if (!await enable4SIfNeeded()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const verificationRequestPromise = cli.requestVerification(user.userId);
|
||||
dis.dispatch({
|
||||
action: "set_right_panel_phase",
|
||||
|
|
|
@ -177,6 +177,7 @@ describe.skip('RoomSettings', () => {
|
|||
'm.room.history_visibility': 50,
|
||||
'm.room.power_levels': 50,
|
||||
'm.room.topic': 50,
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111)
|
||||
'im.vector.modular.widgets': 50,
|
||||
},
|
||||
},
|
||||
|
|
39
yarn.lock
39
yarn.lock
|
@ -2270,11 +2270,6 @@ ccount@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.5.tgz#ac82a944905a65ce204eb03023157edf29425c17"
|
||||
integrity sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw==
|
||||
|
||||
chain-function@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.1.tgz#c63045e5b4b663fb86f1c6e186adaf1de402a1cc"
|
||||
integrity sha512-SxltgMwL9uCko5/ZCLiyG2B7R9fY4pDZUw7hJ4MhirdjBLosoDqkWABi3XMucddHdLiFJMb7PD2MZifZriuMTg==
|
||||
|
||||
chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
|
@ -2947,13 +2942,6 @@ doctrine@^3.0.0:
|
|||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
dom-helpers@^3.2.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8"
|
||||
integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
|
||||
dom-serializer@0, dom-serializer@^0.2.1:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
|
||||
|
@ -6775,7 +6763,7 @@ prop-types-exact@^1.2.0:
|
|||
object.assign "^4.1.0"
|
||||
reflect.ownkeys "^0.2.0"
|
||||
|
||||
prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||
prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||
version "15.7.2"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
||||
|
@ -6958,13 +6946,6 @@ rc@1.2.8, rc@^1.2.8:
|
|||
minimist "^1.2.0"
|
||||
strip-json-comments "~2.0.1"
|
||||
|
||||
react-addons-css-transition-group@15.6.2:
|
||||
version "15.6.2"
|
||||
resolved "https://registry.yarnpkg.com/react-addons-css-transition-group/-/react-addons-css-transition-group-15.6.2.tgz#9e4376bcf40b5217d14ec68553081cee4b08a6d6"
|
||||
integrity sha1-nkN2vPQLUhfRTsaFUwgc7ksIptY=
|
||||
dependencies:
|
||||
react-transition-group "^1.2.0"
|
||||
|
||||
react-beautiful-dnd@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-4.0.1.tgz#3b0a49bf6be75af351176c904f012611dd292b81"
|
||||
|
@ -7052,17 +7033,6 @@ react-test-renderer@^16.0.0-0, react-test-renderer@^16.9.0:
|
|||
react-is "^16.8.6"
|
||||
scheduler "^0.19.1"
|
||||
|
||||
react-transition-group@^1.2.0:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-1.2.1.tgz#e11f72b257f921b213229a774df46612346c7ca6"
|
||||
integrity sha512-CWaL3laCmgAFdxdKbhhps+c0HRGF4c+hdM4H23+FI1QBNUyx/AMeIJGWorehPNSaKnQNOAxL7PQmqMu78CDj3Q==
|
||||
dependencies:
|
||||
chain-function "^1.0.0"
|
||||
dom-helpers "^3.2.0"
|
||||
loose-envify "^1.3.1"
|
||||
prop-types "^15.5.6"
|
||||
warning "^3.0.0"
|
||||
|
||||
react@^16.9.0:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
|
||||
|
@ -8803,13 +8773,6 @@ walker@^1.0.7, walker@~1.0.5:
|
|||
dependencies:
|
||||
makeerror "1.0.x"
|
||||
|
||||
warning@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c"
|
||||
integrity sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=
|
||||
dependencies:
|
||||
loose-envify "^1.0.0"
|
||||
|
||||
watchpack@^1.6.0:
|
||||
version "1.6.1"
|
||||
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.1.tgz#280da0a8718592174010c078c7585a74cd8cd0e2"
|
||||
|
|
Loading…
Reference in a new issue