Merge branch 'develop' into travis/sso-uia
This commit is contained in:
commit
b35356833f
51 changed files with 500 additions and 159 deletions
156
CHANGELOG.md
156
CHANGELOG.md
|
@ -1,3 +1,159 @@
|
||||||
|
Changes in [2.3.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.3.0) (2020-03-30)
|
||||||
|
===================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.3.0-rc.1...v2.3.0)
|
||||||
|
|
||||||
|
* Upgrade JS SDK to 5.2.0
|
||||||
|
|
||||||
|
Changes in [2.3.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.3.0-rc.1) (2020-03-26)
|
||||||
|
=============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.2.3...v2.3.0-rc.1)
|
||||||
|
|
||||||
|
* Upgrade JS SDK to 5.2.0-rc.1
|
||||||
|
* Add a flag to control whether cross-signing signatures are trusted
|
||||||
|
[\#4277](https://github.com/matrix-org/matrix-react-sdk/pull/4277)
|
||||||
|
* Update from Weblate
|
||||||
|
[\#4282](https://github.com/matrix-org/matrix-react-sdk/pull/4282)
|
||||||
|
* Update copy on SSSS symmetric upgrade toast
|
||||||
|
[\#4281](https://github.com/matrix-org/matrix-react-sdk/pull/4281)
|
||||||
|
* Wait for SSSS upgrade to complete
|
||||||
|
[\#4270](https://github.com/matrix-org/matrix-react-sdk/pull/4270)
|
||||||
|
* Update cross-signing verification copy and fix i18n
|
||||||
|
[\#4278](https://github.com/matrix-org/matrix-react-sdk/pull/4278)
|
||||||
|
* Fix soft-crash on bad permalinks
|
||||||
|
[\#4280](https://github.com/matrix-org/matrix-react-sdk/pull/4280)
|
||||||
|
* Fix: make self-verification wait for incoming request
|
||||||
|
[\#4267](https://github.com/matrix-org/matrix-react-sdk/pull/4267)
|
||||||
|
* Fall back to non-standard persisted api for Safari
|
||||||
|
[\#4272](https://github.com/matrix-org/matrix-react-sdk/pull/4272)
|
||||||
|
* Respond to backup key sharing requests
|
||||||
|
[\#4275](https://github.com/matrix-org/matrix-react-sdk/pull/4275)
|
||||||
|
* Log and display secret sharing cache state
|
||||||
|
[\#4268](https://github.com/matrix-org/matrix-react-sdk/pull/4268)
|
||||||
|
* Support sending config and ready events to capable widgets (Jitsi)
|
||||||
|
[\#4266](https://github.com/matrix-org/matrix-react-sdk/pull/4266)
|
||||||
|
* If cached keys are present in the key backup dialog, use them
|
||||||
|
[\#4273](https://github.com/matrix-org/matrix-react-sdk/pull/4273)
|
||||||
|
* Fix formatbar not hidden on highlighted message sent
|
||||||
|
[\#4265](https://github.com/matrix-org/matrix-react-sdk/pull/4265)
|
||||||
|
* Support Jitsi conferences sent/received on Riot Mobile and older Riot Webs
|
||||||
|
[\#4252](https://github.com/matrix-org/matrix-react-sdk/pull/4252)
|
||||||
|
* Use unified function to check cross-signing is ready
|
||||||
|
[\#4263](https://github.com/matrix-org/matrix-react-sdk/pull/4263)
|
||||||
|
* Migrate SSSS to symmetric
|
||||||
|
[\#4224](https://github.com/matrix-org/matrix-react-sdk/pull/4224)
|
||||||
|
* Migration to symmetric SSSS
|
||||||
|
[\#4242](https://github.com/matrix-org/matrix-react-sdk/pull/4242)
|
||||||
|
* Always display verification request toasts on top
|
||||||
|
[\#4262](https://github.com/matrix-org/matrix-react-sdk/pull/4262)
|
||||||
|
* Fix: assume SAS is supported when starting request with .start
|
||||||
|
[\#4249](https://github.com/matrix-org/matrix-react-sdk/pull/4249)
|
||||||
|
* Fix logout when Olm failed to load.
|
||||||
|
[\#4261](https://github.com/matrix-org/matrix-react-sdk/pull/4261)
|
||||||
|
* Improve naming of Jitsi conferences
|
||||||
|
[\#4251](https://github.com/matrix-org/matrix-react-sdk/pull/4251)
|
||||||
|
* Handle matrix.to user permalink in-room rather than solo
|
||||||
|
[\#4245](https://github.com/matrix-org/matrix-react-sdk/pull/4245)
|
||||||
|
* Fix: filter room list (again) by canonical and alternative aliases
|
||||||
|
[\#4260](https://github.com/matrix-org/matrix-react-sdk/pull/4260)
|
||||||
|
* EventIndex: Add some logging to the file panel populating.
|
||||||
|
[\#4250](https://github.com/matrix-org/matrix-react-sdk/pull/4250)
|
||||||
|
* Update from Weblate
|
||||||
|
[\#4259](https://github.com/matrix-org/matrix-react-sdk/pull/4259)
|
||||||
|
* Migrate RoomView to React Contexts in the hope for better temporal stability
|
||||||
|
[\#4258](https://github.com/matrix-org/matrix-react-sdk/pull/4258)
|
||||||
|
* Update WidgetUtils.js fix Jitsi path
|
||||||
|
[\#4256](https://github.com/matrix-org/matrix-react-sdk/pull/4256)
|
||||||
|
* Fix local jitsi build url fail and missing argument
|
||||||
|
[\#4255](https://github.com/matrix-org/matrix-react-sdk/pull/4255)
|
||||||
|
* Add shortcut CmdOrCtrl+. to toggle right panel
|
||||||
|
[\#4244](https://github.com/matrix-org/matrix-react-sdk/pull/4244)
|
||||||
|
* Improve Keyboard Shortcuts. Add alt-arrows & alt-shift-arrows
|
||||||
|
[\#4241](https://github.com/matrix-org/matrix-react-sdk/pull/4241)
|
||||||
|
* Bring back legacy verification by comparing public device keys
|
||||||
|
[\#4240](https://github.com/matrix-org/matrix-react-sdk/pull/4240)
|
||||||
|
* Searching: Return an empty result if the search term is an empty string.
|
||||||
|
[\#4248](https://github.com/matrix-org/matrix-react-sdk/pull/4248)
|
||||||
|
* Break continuation on showHiddenEvents-rendered events
|
||||||
|
[\#4247](https://github.com/matrix-org/matrix-react-sdk/pull/4247)
|
||||||
|
* Watch for show-RR settings changes, use room-specific and fix margins
|
||||||
|
[\#4246](https://github.com/matrix-org/matrix-react-sdk/pull/4246)
|
||||||
|
* Register Mac electron specific Cmd+, shortcut to User Settings
|
||||||
|
[\#4243](https://github.com/matrix-org/matrix-react-sdk/pull/4243)
|
||||||
|
* Use a local wrapper for Jitsi calls
|
||||||
|
[\#4234](https://github.com/matrix-org/matrix-react-sdk/pull/4234)
|
||||||
|
* Invite Dialog fixes
|
||||||
|
[\#4233](https://github.com/matrix-org/matrix-react-sdk/pull/4233)
|
||||||
|
* RoomPreviewBar word-break the sender name too
|
||||||
|
[\#4239](https://github.com/matrix-org/matrix-react-sdk/pull/4239)
|
||||||
|
* Report to the user when a key signature upload fails
|
||||||
|
[\#4229](https://github.com/matrix-org/matrix-react-sdk/pull/4229)
|
||||||
|
* pre-send megolm keys when possible when a user starts typing
|
||||||
|
[\#4235](https://github.com/matrix-org/matrix-react-sdk/pull/4235)
|
||||||
|
* we don't do mx_fadable anymore so get rid of broken RightPanel disabling
|
||||||
|
[\#4238](https://github.com/matrix-org/matrix-react-sdk/pull/4238)
|
||||||
|
* Fix left left panel overflowing vertically
|
||||||
|
[\#4237](https://github.com/matrix-org/matrix-react-sdk/pull/4237)
|
||||||
|
* Fix custom tags causing left panel to over-expand
|
||||||
|
[\#4236](https://github.com/matrix-org/matrix-react-sdk/pull/4236)
|
||||||
|
* Add Keyboard shortcuts dialog
|
||||||
|
[\#4231](https://github.com/matrix-org/matrix-react-sdk/pull/4231)
|
||||||
|
* Don't use buildkite agent to upload logs
|
||||||
|
[\#4232](https://github.com/matrix-org/matrix-react-sdk/pull/4232)
|
||||||
|
* Remove Gemini Scrollbars
|
||||||
|
[\#4217](https://github.com/matrix-org/matrix-react-sdk/pull/4217)
|
||||||
|
* Room Directory Explore Servers redesign
|
||||||
|
[\#4209](https://github.com/matrix-org/matrix-react-sdk/pull/4209)
|
||||||
|
* Fix redo keyboard shortcut on macOS
|
||||||
|
[\#4110](https://github.com/matrix-org/matrix-react-sdk/pull/4110)
|
||||||
|
* Fix: ensure local state for aliases doesn't get garbled up
|
||||||
|
[\#4230](https://github.com/matrix-org/matrix-react-sdk/pull/4230)
|
||||||
|
* Rename 'jump to bottom' to avoid ublock block
|
||||||
|
[\#4208](https://github.com/matrix-org/matrix-react-sdk/pull/4208)
|
||||||
|
* Restore key backup in background after complete security
|
||||||
|
[\#4225](https://github.com/matrix-org/matrix-react-sdk/pull/4225)
|
||||||
|
* Fix key backup trust text for cross-signing
|
||||||
|
[\#4223](https://github.com/matrix-org/matrix-react-sdk/pull/4223)
|
||||||
|
* Add default on config setting to control call button in composer
|
||||||
|
[\#4227](https://github.com/matrix-org/matrix-react-sdk/pull/4227)
|
||||||
|
* Fix: make alternative addresses UX less confusing
|
||||||
|
[\#4221](https://github.com/matrix-org/matrix-react-sdk/pull/4221)
|
||||||
|
* Wait for verification request on login
|
||||||
|
[\#4222](https://github.com/matrix-org/matrix-react-sdk/pull/4222)
|
||||||
|
* EventIndex: Add support to delete events from the index.
|
||||||
|
[\#4204](https://github.com/matrix-org/matrix-react-sdk/pull/4204)
|
||||||
|
* EventIndex: Remove a checkpoint if the HTTP request returns a 403.
|
||||||
|
[\#4214](https://github.com/matrix-org/matrix-react-sdk/pull/4214)
|
||||||
|
* Move to composer when typing letters with Shift held
|
||||||
|
[\#4216](https://github.com/matrix-org/matrix-react-sdk/pull/4216)
|
||||||
|
* Wrap large room names when previewing them
|
||||||
|
[\#4213](https://github.com/matrix-org/matrix-react-sdk/pull/4213)
|
||||||
|
* Rename Review Devices to Review Sessions
|
||||||
|
[\#4219](https://github.com/matrix-org/matrix-react-sdk/pull/4219)
|
||||||
|
* Fix typo in tabIndex to make React happy
|
||||||
|
[\#4215](https://github.com/matrix-org/matrix-react-sdk/pull/4215)
|
||||||
|
* Proof of concept for custom theme adding
|
||||||
|
[\#4148](https://github.com/matrix-org/matrix-react-sdk/pull/4148)
|
||||||
|
* Remove stuff that yarn install doesn't think we need
|
||||||
|
[\#4205](https://github.com/matrix-org/matrix-react-sdk/pull/4205)
|
||||||
|
* Declare jsx in tsconfig for IDEs
|
||||||
|
[\#4207](https://github.com/matrix-org/matrix-react-sdk/pull/4207)
|
||||||
|
* Fix: best-effort to join room without canonical alias over federation from
|
||||||
|
room directory
|
||||||
|
[\#4210](https://github.com/matrix-org/matrix-react-sdk/pull/4210)
|
||||||
|
* Test for cross-signing homeserver support during login, toasts
|
||||||
|
[\#4206](https://github.com/matrix-org/matrix-react-sdk/pull/4206)
|
||||||
|
* Send verification request to a single device in a way compatible with non-
|
||||||
|
cross-signing
|
||||||
|
[\#4202](https://github.com/matrix-org/matrix-react-sdk/pull/4202)
|
||||||
|
* Fixes for removing local alias
|
||||||
|
[\#4199](https://github.com/matrix-org/matrix-react-sdk/pull/4199)
|
||||||
|
* yarn upgrade
|
||||||
|
[\#4201](https://github.com/matrix-org/matrix-react-sdk/pull/4201)
|
||||||
|
* Support TypeScript for React components
|
||||||
|
[\#4203](https://github.com/matrix-org/matrix-react-sdk/pull/4203)
|
||||||
|
* When room name is changed, show both the old and new name
|
||||||
|
[\#4183](https://github.com/matrix-org/matrix-react-sdk/pull/4183)
|
||||||
|
|
||||||
Changes in [2.2.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.2.3) (2020-03-17)
|
Changes in [2.2.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.2.3) (2020-03-17)
|
||||||
===================================================================================================
|
===================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.2.3-rc.1...v2.2.3)
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.2.3-rc.1...v2.2.3)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "2.2.3",
|
"version": "2.3.0",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
@ -14,28 +14,31 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_AutoHideScrollbar {
|
// make any scrollbar grey and thin
|
||||||
overflow-x: hidden;
|
html {
|
||||||
overflow-y: auto;
|
scrollbar-color: $scrollbar-thumb-color $scrollbar-track-color;
|
||||||
overflow-y: overlay; // where supported
|
|
||||||
-ms-overflow-style: -ms-autohiding-scrollbar;
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
height: 6px;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
}
|
||||||
|
// scrollbar-width is not inherited (but -color is, why?!),
|
||||||
&::-webkit-scrollbar-thumb {
|
// so declare it on every element
|
||||||
border-radius: 3px;
|
* {
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollbar-color: transparent transparent;
|
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
background-color: $scrollbar-track-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: $scrollbar-thumb-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make auto-hide scrollbars not transparent again on hover
|
||||||
.mx_AutoHideScrollbar:hover {
|
.mx_AutoHideScrollbar:hover {
|
||||||
|
scrollbar-color: $scrollbar-thumb-color $scrollbar-track-color;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
background-color: $scrollbar-track-color;
|
background-color: $scrollbar-track-color;
|
||||||
}
|
}
|
||||||
|
@ -43,5 +46,22 @@ limitations under the License.
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
background-color: $scrollbar-thumb-color;
|
background-color: $scrollbar-thumb-color;
|
||||||
}
|
}
|
||||||
scrollbar-color: $scrollbar-thumb-color $scrollbar-track-color;
|
}
|
||||||
|
|
||||||
|
// make scrollbars transparent for autohide scrollbars
|
||||||
|
.mx_AutoHideScrollbar {
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-y: overlay; // where supported
|
||||||
|
-ms-overflow-style: -ms-autohiding-scrollbar;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar-color: transparent transparent;
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,7 +137,7 @@ limitations under the License.
|
||||||
top: -8px;
|
top: -8px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-color: $neutral-badge-color;
|
background-color: $neutral-badge-color;
|
||||||
color: #ffffff;
|
color: #000;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -430,7 +430,7 @@ async function _startCallApp(roomId, type) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const confId = `JitsiConference_${generateHumanReadableId()}`;
|
const confId = `JitsiConference${generateHumanReadableId()}`;
|
||||||
const jitsiDomain = SdkConfig.get()['jitsi']['preferredDomain'];
|
const jitsiDomain = SdkConfig.get()['jitsi']['preferredDomain'];
|
||||||
|
|
||||||
let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl();
|
let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl();
|
||||||
|
|
|
@ -216,19 +216,19 @@ export async function promptForBackupPassphrase() {
|
||||||
*
|
*
|
||||||
* @param {Function} [func] An operation to perform once secret storage has been
|
* @param {Function} [func] An operation to perform once secret storage has been
|
||||||
* bootstrapped. Optional.
|
* bootstrapped. Optional.
|
||||||
* @param {bool} [force] Reset secret storage even if it's already set up
|
* @param {bool} [forceReset] Reset secret storage even if it's already set up
|
||||||
*/
|
*/
|
||||||
export async function accessSecretStorage(func = async () => { }, force = false) {
|
export async function accessSecretStorage(func = async () => { }, forceReset = false) {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
secretStorageBeingAccessed = true;
|
secretStorageBeingAccessed = true;
|
||||||
try {
|
try {
|
||||||
if (!await cli.hasSecretStorageKey() || force) {
|
if (!await cli.hasSecretStorageKey() || forceReset) {
|
||||||
// This dialog calls bootstrap itself after guiding the user through
|
// This dialog calls bootstrap itself after guiding the user through
|
||||||
// passphrase creation.
|
// passphrase creation.
|
||||||
const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '',
|
const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '',
|
||||||
import("./async-components/views/dialogs/secretstorage/CreateSecretStorageDialog"),
|
import("./async-components/views/dialogs/secretstorage/CreateSecretStorageDialog"),
|
||||||
{
|
{
|
||||||
force,
|
force: forceReset,
|
||||||
},
|
},
|
||||||
null, /* priority = */ false, /* static = */ true,
|
null, /* priority = */ false, /* static = */ true,
|
||||||
);
|
);
|
||||||
|
|
|
@ -118,6 +118,11 @@ const shortcuts: Record<Categories, IShortcut[]> = {
|
||||||
key: Key.ARROW_DOWN,
|
key: Key.ARROW_DOWN,
|
||||||
}],
|
}],
|
||||||
description: _td("Navigate composer history"),
|
description: _td("Navigate composer history"),
|
||||||
|
}, {
|
||||||
|
keybinds: [{
|
||||||
|
key: Key.ESCAPE,
|
||||||
|
}],
|
||||||
|
description: _td("Cancel replying to a message"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -168,7 +168,6 @@ export default class ManageEventIndexDialog extends React.Component {
|
||||||
totalRooms: formatCountLong(this.state.roomCount),
|
totalRooms: formatCountLong(this.state.roomCount),
|
||||||
})} <br />
|
})} <br />
|
||||||
<Field
|
<Field
|
||||||
id={"crawlerSleepTimeMs"}
|
|
||||||
label={_t('Message downloading sleep time(ms)')}
|
label={_t('Message downloading sleep time(ms)')}
|
||||||
type='number'
|
type='number'
|
||||||
value={this.state.crawlerSleepTime}
|
value={this.state.crawlerSleepTime}
|
||||||
|
|
|
@ -412,7 +412,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
<div>{_t("Enter your account password to confirm the upgrade:")}</div>
|
<div>{_t("Enter your account password to confirm the upgrade:")}</div>
|
||||||
<div><Field
|
<div><Field
|
||||||
type="password"
|
type="password"
|
||||||
id="mx_CreateSecretStorage_accountPassword"
|
|
||||||
label={_t("Password")}
|
label={_t("Password")}
|
||||||
value={this.state.accountPassword}
|
value={this.state.accountPassword}
|
||||||
onChange={this._onAccountPasswordChange}
|
onChange={this._onAccountPasswordChange}
|
||||||
|
@ -497,7 +496,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
|
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
|
||||||
<Field
|
<Field
|
||||||
type="password"
|
type="password"
|
||||||
id="mx_CreateSecretStorageDialog_passPhraseField"
|
|
||||||
className="mx_CreateSecretStorageDialog_passPhraseField"
|
className="mx_CreateSecretStorageDialog_passPhraseField"
|
||||||
onChange={this._onPassPhraseChange}
|
onChange={this._onPassPhraseChange}
|
||||||
value={this.state.passPhrase}
|
value={this.state.passPhrase}
|
||||||
|
@ -574,7 +572,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
|
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
|
||||||
<Field
|
<Field
|
||||||
type="password"
|
type="password"
|
||||||
id="mx_CreateSecretStorageDialog_passPhraseField"
|
|
||||||
onChange={this._onPassPhraseConfirmChange}
|
onChange={this._onPassPhraseConfirmChange}
|
||||||
value={this.state.passPhraseConfirm}
|
value={this.state.passPhraseConfirm}
|
||||||
className="mx_CreateSecretStorageDialog_passPhraseField"
|
className="mx_CreateSecretStorageDialog_passPhraseField"
|
||||||
|
|
|
@ -657,6 +657,7 @@ export default createReactClass({
|
||||||
collapseLhs: true,
|
collapseLhs: true,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case 'focus_room_filter': // for CtrlOrCmd+K to work by expanding the left panel first
|
||||||
case 'show_left_panel':
|
case 'show_left_panel':
|
||||||
this.setState({
|
this.setState({
|
||||||
collapseLhs: false,
|
collapseLhs: false,
|
||||||
|
|
|
@ -55,6 +55,7 @@ import RightPanelStore from "../../stores/RightPanelStore";
|
||||||
import {haveTileForEvent} from "../views/rooms/EventTile";
|
import {haveTileForEvent} from "../views/rooms/EventTile";
|
||||||
import RoomContext from "../../contexts/RoomContext";
|
import RoomContext from "../../contexts/RoomContext";
|
||||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
|
import { shieldStatusForRoom } from '../../utils/ShieldUtils';
|
||||||
|
|
||||||
const DEBUG = false;
|
const DEBUG = false;
|
||||||
let debuglog = function() {};
|
let debuglog = function() {};
|
||||||
|
@ -817,40 +818,9 @@ export default createReactClass({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Duplication between here and _updateE2eStatus in RoomTile
|
|
||||||
/* At this point, the user has encryption on and cross-signing on */
|
/* At this point, the user has encryption on and cross-signing on */
|
||||||
const e2eMembers = await room.getEncryptionTargetMembers();
|
|
||||||
const verified = [];
|
|
||||||
const unverified = [];
|
|
||||||
e2eMembers.map(({userId}) => userId)
|
|
||||||
.filter((userId) => userId !== this.context.getUserId())
|
|
||||||
.forEach((userId) => {
|
|
||||||
(this.context.checkUserTrust(userId).isCrossSigningVerified() ?
|
|
||||||
verified : unverified).push(userId);
|
|
||||||
});
|
|
||||||
|
|
||||||
debuglog("e2e verified", verified, "unverified", unverified);
|
|
||||||
|
|
||||||
/* Check all verified user devices. */
|
|
||||||
/* Don't alarm if no other users are verified */
|
|
||||||
const targets = (verified.length > 0) ? [...verified, this.context.getUserId()] : verified;
|
|
||||||
for (const userId of targets) {
|
|
||||||
const devices = await this.context.getStoredDevicesForUser(userId);
|
|
||||||
const anyDeviceNotVerified = devices.some(({deviceId}) => {
|
|
||||||
return !this.context.checkDeviceTrust(userId, deviceId).isVerified();
|
|
||||||
});
|
|
||||||
if (anyDeviceNotVerified) {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
e2eStatus: "warning",
|
e2eStatus: await shieldStatusForRoom(this.context, room),
|
||||||
});
|
|
||||||
debuglog("e2e status set to warning as not all users trust all of their sessions." +
|
|
||||||
" Aborted on user", userId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
e2eStatus: unverified.length === 0 ? "verified" : "normal",
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import * as React from "react";
|
||||||
import {_t} from '../../languageHandler';
|
import {_t} from '../../languageHandler';
|
||||||
import * as PropTypes from "prop-types";
|
import * as PropTypes from "prop-types";
|
||||||
import * as sdk from "../../index";
|
import * as sdk from "../../index";
|
||||||
|
import AutoHideScrollbar from './AutoHideScrollbar';
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -113,9 +114,9 @@ export default class TabbedView extends React.Component<IProps, IState> {
|
||||||
private _renderTabPanel(tab: Tab): React.ReactNode {
|
private _renderTabPanel(tab: Tab): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<div className="mx_TabbedView_tabPanel" key={"mx_tabpanel_" + tab.label}>
|
<div className="mx_TabbedView_tabPanel" key={"mx_tabpanel_" + tab.label}>
|
||||||
<div className='mx_TabbedView_tabPanelContent'>
|
<AutoHideScrollbar className='mx_TabbedView_tabPanelContent'>
|
||||||
{tab.body}
|
{tab.body}
|
||||||
</div>
|
</AutoHideScrollbar>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -296,7 +296,6 @@ export default createReactClass({
|
||||||
<form onSubmit={this.onSubmitForm}>
|
<form onSubmit={this.onSubmitForm}>
|
||||||
<div className="mx_AuthBody_fieldRow">
|
<div className="mx_AuthBody_fieldRow">
|
||||||
<Field
|
<Field
|
||||||
id="mx_ForgotPassword_email"
|
|
||||||
name="reset_email" // define a name so browser's password autofill gets less confused
|
name="reset_email" // define a name so browser's password autofill gets less confused
|
||||||
type="text"
|
type="text"
|
||||||
label={_t('Email')}
|
label={_t('Email')}
|
||||||
|
@ -307,7 +306,6 @@ export default createReactClass({
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_AuthBody_fieldRow">
|
<div className="mx_AuthBody_fieldRow">
|
||||||
<Field
|
<Field
|
||||||
id="mx_ForgotPassword_password"
|
|
||||||
name="reset_password"
|
name="reset_password"
|
||||||
type="password"
|
type="password"
|
||||||
label={_t('Password')}
|
label={_t('Password')}
|
||||||
|
@ -315,7 +313,6 @@ export default createReactClass({
|
||||||
onChange={this.onInputChanged.bind(this, "password")}
|
onChange={this.onInputChanged.bind(this, "password")}
|
||||||
/>
|
/>
|
||||||
<Field
|
<Field
|
||||||
id="mx_ForgotPassword_passwordConfirm"
|
|
||||||
name="reset_password_confirm"
|
name="reset_password_confirm"
|
||||||
type="password"
|
type="password"
|
||||||
label={_t('Confirm')}
|
label={_t('Confirm')}
|
||||||
|
|
|
@ -213,7 +213,6 @@ export default class SoftLogout extends React.Component {
|
||||||
<p>{introText}</p>
|
<p>{introText}</p>
|
||||||
{error}
|
{error}
|
||||||
<Field
|
<Field
|
||||||
id="softlogout_password"
|
|
||||||
type="password"
|
type="password"
|
||||||
label={_t("Password")}
|
label={_t("Password")}
|
||||||
onChange={this.onPasswordChange}
|
onChange={this.onPasswordChange}
|
||||||
|
|
|
@ -145,7 +145,6 @@ export const PasswordAuthEntry = createReactClass({
|
||||||
<p>{ _t("Confirm your identity by entering your account password below.") }</p>
|
<p>{ _t("Confirm your identity by entering your account password below.") }</p>
|
||||||
<form onSubmit={this._onSubmit} className="mx_InteractiveAuthEntryComponents_passwordSection">
|
<form onSubmit={this._onSubmit} className="mx_InteractiveAuthEntryComponents_passwordSection">
|
||||||
<Field
|
<Field
|
||||||
id="mx_InteractiveAuthEntryComponents_password"
|
|
||||||
className={passwordBoxClass}
|
className={passwordBoxClass}
|
||||||
type="password"
|
type="password"
|
||||||
name="passwordField"
|
name="passwordField"
|
||||||
|
|
|
@ -106,7 +106,8 @@ export default class ModularServerConfig extends ServerConfig {
|
||||||
)}
|
)}
|
||||||
<form onSubmit={this.onSubmit} autoComplete="off" action={null}>
|
<form onSubmit={this.onSubmit} autoComplete="off" action={null}>
|
||||||
<div className="mx_ServerConfig_fields">
|
<div className="mx_ServerConfig_fields">
|
||||||
<Field id="mx_ServerConfig_hsUrl"
|
<Field
|
||||||
|
id="mx_ServerConfig_hsUrl"
|
||||||
label={_t("Server Name")}
|
label={_t("Server Name")}
|
||||||
placeholder={this.props.serverConfig.hsUrl}
|
placeholder={this.props.serverConfig.hsUrl}
|
||||||
value={this.state.hsUrl}
|
value={this.state.hsUrl}
|
||||||
|
|
|
@ -193,7 +193,6 @@ export default class PasswordLogin extends React.Component {
|
||||||
classes.error = this.props.loginIncorrect && !this.state.username;
|
classes.error = this.props.loginIncorrect && !this.state.username;
|
||||||
return <Field
|
return <Field
|
||||||
className={classNames(classes)}
|
className={classNames(classes)}
|
||||||
id="mx_PasswordLogin_email"
|
|
||||||
name="username" // make it a little easier for browser's remember-password
|
name="username" // make it a little easier for browser's remember-password
|
||||||
key="email_input"
|
key="email_input"
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -209,7 +208,6 @@ export default class PasswordLogin extends React.Component {
|
||||||
classes.error = this.props.loginIncorrect && !this.state.username;
|
classes.error = this.props.loginIncorrect && !this.state.username;
|
||||||
return <Field
|
return <Field
|
||||||
className={classNames(classes)}
|
className={classNames(classes)}
|
||||||
id="mx_PasswordLogin_username"
|
|
||||||
name="username" // make it a little easier for browser's remember-password
|
name="username" // make it a little easier for browser's remember-password
|
||||||
key="username_input"
|
key="username_input"
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -233,7 +231,6 @@ export default class PasswordLogin extends React.Component {
|
||||||
|
|
||||||
return <Field
|
return <Field
|
||||||
className={classNames(classes)}
|
className={classNames(classes)}
|
||||||
id="mx_PasswordLogin_phoneNumber"
|
|
||||||
name="phoneNumber"
|
name="phoneNumber"
|
||||||
key="phone_input"
|
key="phone_input"
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -290,7 +287,6 @@ export default class PasswordLogin extends React.Component {
|
||||||
<div className="mx_Login_type_container">
|
<div className="mx_Login_type_container">
|
||||||
<label className="mx_Login_type_label">{ _t('Sign in with') }</label>
|
<label className="mx_Login_type_label">{ _t('Sign in with') }</label>
|
||||||
<Field
|
<Field
|
||||||
id="mx_PasswordLogin_type"
|
|
||||||
element="select"
|
element="select"
|
||||||
value={this.state.loginType}
|
value={this.state.loginType}
|
||||||
onChange={this.onLoginTypeChange}
|
onChange={this.onLoginTypeChange}
|
||||||
|
@ -328,7 +324,6 @@ export default class PasswordLogin extends React.Component {
|
||||||
{loginField}
|
{loginField}
|
||||||
<Field
|
<Field
|
||||||
className={pwFieldClass}
|
className={pwFieldClass}
|
||||||
id="mx_PasswordLogin_password"
|
|
||||||
type="password"
|
type="password"
|
||||||
name="password"
|
name="password"
|
||||||
label={_t('Password')}
|
label={_t('Password')}
|
||||||
|
|
|
@ -470,7 +470,6 @@ export default createReactClass({
|
||||||
_t("Email") :
|
_t("Email") :
|
||||||
_t("Email (optional)");
|
_t("Email (optional)");
|
||||||
return <Field
|
return <Field
|
||||||
id="mx_RegistrationForm_email"
|
|
||||||
ref={field => this[FIELD_EMAIL] = field}
|
ref={field => this[FIELD_EMAIL] = field}
|
||||||
type="text"
|
type="text"
|
||||||
label={emailPlaceholder}
|
label={emailPlaceholder}
|
||||||
|
@ -524,7 +523,6 @@ export default createReactClass({
|
||||||
onOptionChange={this.onPhoneCountryChange}
|
onOptionChange={this.onPhoneCountryChange}
|
||||||
/>;
|
/>;
|
||||||
return <Field
|
return <Field
|
||||||
id="mx_RegistrationForm_phoneNumber"
|
|
||||||
ref={field => this[FIELD_PHONE_NUMBER] = field}
|
ref={field => this[FIELD_PHONE_NUMBER] = field}
|
||||||
type="text"
|
type="text"
|
||||||
label={phoneLabel}
|
label={phoneLabel}
|
||||||
|
|
|
@ -223,7 +223,8 @@ export default class ServerConfig extends React.PureComponent {
|
||||||
{sub}
|
{sub}
|
||||||
</a>,
|
</a>,
|
||||||
})}
|
})}
|
||||||
<Field id="mx_ServerConfig_hsUrl"
|
<Field
|
||||||
|
id="mx_ServerConfig_hsUrl"
|
||||||
label={_t("Homeserver URL")}
|
label={_t("Homeserver URL")}
|
||||||
placeholder={this.props.serverConfig.hsUrl}
|
placeholder={this.props.serverConfig.hsUrl}
|
||||||
value={this.state.hsUrl}
|
value={this.state.hsUrl}
|
||||||
|
@ -246,7 +247,7 @@ export default class ServerConfig extends React.PureComponent {
|
||||||
{sub}
|
{sub}
|
||||||
</a>,
|
</a>,
|
||||||
})}
|
})}
|
||||||
<Field id="mx_ServerConfig_isUrl"
|
<Field
|
||||||
label={_t("Identity Server URL")}
|
label={_t("Identity Server URL")}
|
||||||
placeholder={this.props.serverConfig.isUrl}
|
placeholder={this.props.serverConfig.isUrl}
|
||||||
value={this.state.isUrl || ''}
|
value={this.state.isUrl || ''}
|
||||||
|
|
|
@ -166,7 +166,6 @@ export default class BugReportDialog extends React.Component {
|
||||||
) }
|
) }
|
||||||
</b></p>
|
</b></p>
|
||||||
<Field
|
<Field
|
||||||
id="mx_BugReportDialog_issueUrl"
|
|
||||||
type="text"
|
type="text"
|
||||||
className="mx_BugReportDialog_field_input"
|
className="mx_BugReportDialog_field_input"
|
||||||
label={_t("GitHub issue")}
|
label={_t("GitHub issue")}
|
||||||
|
@ -175,7 +174,6 @@ export default class BugReportDialog extends React.Component {
|
||||||
placeholder="https://github.com/vector-im/riot-web/issues/..."
|
placeholder="https://github.com/vector-im/riot-web/issues/..."
|
||||||
/>
|
/>
|
||||||
<Field
|
<Field
|
||||||
id="mx_BugReportDialog_notes"
|
|
||||||
className="mx_BugReportDialog_field_input"
|
className="mx_BugReportDialog_field_input"
|
||||||
element="textarea"
|
element="textarea"
|
||||||
label={_t("Notes")}
|
label={_t("Notes")}
|
||||||
|
|
|
@ -174,7 +174,7 @@ export default createReactClass({
|
||||||
const domain = MatrixClientPeg.get().getDomain();
|
const domain = MatrixClientPeg.get().getDomain();
|
||||||
aliasField = (
|
aliasField = (
|
||||||
<div className="mx_CreateRoomDialog_aliasContainer">
|
<div className="mx_CreateRoomDialog_aliasContainer">
|
||||||
<RoomAliasField id="alias" ref={ref => this._aliasFieldRef = ref} onChange={this.onAliasChange} domain={domain} value={this.state.alias} />
|
<RoomAliasField ref={ref => this._aliasFieldRef = ref} onChange={this.onAliasChange} domain={domain} value={this.state.alias} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -188,8 +188,8 @@ export default createReactClass({
|
||||||
>
|
>
|
||||||
<form onSubmit={this.onOk} onKeyDown={this._onKeyDown}>
|
<form onSubmit={this.onOk} onKeyDown={this._onKeyDown}>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
<Field id="name" ref={ref => this._nameFieldRef = ref} label={ _t('Name') } onChange={this.onNameChange} onValidate={this.onNameValidate} value={this.state.name} className="mx_CreateRoomDialog_name" />
|
<Field ref={ref => this._nameFieldRef = ref} label={ _t('Name') } onChange={this.onNameChange} onValidate={this.onNameValidate} value={this.state.name} className="mx_CreateRoomDialog_name" />
|
||||||
<Field id="topic" label={ _t('Topic (optional)') } onChange={this.onTopicChange} value={this.state.topic} />
|
<Field label={ _t('Topic (optional)') } onChange={this.onTopicChange} value={this.state.topic} />
|
||||||
<LabelledToggleSwitch label={ _t("Make this room public")} onChange={this.onPublicChange} value={this.state.isPublic} />
|
<LabelledToggleSwitch label={ _t("Make this room public")} onChange={this.onPublicChange} value={this.state.isPublic} />
|
||||||
{ privateLabel }
|
{ privateLabel }
|
||||||
{ publicLabel }
|
{ publicLabel }
|
||||||
|
|
|
@ -174,7 +174,6 @@ export default class DeactivateAccountDialog extends React.Component {
|
||||||
|
|
||||||
<p>{ _t("To continue, please enter your password:") }</p>
|
<p>{ _t("To continue, please enter your password:") }</p>
|
||||||
<Field
|
<Field
|
||||||
id="mx_DeactivateAccountDialog_password"
|
|
||||||
type="password"
|
type="password"
|
||||||
label={_t('Password')}
|
label={_t('Password')}
|
||||||
onChange={this._onPasswordFieldChange}
|
onChange={this._onPasswordFieldChange}
|
||||||
|
|
|
@ -302,7 +302,7 @@ class FilteredList extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const TruncatedList = sdk.getComponent("elements.TruncatedList");
|
const TruncatedList = sdk.getComponent("elements.TruncatedList");
|
||||||
return <div>
|
return <div>
|
||||||
<Field id="DevtoolsDialog_FilteredList_filter" label={_t('Filter results')} autoFocus={true} size={64}
|
<Field label={_t('Filter results')} autoFocus={true} size={64}
|
||||||
type="text" autoComplete="off" value={this.props.query} onChange={this.onQuery}
|
type="text" autoComplete="off" value={this.props.query} onChange={this.onQuery}
|
||||||
className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query"
|
className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query"
|
||||||
// force re-render so that autoFocus is applied when this component is re-used
|
// force re-render so that autoFocus is applied when this component is re-used
|
||||||
|
|
|
@ -123,7 +123,6 @@ export default class ReportEventDialog extends PureComponent {
|
||||||
</p>
|
</p>
|
||||||
{adminMessage}
|
{adminMessage}
|
||||||
<Field
|
<Field
|
||||||
id="mx_ReportEventDialog_reason"
|
|
||||||
className="mx_ReportEventDialog_reason"
|
className="mx_ReportEventDialog_reason"
|
||||||
element="textarea"
|
element="textarea"
|
||||||
label={_t("Reason")}
|
label={_t("Reason")}
|
||||||
|
|
|
@ -116,7 +116,6 @@ export default createReactClass({
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Field
|
<Field
|
||||||
id="mx_TextInputDialog_field"
|
|
||||||
className="mx_TextInputDialog_input"
|
className="mx_TextInputDialog_input"
|
||||||
ref={this._field}
|
ref={this._field}
|
||||||
type="text"
|
type="text"
|
||||||
|
|
|
@ -121,7 +121,7 @@ export default class EditableItemList extends React.Component {
|
||||||
return (
|
return (
|
||||||
<form onSubmit={this._onItemAdded} autoComplete="off"
|
<form onSubmit={this._onItemAdded} autoComplete="off"
|
||||||
noValidate={true} className="mx_EditableItemList_newItem">
|
noValidate={true} className="mx_EditableItemList_newItem">
|
||||||
<Field id={`mx_EditableItemList_new_${this.props.id}`} label={this.props.placeholder} type="text"
|
<Field label={this.props.placeholder} type="text"
|
||||||
autoComplete="off" value={this.props.newItem || ""} onChange={this._onNewItemChanged}
|
autoComplete="off" value={this.props.newItem || ""} onChange={this._onNewItemChanged}
|
||||||
list={this.props.suggestionsListId} />
|
list={this.props.suggestionsListId} />
|
||||||
<AccessibleButton onClick={this._onItemAdded} kind="primary" type="submit">
|
<AccessibleButton onClick={this._onItemAdded} kind="primary" type="submit">
|
||||||
|
|
|
@ -23,10 +23,16 @@ import { debounce } from 'lodash';
|
||||||
// Invoke validation from user input (when typing, etc.) at most once every N ms.
|
// Invoke validation from user input (when typing, etc.) at most once every N ms.
|
||||||
const VALIDATION_THROTTLE_MS = 200;
|
const VALIDATION_THROTTLE_MS = 200;
|
||||||
|
|
||||||
|
const BASE_ID = "mx_Field";
|
||||||
|
let count = 1;
|
||||||
|
function getId() {
|
||||||
|
return `${BASE_ID}_${count++}`;
|
||||||
|
}
|
||||||
|
|
||||||
export default class Field extends React.PureComponent {
|
export default class Field extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
// The field's ID, which binds the input and label together.
|
// The field's ID, which binds the input and label together. Immutable.
|
||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string,
|
||||||
// The element to create. Defaults to "input".
|
// The element to create. Defaults to "input".
|
||||||
// To define options for a select, use <Field><option ... /></Field>
|
// To define options for a select, use <Field><option ... /></Field>
|
||||||
element: PropTypes.oneOf(["input", "select", "textarea"]),
|
element: PropTypes.oneOf(["input", "select", "textarea"]),
|
||||||
|
@ -63,13 +69,15 @@ export default class Field extends React.PureComponent {
|
||||||
// All other props pass through to the <input>.
|
// All other props pass through to the <input>.
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor(props) {
|
||||||
super();
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
valid: undefined,
|
valid: undefined,
|
||||||
feedback: undefined,
|
feedback: undefined,
|
||||||
focused: false,
|
focused: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.id = this.props.id || getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
onFocus = (ev) => {
|
onFocus = (ev) => {
|
||||||
|
@ -167,6 +175,7 @@ export default class Field extends React.PureComponent {
|
||||||
inputProps.type = inputProps.type || "text";
|
inputProps.type = inputProps.type || "text";
|
||||||
inputProps.ref = input => this.input = input;
|
inputProps.ref = input => this.input = input;
|
||||||
inputProps.placeholder = inputProps.placeholder || inputProps.label;
|
inputProps.placeholder = inputProps.placeholder || inputProps.label;
|
||||||
|
inputProps.id = this.id; // this overwrites the id from props
|
||||||
|
|
||||||
inputProps.onFocus = this.onFocus;
|
inputProps.onFocus = this.onFocus;
|
||||||
inputProps.onChange = this.onChange;
|
inputProps.onChange = this.onChange;
|
||||||
|
@ -211,7 +220,7 @@ export default class Field extends React.PureComponent {
|
||||||
return <div className={fieldClasses}>
|
return <div className={fieldClasses}>
|
||||||
{prefixContainer}
|
{prefixContainer}
|
||||||
{fieldInput}
|
{fieldInput}
|
||||||
<label htmlFor={this.props.id}>{this.props.label}</label>
|
<label htmlFor={this.id}>{this.props.label}</label>
|
||||||
{postfixContainer}
|
{postfixContainer}
|
||||||
{fieldTooltip}
|
{fieldTooltip}
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -132,7 +132,7 @@ export default createReactClass({
|
||||||
const label = typeof this.props.label === "undefined" ? _t("Power level") : this.props.label;
|
const label = typeof this.props.label === "undefined" ? _t("Power level") : this.props.label;
|
||||||
if (this.state.custom) {
|
if (this.state.custom) {
|
||||||
picker = (
|
picker = (
|
||||||
<Field id={`powerSelector_custom_${this.props.powerLevelKey}`} type="number"
|
<Field type="number"
|
||||||
label={label} max={this.props.maxValue}
|
label={label} max={this.props.maxValue}
|
||||||
onBlur={this.onCustomBlur} onKeyDown={this.onCustomKeyDown} onChange={this.onCustomChange}
|
onBlur={this.onCustomBlur} onKeyDown={this.onCustomKeyDown} onChange={this.onCustomChange}
|
||||||
value={String(this.state.customValue)} disabled={this.props.disabled} />
|
value={String(this.state.customValue)} disabled={this.props.disabled} />
|
||||||
|
@ -151,7 +151,7 @@ export default createReactClass({
|
||||||
});
|
});
|
||||||
|
|
||||||
picker = (
|
picker = (
|
||||||
<Field id={`powerSelector_notCustom_${this.props.powerLevelKey}`} element="select"
|
<Field element="select"
|
||||||
label={label} onChange={this.onSelectChange}
|
label={label} onChange={this.onSelectChange}
|
||||||
value={String(this.state.selectValue)} disabled={this.props.disabled}>
|
value={String(this.state.selectValue)} disabled={this.props.disabled}>
|
||||||
{options}
|
{options}
|
||||||
|
|
|
@ -23,7 +23,6 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||||
// Controlled form component wrapping Field for inputting a room alias scoped to a given domain
|
// Controlled form component wrapping Field for inputting a room alias scoped to a given domain
|
||||||
export default class RoomAliasField extends React.PureComponent {
|
export default class RoomAliasField extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
id: PropTypes.string.isRequired,
|
|
||||||
domain: PropTypes.string.isRequired,
|
domain: PropTypes.string.isRequired,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
|
@ -50,7 +49,6 @@ export default class RoomAliasField extends React.PureComponent {
|
||||||
className="mx_RoomAliasField"
|
className="mx_RoomAliasField"
|
||||||
prefix={poundSign}
|
prefix={poundSign}
|
||||||
postfix={domain}
|
postfix={domain}
|
||||||
id={this.props.id}
|
|
||||||
ref={ref => this._fieldRef = ref}
|
ref={ref => this._fieldRef = ref}
|
||||||
onValidate={this._onValidate}
|
onValidate={this._onValidate}
|
||||||
placeholder={_t("e.g. my-room")}
|
placeholder={_t("e.g. my-room")}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
import * as recent from '../../../emojipicker/recent';
|
import * as recent from '../../../emojipicker/recent';
|
||||||
import {DATA_BY_CATEGORY, getEmojiFromUnicode} from "../../../emoji";
|
import {DATA_BY_CATEGORY, getEmojiFromUnicode} from "../../../emoji";
|
||||||
|
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||||
|
|
||||||
export const CATEGORY_HEADER_HEIGHT = 22;
|
export const CATEGORY_HEADER_HEIGHT = 22;
|
||||||
export const EMOJI_HEIGHT = 37;
|
export const EMOJI_HEIGHT = 37;
|
||||||
|
@ -214,7 +215,7 @@ class EmojiPicker extends React.Component {
|
||||||
<div className="mx_EmojiPicker">
|
<div className="mx_EmojiPicker">
|
||||||
<Header categories={this.categories} defaultCategory="recent" onAnchorClick={this.scrollToCategory} />
|
<Header categories={this.categories} defaultCategory="recent" onAnchorClick={this.scrollToCategory} />
|
||||||
<Search query={this.state.filter} onChange={this.onChangeFilter} />
|
<Search query={this.state.filter} onChange={this.onChangeFilter} />
|
||||||
<div className="mx_EmojiPicker_body" ref={this.bodyRef} onScroll={this.onScroll}>
|
<AutoHideScrollbar className="mx_EmojiPicker_body" wrappedRef={e => this.bodyRef.current = e} onScroll={this.onScroll}>
|
||||||
{this.categories.map(category => {
|
{this.categories.map(category => {
|
||||||
const emojis = this.memoizedDataByCategory[category.id];
|
const emojis = this.memoizedDataByCategory[category.id];
|
||||||
const categoryElement = (<Category key={category.id} id={category.id} name={category.name}
|
const categoryElement = (<Category key={category.id} id={category.id} name={category.name}
|
||||||
|
@ -226,7 +227,7 @@ class EmojiPicker extends React.Component {
|
||||||
heightBefore += height;
|
heightBefore += height;
|
||||||
return categoryElement;
|
return categoryElement;
|
||||||
})}
|
})}
|
||||||
</div>
|
</AutoHideScrollbar>
|
||||||
{this.state.previewEmoji || !this.props.showQuickReactions
|
{this.state.previewEmoji || !this.props.showQuickReactions
|
||||||
? <Preview emoji={this.state.previewEmoji} />
|
? <Preview emoji={this.state.previewEmoji} />
|
||||||
: <QuickReactions onClick={this.onClickEmoji} selectedEmojis={this.props.selectedEmojis} /> }
|
: <QuickReactions onClick={this.onClickEmoji} selectedEmojis={this.props.selectedEmojis} /> }
|
||||||
|
|
|
@ -62,7 +62,6 @@ class EditableAliasesList extends EditableItemList {
|
||||||
className="mx_EditableItemList_newItem"
|
className="mx_EditableItemList_newItem"
|
||||||
>
|
>
|
||||||
<RoomAliasField
|
<RoomAliasField
|
||||||
id={`mx_EditableItemList_new_${this.props.id}`}
|
|
||||||
ref={this._aliasField}
|
ref={this._aliasField}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
value={this.props.newItem || ""}
|
value={this.props.newItem || ""}
|
||||||
|
|
|
@ -155,7 +155,7 @@ export default class RoomProfileSettings extends React.Component {
|
||||||
onChange={this._onAvatarChanged} accept="image/*" />
|
onChange={this._onAvatarChanged} accept="image/*" />
|
||||||
<div className="mx_ProfileSettings_profile">
|
<div className="mx_ProfileSettings_profile">
|
||||||
<div className="mx_ProfileSettings_controls">
|
<div className="mx_ProfileSettings_controls">
|
||||||
<Field id="profileDisplayName" label={_t("Room Name")}
|
<Field label={_t("Room Name")}
|
||||||
type="text" value={this.state.displayName} autoComplete="off"
|
type="text" value={this.state.displayName} autoComplete="off"
|
||||||
onChange={this._onDisplayNameChanged} disabled={!this.state.canSetName} />
|
onChange={this._onDisplayNameChanged} disabled={!this.state.canSetName} />
|
||||||
<Field id="profileTopic" label={_t("Room Topic")} disabled={!this.state.canSetTopic}
|
<Field id="profileTopic" label={_t("Room Topic")} disabled={!this.state.canSetTopic}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import { _t } from '../../../languageHandler';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import RateLimitedFunc from '../../../ratelimitedfunc';
|
import RateLimitedFunc from '../../../ratelimitedfunc';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||||
|
|
||||||
|
|
||||||
export default createReactClass({
|
export default createReactClass({
|
||||||
|
@ -264,14 +265,14 @@ export default createReactClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes} style={style} >
|
<AutoHideScrollbar className={classes} style={style} >
|
||||||
{ stateViews }
|
{ stateViews }
|
||||||
{ appsDrawer }
|
{ appsDrawer }
|
||||||
{ fileDropTarget }
|
{ fileDropTarget }
|
||||||
{ callView }
|
{ callView }
|
||||||
{ conferenceCallNotification }
|
{ conferenceCallNotification }
|
||||||
{ this.props.children }
|
{ this.props.children }
|
||||||
</div>
|
</AutoHideScrollbar>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,6 +37,7 @@ import E2EIcon from './E2EIcon';
|
||||||
import InviteOnlyIcon from './InviteOnlyIcon';
|
import InviteOnlyIcon from './InviteOnlyIcon';
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
import rate_limited_func from '../../../ratelimitedfunc';
|
import rate_limited_func from '../../../ratelimitedfunc';
|
||||||
|
import { shieldStatusForRoom } from '../../../utils/ShieldUtils';
|
||||||
|
|
||||||
export default createReactClass({
|
export default createReactClass({
|
||||||
displayName: 'RoomTile',
|
displayName: 'RoomTile',
|
||||||
|
@ -154,35 +155,9 @@ export default createReactClass({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Duplication between here and _updateE2eStatus in RoomView
|
/* At this point, the user has encryption on and cross-signing on */
|
||||||
const e2eMembers = await this.props.room.getEncryptionTargetMembers();
|
|
||||||
const verified = [];
|
|
||||||
const unverified = [];
|
|
||||||
e2eMembers.map(({userId}) => userId)
|
|
||||||
.filter((userId) => userId !== cli.getUserId())
|
|
||||||
.forEach((userId) => {
|
|
||||||
(cli.checkUserTrust(userId).isCrossSigningVerified() ?
|
|
||||||
verified : unverified).push(userId);
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Check all verified user devices. */
|
|
||||||
/* Don't alarm if no other users are verified */
|
|
||||||
const targets = (verified.length > 0) ? [...verified, cli.getUserId()] : verified;
|
|
||||||
for (const userId of targets) {
|
|
||||||
const devices = await cli.getStoredDevicesForUser(userId);
|
|
||||||
const allDevicesVerified = devices.every(({deviceId}) => {
|
|
||||||
return cli.checkDeviceTrust(userId, deviceId).isVerified();
|
|
||||||
});
|
|
||||||
if (!allDevicesVerified) {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
e2eStatus: "warning",
|
e2eStatus: await shieldStatusForRoom(cli, this.props.room),
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
e2eStatus: unverified.length === 0 ? "verified" : "normal",
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -131,8 +131,13 @@ export default class SendMessageComposer extends React.Component {
|
||||||
this.onVerticalArrow(event, false);
|
this.onVerticalArrow(event, false);
|
||||||
} else if (this._prepareToEncrypt) {
|
} else if (this._prepareToEncrypt) {
|
||||||
this._prepareToEncrypt();
|
this._prepareToEncrypt();
|
||||||
|
} else if (event.key === Key.ESCAPE) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'reply_to_event',
|
||||||
|
event: null,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
onVerticalArrow(e, up) {
|
onVerticalArrow(e, up) {
|
||||||
// arrows from an initial-caret composer navigates recent messages to edit
|
// arrows from an initial-caret composer navigates recent messages to edit
|
||||||
|
|
|
@ -235,7 +235,7 @@ export default createReactClass({
|
||||||
if (!this.state.cachedPassword) {
|
if (!this.state.cachedPassword) {
|
||||||
currentPassword = (
|
currentPassword = (
|
||||||
<div className={rowClassName}>
|
<div className={rowClassName}>
|
||||||
<Field id="mx_ChangePassword_oldPassword"
|
<Field
|
||||||
type="password"
|
type="password"
|
||||||
label={_t('Current password')}
|
label={_t('Current password')}
|
||||||
value={this.state.oldPassword}
|
value={this.state.oldPassword}
|
||||||
|
@ -254,7 +254,6 @@ export default createReactClass({
|
||||||
{ currentPassword }
|
{ currentPassword }
|
||||||
<div className={rowClassName}>
|
<div className={rowClassName}>
|
||||||
<Field
|
<Field
|
||||||
id="mx_ChangePassword_newPassword"
|
|
||||||
type="password"
|
type="password"
|
||||||
label={passwordLabel}
|
label={passwordLabel}
|
||||||
value={this.state.newPassword}
|
value={this.state.newPassword}
|
||||||
|
@ -265,7 +264,6 @@ export default createReactClass({
|
||||||
</div>
|
</div>
|
||||||
<div className={rowClassName}>
|
<div className={rowClassName}>
|
||||||
<Field
|
<Field
|
||||||
id="mx_ChangePassword_newPasswordConfirm"
|
|
||||||
type="password"
|
type="password"
|
||||||
label={_t("Confirm password")}
|
label={_t("Confirm password")}
|
||||||
value={this.state.newPasswordConfirm}
|
value={this.state.newPasswordConfirm}
|
||||||
|
|
|
@ -108,12 +108,12 @@ export default class CrossSigningPanel extends React.PureComponent {
|
||||||
* 2. Access existing secret storage by requesting passphrase and accessing
|
* 2. Access existing secret storage by requesting passphrase and accessing
|
||||||
* cross-signing keys as needed.
|
* cross-signing keys as needed.
|
||||||
* 3. All keys are loaded and there's nothing to do.
|
* 3. All keys are loaded and there's nothing to do.
|
||||||
* @param {bool} [force] Bootstrap again even if keys already present
|
* @param {bool} [forceReset] Bootstrap again even if keys already present
|
||||||
*/
|
*/
|
||||||
_bootstrapSecureSecretStorage = async (force=false) => {
|
_bootstrapSecureSecretStorage = async (forceReset=false) => {
|
||||||
this.setState({ error: null });
|
this.setState({ error: null });
|
||||||
try {
|
try {
|
||||||
await accessSecretStorage(() => undefined, force);
|
await accessSecretStorage(() => undefined, forceReset);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.setState({ error: e });
|
this.setState({ error: e });
|
||||||
console.error("Error bootstrapping secret storage", e);
|
console.error("Error bootstrapping secret storage", e);
|
||||||
|
|
|
@ -153,7 +153,7 @@ export default class ProfileSettings extends React.Component {
|
||||||
{this.state.userId}
|
{this.state.userId}
|
||||||
{hostingSignup}
|
{hostingSignup}
|
||||||
</p>
|
</p>
|
||||||
<Field id="profileDisplayName" label={_t("Display Name")}
|
<Field label={_t("Display Name")}
|
||||||
type="text" value={this.state.displayName} autoComplete="off"
|
type="text" value={this.state.displayName} autoComplete="off"
|
||||||
onChange={this._onDisplayNameChanged} />
|
onChange={this._onDisplayNameChanged} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -403,8 +403,8 @@ export default class SetIdServer extends React.Component {
|
||||||
<span className="mx_SettingsTab_subsectionText">
|
<span className="mx_SettingsTab_subsectionText">
|
||||||
{bodyText}
|
{bodyText}
|
||||||
</span>
|
</span>
|
||||||
<Field label={_t("Enter a new identity server")}
|
<Field
|
||||||
id="mx_SetIdServer_idServer"
|
label={_t("Enter a new identity server")}
|
||||||
type="text"
|
type="text"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
placeholder={this.state.defaultIdServer}
|
placeholder={this.state.defaultIdServer}
|
||||||
|
|
|
@ -237,7 +237,7 @@ export default class EmailAddresses extends React.Component {
|
||||||
{existingEmailElements}
|
{existingEmailElements}
|
||||||
<form onSubmit={this._onAddClick} autoComplete="off"
|
<form onSubmit={this._onAddClick} autoComplete="off"
|
||||||
noValidate={true} className="mx_EmailAddresses_new">
|
noValidate={true} className="mx_EmailAddresses_new">
|
||||||
<Field id="mx_EmailAddressses_newEmailAddress"
|
<Field
|
||||||
type="text"
|
type="text"
|
||||||
label={_t("Email Address")}
|
label={_t("Email Address")}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
|
|
@ -232,7 +232,7 @@ export default class PhoneNumbers extends React.Component {
|
||||||
{this.state.verifyError}
|
{this.state.verifyError}
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={this._onContinueClick} autoComplete="off" noValidate={true}>
|
<form onSubmit={this._onContinueClick} autoComplete="off" noValidate={true}>
|
||||||
<Field id="mx_PhoneNumbers_newPhoneNumberCode"
|
<Field
|
||||||
type="text"
|
type="text"
|
||||||
label={_t("Verification code")}
|
label={_t("Verification code")}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
@ -262,7 +262,7 @@ export default class PhoneNumbers extends React.Component {
|
||||||
{existingPhoneElements}
|
{existingPhoneElements}
|
||||||
<form onSubmit={this._onAddClick} autoComplete="off" noValidate={true} className="mx_PhoneNumbers_new">
|
<form onSubmit={this._onAddClick} autoComplete="off" noValidate={true} className="mx_PhoneNumbers_new">
|
||||||
<div className="mx_PhoneNumbers_input">
|
<div className="mx_PhoneNumbers_input">
|
||||||
<Field id="mx_PhoneNumbers_newPhoneNumber"
|
<Field
|
||||||
type="text"
|
type="text"
|
||||||
label={_t("Phone Number")}
|
label={_t("Phone Number")}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
|
|
@ -208,7 +208,7 @@ export class PhoneNumber extends React.Component {
|
||||||
{this.state.verifyError}
|
{this.state.verifyError}
|
||||||
</span>
|
</span>
|
||||||
<form onSubmit={this.onContinueClick} autoComplete="off" noValidate={true}>
|
<form onSubmit={this.onContinueClick} autoComplete="off" noValidate={true}>
|
||||||
<Field id="mx_PhoneNumbers_newPhoneNumberCode"
|
<Field
|
||||||
type="text"
|
type="text"
|
||||||
label={_t("Verification code")}
|
label={_t("Verification code")}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
|
|
@ -422,7 +422,6 @@ export default class GeneralUserSettingsTab extends React.Component {
|
||||||
<Field
|
<Field
|
||||||
label={_t("Custom theme URL")}
|
label={_t("Custom theme URL")}
|
||||||
type='text'
|
type='text'
|
||||||
id='mx_GeneralUserSettingsTab_customThemeInput'
|
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
onChange={this._onCustomThemeChange}
|
onChange={this._onCustomThemeChange}
|
||||||
value={this.state.customThemeUrl}
|
value={this.state.customThemeUrl}
|
||||||
|
@ -448,7 +447,7 @@ export default class GeneralUserSettingsTab extends React.Component {
|
||||||
<div className="mx_SettingsTab_section mx_GeneralUserSettingsTab_themeSection">
|
<div className="mx_SettingsTab_section mx_GeneralUserSettingsTab_themeSection">
|
||||||
<span className="mx_SettingsTab_subheading">{_t("Theme")}</span>
|
<span className="mx_SettingsTab_subheading">{_t("Theme")}</span>
|
||||||
{systemThemeSection}
|
{systemThemeSection}
|
||||||
<Field id="theme" label={_t("Theme")} element="select"
|
<Field label={_t("Theme")} element="select"
|
||||||
value={this.state.theme} onChange={this._onThemeChange}
|
value={this.state.theme} onChange={this._onThemeChange}
|
||||||
disabled={this.state.useSystemTheme}
|
disabled={this.state.useSystemTheme}
|
||||||
>
|
>
|
||||||
|
|
|
@ -272,7 +272,6 @@ export default class MjolnirUserSettingsTab extends React.Component {
|
||||||
<div>
|
<div>
|
||||||
<form onSubmit={this._onAddPersonalRule} autoComplete="off">
|
<form onSubmit={this._onAddPersonalRule} autoComplete="off">
|
||||||
<Field
|
<Field
|
||||||
id="mx_MjolnirUserSettingsTab_personalAdd"
|
|
||||||
type="text"
|
type="text"
|
||||||
label={_t("Server or user ID to ignore")}
|
label={_t("Server or user ID to ignore")}
|
||||||
placeholder={_t("eg: @bot:* or example.org")}
|
placeholder={_t("eg: @bot:* or example.org")}
|
||||||
|
@ -305,7 +304,6 @@ export default class MjolnirUserSettingsTab extends React.Component {
|
||||||
<div>
|
<div>
|
||||||
<form onSubmit={this._onSubscribeList} autoComplete="off">
|
<form onSubmit={this._onSubscribeList} autoComplete="off">
|
||||||
<Field
|
<Field
|
||||||
id="mx_MjolnirUserSettingsTab_subscriptionAdd"
|
|
||||||
type="text"
|
type="text"
|
||||||
label={_t("Room ID or alias of ban list")}
|
label={_t("Room ID or alias of ban list")}
|
||||||
value={this.state.newList}
|
value={this.state.newList}
|
||||||
|
|
|
@ -195,19 +195,16 @@ export default class PreferencesUserSettingsTab extends React.Component {
|
||||||
{autoHideMenuOption}
|
{autoHideMenuOption}
|
||||||
{autoLaunchOption}
|
{autoLaunchOption}
|
||||||
<Field
|
<Field
|
||||||
id={"autocompleteDelay"}
|
|
||||||
label={_t('Autocomplete delay (ms)')}
|
label={_t('Autocomplete delay (ms)')}
|
||||||
type='number'
|
type='number'
|
||||||
value={this.state.autocompleteDelay}
|
value={this.state.autocompleteDelay}
|
||||||
onChange={this._onAutocompleteDelayChange} />
|
onChange={this._onAutocompleteDelayChange} />
|
||||||
<Field
|
<Field
|
||||||
id={"readMarkerInViewThresholdMs"}
|
|
||||||
label={_t('Read Marker lifetime (ms)')}
|
label={_t('Read Marker lifetime (ms)')}
|
||||||
type='number'
|
type='number'
|
||||||
value={this.state.readMarkerInViewThresholdMs}
|
value={this.state.readMarkerInViewThresholdMs}
|
||||||
onChange={this._onReadMarkerInViewThresholdMs} />
|
onChange={this._onReadMarkerInViewThresholdMs} />
|
||||||
<Field
|
<Field
|
||||||
id={"readMarkerOutOfViewThresholdMs"}
|
|
||||||
label={_t('Read Marker off-screen lifetime (ms)')}
|
label={_t('Read Marker off-screen lifetime (ms)')}
|
||||||
type='number'
|
type='number'
|
||||||
value={this.state.readMarkerOutOfViewThresholdMs}
|
value={this.state.readMarkerOutOfViewThresholdMs}
|
||||||
|
|
|
@ -163,7 +163,7 @@ export default class VoiceUserSettingsTab extends React.Component {
|
||||||
if (audioOutputs.length > 0) {
|
if (audioOutputs.length > 0) {
|
||||||
const defaultDevice = getDefaultDevice(audioOutputs);
|
const defaultDevice = getDefaultDevice(audioOutputs);
|
||||||
speakerDropdown = (
|
speakerDropdown = (
|
||||||
<Field element="select" label={_t("Audio Output")} id="audioOutput"
|
<Field element="select" label={_t("Audio Output")}
|
||||||
value={this.state.activeAudioOutput || defaultDevice}
|
value={this.state.activeAudioOutput || defaultDevice}
|
||||||
onChange={this._setAudioOutput}>
|
onChange={this._setAudioOutput}>
|
||||||
{this._renderDeviceOptions(audioOutputs, 'audioOutput')}
|
{this._renderDeviceOptions(audioOutputs, 'audioOutput')}
|
||||||
|
@ -175,7 +175,7 @@ export default class VoiceUserSettingsTab extends React.Component {
|
||||||
if (audioInputs.length > 0) {
|
if (audioInputs.length > 0) {
|
||||||
const defaultDevice = getDefaultDevice(audioInputs);
|
const defaultDevice = getDefaultDevice(audioInputs);
|
||||||
microphoneDropdown = (
|
microphoneDropdown = (
|
||||||
<Field element="select" label={_t("Microphone")} id="audioInput"
|
<Field element="select" label={_t("Microphone")}
|
||||||
value={this.state.activeAudioInput || defaultDevice}
|
value={this.state.activeAudioInput || defaultDevice}
|
||||||
onChange={this._setAudioInput}>
|
onChange={this._setAudioInput}>
|
||||||
{this._renderDeviceOptions(audioInputs, 'audioInput')}
|
{this._renderDeviceOptions(audioInputs, 'audioInput')}
|
||||||
|
@ -187,7 +187,7 @@ export default class VoiceUserSettingsTab extends React.Component {
|
||||||
if (videoInputs.length > 0) {
|
if (videoInputs.length > 0) {
|
||||||
const defaultDevice = getDefaultDevice(videoInputs);
|
const defaultDevice = getDefaultDevice(videoInputs);
|
||||||
webcamDropdown = (
|
webcamDropdown = (
|
||||||
<Field element="select" label={_t("Camera")} id="videoInput"
|
<Field element="select" label={_t("Camera")}
|
||||||
value={this.state.activeVideoInput || defaultDevice}
|
value={this.state.activeVideoInput || defaultDevice}
|
||||||
onChange={this._setVideoInput}>
|
onChange={this._setVideoInput}>
|
||||||
{this._renderDeviceOptions(videoInputs, 'videoInput')}
|
{this._renderDeviceOptions(videoInputs, 'videoInput')}
|
||||||
|
|
|
@ -2217,6 +2217,7 @@
|
||||||
"Navigate recent messages to edit": "Navigate recent messages to edit",
|
"Navigate recent messages to edit": "Navigate recent messages to edit",
|
||||||
"Jump to start/end of the composer": "Jump to start/end of the composer",
|
"Jump to start/end of the composer": "Jump to start/end of the composer",
|
||||||
"Navigate composer history": "Navigate composer history",
|
"Navigate composer history": "Navigate composer history",
|
||||||
|
"Cancel replying to a message": "Cancel replying to a message",
|
||||||
"Toggle microphone mute": "Toggle microphone mute",
|
"Toggle microphone mute": "Toggle microphone mute",
|
||||||
"Toggle video on/off": "Toggle video on/off",
|
"Toggle video on/off": "Toggle video on/off",
|
||||||
"Jump to room search": "Jump to room search",
|
"Jump to room search": "Jump to room search",
|
||||||
|
|
|
@ -469,6 +469,9 @@ export default class EventIndex extends EventEmitter {
|
||||||
// decryption keys, do we want to retry this checkpoint at a later
|
// decryption keys, do we want to retry this checkpoint at a later
|
||||||
// stage?
|
// stage?
|
||||||
const filteredEvents = matrixEvents.filter(this.isValidEvent);
|
const filteredEvents = matrixEvents.filter(this.isValidEvent);
|
||||||
|
const undecryptableEvents = matrixEvents.filter((ev) => {
|
||||||
|
return ev.isDecryptionFailure();
|
||||||
|
});
|
||||||
|
|
||||||
// Collect the redaction events so we can delete the redacted events
|
// Collect the redaction events so we can delete the redacted events
|
||||||
// from the index.
|
// from the index.
|
||||||
|
@ -503,7 +506,10 @@ export default class EventIndex extends EventEmitter {
|
||||||
console.log(
|
console.log(
|
||||||
"EventIndex: Crawled room",
|
"EventIndex: Crawled room",
|
||||||
client.getRoom(checkpoint.roomId).name,
|
client.getRoom(checkpoint.roomId).name,
|
||||||
"and fetched", events.length, "events.",
|
"and fetched total", matrixEvents.length, "events of which",
|
||||||
|
events.length, "are being added,", redactionEvents.length,
|
||||||
|
"are redacted,", matrixEvents.length - events.length,
|
||||||
|
"are being skipped, undecryptable", undecryptableEvents.length,
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
50
src/utils/ShieldUtils.ts
Normal file
50
src/utils/ShieldUtils.ts
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import DMRoomMap from './DMRoomMap';
|
||||||
|
|
||||||
|
/* For now, a cut-down type spec for the client */
|
||||||
|
interface Client {
|
||||||
|
getUserId: () => string;
|
||||||
|
checkUserTrust: (userId: string) => {
|
||||||
|
isCrossSigningVerified: () => boolean
|
||||||
|
};
|
||||||
|
getStoredDevicesForUser: (userId: string) => Promise<[{ deviceId: string }]>;
|
||||||
|
checkDeviceTrust: (userId: string, deviceId: string) => {
|
||||||
|
isVerified: () => boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Room {
|
||||||
|
getEncryptionTargetMembers: () => Promise<[{userId: string}]>;
|
||||||
|
roomId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function shieldStatusForRoom(client: Client, room: Room): Promise<string> {
|
||||||
|
const members = (await room.getEncryptionTargetMembers()).map(({userId}) => userId);
|
||||||
|
const inDMMap = !!DMRoomMap.shared().getUserIdForRoomId(room.roomId);
|
||||||
|
|
||||||
|
const verified: string[] = [];
|
||||||
|
const unverified: string[] = [];
|
||||||
|
members.filter((userId) => userId !== client.getUserId())
|
||||||
|
.forEach((userId) => {
|
||||||
|
(client.checkUserTrust(userId).isCrossSigningVerified() ?
|
||||||
|
verified : unverified).push(userId);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Check all verified user devices. */
|
||||||
|
/* Don't alarm if no other users are verified */
|
||||||
|
const includeUser = (verified.length > 0) && // Don't alarm for self in rooms where nobody else is verified
|
||||||
|
!inDMMap && // Don't alarm for self in DMs with other users
|
||||||
|
(members.length !== 2) || // Don't alarm for self in 1:1 chats with other users
|
||||||
|
(members.length === 1); // Do alarm for self if we're alone in a room
|
||||||
|
const targets = includeUser ? [...verified, client.getUserId()] : verified;
|
||||||
|
for (const userId of targets) {
|
||||||
|
const devices = await client.getStoredDevicesForUser(userId);
|
||||||
|
const anyDeviceNotVerified = devices.some(({deviceId}) => {
|
||||||
|
return !client.checkDeviceTrust(userId, deviceId).isVerified();
|
||||||
|
});
|
||||||
|
if (anyDeviceNotVerified) {
|
||||||
|
return "warning";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return unverified.length === 0 ? "verified" : "normal";
|
||||||
|
}
|
170
test/utils/ShieldUtils-test.js
Normal file
170
test/utils/ShieldUtils-test.js
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
import { shieldStatusForRoom } from '../../src/utils/ShieldUtils';
|
||||||
|
import DMRoomMap from '../../src/utils/DMRoomMap';
|
||||||
|
|
||||||
|
function mkClient(selfTrust) {
|
||||||
|
return {
|
||||||
|
getUserId: () => "@self:localhost",
|
||||||
|
checkUserTrust: (userId) => ({
|
||||||
|
isCrossSigningVerified: () => userId[1] == "T",
|
||||||
|
}),
|
||||||
|
checkDeviceTrust: (userId, deviceId) => ({
|
||||||
|
isVerified: () => userId === "@self:localhost" ? selfTrust : userId[2] == "T",
|
||||||
|
}),
|
||||||
|
getStoredDevicesForUser: async (userId) => ["DEVICE"],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("mkClient self-test", function() {
|
||||||
|
test.each([true, false])("behaves well for self-trust=%s", (v) => {
|
||||||
|
const client = mkClient(v);
|
||||||
|
expect(client.checkDeviceTrust("@self:localhost", "DEVICE").isVerified()).toBe(v);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
["@TT:h", true],
|
||||||
|
["@TF:h", true],
|
||||||
|
["@FT:h", false],
|
||||||
|
["@FF:h", false]],
|
||||||
|
)("behaves well for user trust %s", (userId, trust) => {
|
||||||
|
expect(mkClient().checkUserTrust(userId).isCrossSigningVerified()).toBe(trust);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
["@TT:h", true],
|
||||||
|
["@TF:h", false],
|
||||||
|
["@FT:h", true],
|
||||||
|
["@FF:h", false]],
|
||||||
|
)("behaves well for device trust %s", (userId, trust) => {
|
||||||
|
expect(mkClient().checkDeviceTrust(userId, "device").isVerified()).toBe(trust);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("shieldStatusForMembership self-trust behaviour", function() {
|
||||||
|
beforeAll(() => {
|
||||||
|
DMRoomMap._sharedInstance = {
|
||||||
|
getUserIdForRoomId: (roomId) => roomId === "DM" ? "@any:h" : null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(
|
||||||
|
[[true, true], [true, false],
|
||||||
|
[false, true], [false, false]],
|
||||||
|
)("2 unverified: returns 'normal', self-trust = %s, DM = %s", async (trusted, dm) => {
|
||||||
|
const client = mkClient(trusted);
|
||||||
|
const room = {
|
||||||
|
roomId: dm ? "DM" : "other",
|
||||||
|
getEncryptionTargetMembers: () => ["@self:localhost", "@FF1:h", "@FF2:h"].map((userId) => ({userId})),
|
||||||
|
};
|
||||||
|
const status = await shieldStatusForRoom(client, room);
|
||||||
|
expect(status).toEqual("normal");
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(
|
||||||
|
[["verified", true, true], ["verified", true, false],
|
||||||
|
["verified", false, true], ["warning", false, false]],
|
||||||
|
)("2 verified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
||||||
|
const client = mkClient(trusted);
|
||||||
|
const room = {
|
||||||
|
roomId: dm ? "DM" : "other",
|
||||||
|
getEncryptionTargetMembers: () => ["@self:localhost", "@TT1:h", "@TT2:h"].map((userId) => ({userId})),
|
||||||
|
};
|
||||||
|
const status = await shieldStatusForRoom(client, room);
|
||||||
|
expect(status).toEqual(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(
|
||||||
|
[["normal", true, true], ["normal", true, false],
|
||||||
|
["normal", false, true], ["warning", false, false]],
|
||||||
|
)("2 mixed: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
||||||
|
const client = mkClient(trusted);
|
||||||
|
const room = {
|
||||||
|
roomId: dm ? "DM" : "other",
|
||||||
|
getEncryptionTargetMembers: () => ["@self:localhost", "@TT1:h", "@FF2:h"].map((userId) => ({userId})),
|
||||||
|
};
|
||||||
|
const status = await shieldStatusForRoom(client, room);
|
||||||
|
expect(status).toEqual(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(
|
||||||
|
[["verified", true, true], ["verified", true, false],
|
||||||
|
["warning", false, true], ["warning", false, false]],
|
||||||
|
)("0 others: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
||||||
|
const client = mkClient(trusted);
|
||||||
|
const room = {
|
||||||
|
roomId: dm ? "DM" : "other",
|
||||||
|
getEncryptionTargetMembers: () => ["@self:localhost"].map((userId) => ({userId})),
|
||||||
|
};
|
||||||
|
const status = await shieldStatusForRoom(client, room);
|
||||||
|
expect(status).toEqual(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(
|
||||||
|
[["verified", true, true], ["verified", true, false],
|
||||||
|
["verified", false, true], ["verified", false, false]],
|
||||||
|
)("1 verified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
||||||
|
const client = mkClient(trusted);
|
||||||
|
const room = {
|
||||||
|
roomId: dm ? "DM" : "other",
|
||||||
|
getEncryptionTargetMembers: () => ["@self:localhost", "@TT:h"].map((userId) => ({userId})),
|
||||||
|
};
|
||||||
|
const status = await shieldStatusForRoom(client, room);
|
||||||
|
expect(status).toEqual(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(
|
||||||
|
[["normal", true, true], ["normal", true, false],
|
||||||
|
["normal", false, true], ["normal", false, false]],
|
||||||
|
)("1 unverified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
||||||
|
const client = mkClient(trusted);
|
||||||
|
const room = {
|
||||||
|
roomId: dm ? "DM" : "other",
|
||||||
|
getEncryptionTargetMembers: () => ["@self:localhost", "@FF:h"].map((userId) => ({userId})),
|
||||||
|
};
|
||||||
|
const status = await shieldStatusForRoom(client, room);
|
||||||
|
expect(status).toEqual(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("shieldStatusForMembership other-trust behaviour", function() {
|
||||||
|
beforeAll(() => {
|
||||||
|
DMRoomMap._sharedInstance = {
|
||||||
|
getUserIdForRoomId: (roomId) => roomId === "DM" ? "@any:h" : null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(
|
||||||
|
[["warning", true], ["warning", false]],
|
||||||
|
)("1 verified/untrusted: returns '%s', DM = %s", async (result, dm) => {
|
||||||
|
const client = mkClient(true);
|
||||||
|
const room = {
|
||||||
|
roomId: dm ? "DM" : "other",
|
||||||
|
getEncryptionTargetMembers: () => ["@self:localhost", "@TF:h"].map((userId) => ({userId})),
|
||||||
|
};
|
||||||
|
const status = await shieldStatusForRoom(client, room);
|
||||||
|
expect(status).toEqual(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(
|
||||||
|
[["warning", true], ["warning", false]],
|
||||||
|
)("2 verified/untrusted: returns '%s', DM = %s", async (result, dm) => {
|
||||||
|
const client = mkClient(true);
|
||||||
|
const room = {
|
||||||
|
roomId: dm ? "DM" : "other",
|
||||||
|
getEncryptionTargetMembers: () => ["@self:localhost", "@TF:h", "@TT: h"].map((userId) => ({userId})),
|
||||||
|
};
|
||||||
|
const status = await shieldStatusForRoom(client, room);
|
||||||
|
expect(status).toEqual(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(
|
||||||
|
[["normal", true], ["normal", false]],
|
||||||
|
)("2 unverified/untrusted: returns '%s', DM = %s", async (result, dm) => {
|
||||||
|
const client = mkClient(true);
|
||||||
|
const room = {
|
||||||
|
roomId: dm ? "DM" : "other",
|
||||||
|
getEncryptionTargetMembers: () => ["@self:localhost", "@FF:h", "@FT: h"].map((userId) => ({userId})),
|
||||||
|
};
|
||||||
|
const status = await shieldStatusForRoom(client, room);
|
||||||
|
expect(status).toEqual(result);
|
||||||
|
});
|
||||||
|
});
|
|
@ -5690,8 +5690,8 @@ mathml-tag-names@^2.0.1:
|
||||||
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
|
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
|
||||||
|
|
||||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
|
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
|
||||||
version "5.1.1"
|
version "5.2.0"
|
||||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/b2e154377a4268441a3b27b183dd7f7018187035"
|
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/223d37ffce674a23ca73702f04b9ba31cfd84196"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.8.3"
|
"@babel/runtime" "^7.8.3"
|
||||||
another-json "^0.2.0"
|
another-json "^0.2.0"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue