diff --git a/res/css/_components.scss b/res/css/_components.scss index fe331504f2..c82dedc069 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -77,7 +77,6 @@ @import "./views/elements/_Dropdown.scss"; @import "./views/elements/_EditableItemList.scss"; @import "./views/elements/_Field.scss"; -@import "./views/elements/_HexVerify.scss"; @import "./views/elements/_ImageView.scss"; @import "./views/elements/_InlineSpinner.scss"; @import "./views/elements/_ManageIntegsButton.scss"; @@ -156,6 +155,7 @@ @import "./views/settings/tabs/_SecuritySettingsTab.scss"; @import "./views/settings/tabs/_SettingsTab.scss"; @import "./views/settings/tabs/_VoiceSettingsTab.scss"; +@import "./views/verification/_VerificationShowSas.scss"; @import "./views/voip/_CallView.scss"; @import "./views/voip/_IncomingCallbox.scss"; @import "./views/voip/_VideoView.scss"; diff --git a/res/css/structures/_CustomRoomTagPanel.scss b/res/css/structures/_CustomRoomTagPanel.scss index f02421db2c..45961d7be1 100644 --- a/res/css/structures/_CustomRoomTagPanel.scss +++ b/res/css/structures/_CustomRoomTagPanel.scss @@ -21,7 +21,11 @@ limitations under the License. .mx_CustomRoomTagPanel { background-color: $tagpanel-bg-color; - max-height: 40%; + max-height: 40vh; +} + +.mx_CustomRoomTagPanel_scroller { + max-height: inherit; } .mx_CustomRoomTagPanel .mx_AccessibleButton { diff --git a/res/css/structures/_TabbedView.scss b/res/css/structures/_TabbedView.scss index cbd2870cba..fb4df53d52 100644 --- a/res/css/structures/_TabbedView.scss +++ b/res/css/structures/_TabbedView.scss @@ -85,6 +85,7 @@ limitations under the License. flex-grow: 1; display: flex; flex-direction: column; + min-height: 0; // firefox } .mx_TabbedView_tabPanelContent { diff --git a/res/css/views/elements/_HexVerify.scss b/res/css/views/verification/_VerificationShowSas.scss similarity index 78% rename from res/css/views/elements/_HexVerify.scss rename to res/css/views/verification/_VerificationShowSas.scss index 3f3ee4b7ea..32ccf6b0bb 100644 --- a/res/css/views/elements/_HexVerify.scss +++ b/res/css/views/verification/_VerificationShowSas.scss @@ -14,21 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_HexVerify { +.mx_VerificationShowSas_sas { text-align: center; -} - -.mx_HexVerify_pair { - display: inline-block; font-weight: bold; padding-left: 3px; padding-right: 3px; } - -.mx_HexVerify_pair_verified { - color: $accent-color; -} - -.mx_HexVerify_pair:hover{ - color: $accent-color; -} diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index 882a913452..f283eb84a5 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -135,14 +135,7 @@ class MatrixClientPeg { const opts = utils.deepCopy(this.opts); // the react sdk doesn't work without this, so don't allow opts.pendingEventOrdering = "detached"; - - const LAZY_LOADING_FEATURE = "feature_lazyloading"; - if (SettingsStore.isFeatureEnabled(LAZY_LOADING_FEATURE)) { - const userId = this.matrixClient.credentials.userId; - if (phasedRollOutExpiredForUser(userId, LAZY_LOADING_FEATURE, Date.now())) { - opts.lazyLoadMembers = true; - } - } + opts.lazyLoadMembers = true; // Connect the matrix client to the dispatcher MatrixActionCreators.start(this.matrixClient); diff --git a/src/TextForEvent.js b/src/TextForEvent.js index 2a37295f83..ac0af82ff1 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -134,6 +134,38 @@ function textForTombstoneEvent(ev) { return _t('%(senderDisplayName)s upgraded this room.', {senderDisplayName}); } +function textForJoinRulesEvent(ev) { + const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); + switch (ev.getContent().join_rule) { + case "public": + return _t('%(senderDisplayName)s made the room public to whoever knows the link.', {senderDisplayName}); + case "invite": + return _t('%(senderDisplayName)s made the room invite only.', {senderDisplayName}); + default: + // The spec supports "knock" and "private", however nothing implements these. + return _t('%(senderDisplayName)s changed the join rule to %(rule)s', { + senderDisplayName, + rule: ev.getContent().join_rule, + }); + } +} + +function textForGuestAccessEvent(ev) { + const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); + switch (ev.getContent().guest_access) { + case "can_join": + return _t('%(senderDisplayName)s has allowed guests to join the room.', {senderDisplayName}); + case "forbidden": + return _t('%(senderDisplayName)s has prevented guests from joining the room.', {senderDisplayName}); + default: + // There's no other options we can expect, however just for safety's sake we'll do this. + return _t('%(senderDisplayName)s changed guest access to %(rule)s', { + senderDisplayName, + rule: ev.getContent().guest_access, + }); + } +} + function textForServerACLEvent(ev) { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const prevContent = ev.getPrevContent(); @@ -439,6 +471,8 @@ const stateHandlers = { 'm.room.pinned_events': textForPinnedEvent, 'm.room.server_acl': textForServerACLEvent, 'm.room.tombstone': textForTombstoneEvent, + 'm.room.join_rules': textForJoinRulesEvent, + 'm.room.guest_access': textForGuestAccessEvent, 'im.vector.modular.widgets': textForWidgetEvent, }; diff --git a/src/components/structures/LeftPanel.js b/src/components/structures/LeftPanel.js index 0ae9b032d1..bd49f8acd4 100644 --- a/src/components/structures/LeftPanel.js +++ b/src/components/structures/LeftPanel.js @@ -190,10 +190,13 @@ const LeftPanel = React.createClass({ const tagPanelEnabled = SettingsStore.getValue("TagPanel.enableTagPanel"); let tagPanelContainer; + + const isCustomTagsEnabled = SettingsStore.isFeatureEnabled("feature_custom_tags"); + if (tagPanelEnabled) { tagPanelContainer = (
- + { isCustomTagsEnabled ? : undefined }
); } diff --git a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js b/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js index e5b82baa92..712d8d2b4e 100644 --- a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js +++ b/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js @@ -19,6 +19,8 @@ import sdk from '../../../../index'; import MatrixClientPeg from '../../../../MatrixClientPeg'; import Modal from '../../../../Modal'; +import { MatrixClient } from 'matrix-js-sdk'; + import { _t } from '../../../../languageHandler'; const RESTORE_TYPE_PASSPHRASE = 0; @@ -88,7 +90,7 @@ export default React.createClass({ }); try { const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithPassword( - this.state.passPhrase, undefined, undefined, this.state.backupInfo.version, + this.state.passPhrase, undefined, undefined, this.state.backupInfo, ); this.setState({ loading: false, @@ -107,11 +109,11 @@ export default React.createClass({ this.setState({ loading: true, restoreError: null, - restoreType: RESTORE_TYPE_PASSPHRASE, + restoreType: RESTORE_TYPE_RECOVERYKEY, }); try { const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithRecoveryKey( - this.state.recoveryKey, undefined, undefined, this.state.backupInfo.version, + this.state.recoveryKey, undefined, undefined, this.state.backupInfo, ); this.setState({ loading: false, @@ -185,32 +187,31 @@ export default React.createClass({ title = _t("Error"); content = _t("Unable to load backup status"); } else if (this.state.restoreError) { - title = _t("Error"); - content = _t("Unable to restore backup"); + if (this.state.restoreError.errcode === MatrixClient.RESTORE_BACKUP_ERROR_BAD_KEY) { + if (this.state.restoreType === RESTORE_TYPE_RECOVERYKEY) { + title = _t("Recovery Key Mismatch"); + content =
+

{_t( + "Backup could not be decrypted with this key: " + + "please verify that you entered the correct recovery key.", + )}

+
; + } else { + title = _t("Incorrect Recovery Passphrase"); + content =
+

{_t( + "Backup could not be decrypted with this passphrase: " + + "please verify that you entered the correct recovery passphrase.", + )}

+
; + } + } else { + title = _t("Error"); + content = _t("Unable to restore backup"); + } } else if (this.state.backupInfo === null) { title = _t("Error"); content = _t("No backup found!"); - } else if ( - this.state.recoverInfo && - this.state.recoverInfo.imported === 0 && - this.state.recoverInfo.total > 0 - ) { - title = _t("Error Restoring Backup"); - if (this.state.restoreType === RESTORE_TYPE_RECOVERYKEY) { - content =
-

{_t( - "Backup could not be decrypted with this key: " + - "please verify that you entered the correct recovery key.", - )}

-
; - } else { - content =
-

{_t( - "Backup could not be decrypted with this passphrase: " + - "please verify that you entered the correct recovery passphrase.", - )}

-
; - } } else if (this.state.recoverInfo) { title = _t("Backup Restored"); let failedToDecrypt; diff --git a/src/components/views/elements/HexVerify.js b/src/components/views/elements/HexVerify.js deleted file mode 100644 index 86ead3adc1..0000000000 --- a/src/components/views/elements/HexVerify.js +++ /dev/null @@ -1,103 +0,0 @@ -/* -Copyright 2019 New Vector Ltd. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from "react"; -import PropTypes from "prop-types"; -import classnames from 'classnames'; - -import sdk from '../../../index'; - -class HexVerifyPair extends React.Component { - static propTypes = { - text: PropTypes.string.isRequired, - index: PropTypes.number, - verified: PropTypes.bool, - onChange: PropTypes.func.isRequired, - } - - _onClick = () => { - this.setState({verified: !this.props.verified}); - this.props.onChange(this.props.index, !this.props.verified); - } - - render() { - const classNames = { - mx_HexVerify_pair: true, - mx_HexVerify_pair_verified: this.props.verified, - }; - const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton'); - return - {this.props.text} - ; - } -} - -/* - * Helps a user verify a hexadecimal code matches one displayed - * elsewhere (eg. on a different device) - */ -export default class HexVerify extends React.Component { - static propTypes = { - text: PropTypes.string.isRequired, - onVerifiedStateChange: PropTypes.func, - } - - static defaultProps = { - onVerifiedStateChange: function() {}, - } - - constructor(props) { - super(props); - this.state = { - pairsVerified: [], - }; - for (let i = 0; i < props.text.length; i += 2) { - this.state.pairsVerified.push(false); - } - } - - _onPairChange = (index, newVal) => { - const oldVerified = this.state.pairsVerified.reduce((acc, val) => { - return acc && val; - }, true); - const newPairsVerified = this.state.pairsVerified.slice(0); - newPairsVerified[index] = newVal; - const newVerified = newPairsVerified.reduce((acc, val) => { - return acc && val; - }, true); - this.setState({pairsVerified: newPairsVerified}); - if (oldVerified !== newVerified) { - this.props.onVerifiedStateChange(newVerified); - } - } - - render() { - const pairs = []; - - for (let i = 0; i < this.props.text.length / 2; ++i) { - pairs.push(); - } - return
- {pairs} -
; - } -} diff --git a/src/components/views/elements/ToggleSwitch.js b/src/components/views/elements/ToggleSwitch.js index 19c87aab56..0d8b3d042f 100644 --- a/src/components/views/elements/ToggleSwitch.js +++ b/src/components/views/elements/ToggleSwitch.js @@ -38,6 +38,12 @@ export default class ToggleSwitch extends React.Component { }; } + componentWillReceiveProps(nextProps) { + if (nextProps.checked !== this.state.checked) { + this.setState({checked: nextProps.checked}); + } + } + _onClick = (e) => { e.stopPropagation(); e.preventDefault(); diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index e4a6695ff5..8520d804e0 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -63,6 +63,8 @@ const stateEventTileTypes = { 'm.room.server_acl': 'messages.TextualEvent', 'im.vector.modular.widgets': 'messages.TextualEvent', 'm.room.tombstone': 'messages.TextualEvent', + 'm.room.join_rules': 'messages.TextualEvent', + 'm.room.guest_access': 'messages.TextualEvent', }; function getHandlerTile(ev) { diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 8418ab6d6f..56eb4b713d 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -172,11 +172,14 @@ module.exports = React.createClass({ this._delayedRefreshRoomList(); }); - this._customTagStoreToken = CustomRoomTagStore.addListener(() => { - this.setState({ - customTags: CustomRoomTagStore.getTags(), + + if (SettingsStore.isFeatureEnabled("feature_custom_tags")) { + this._customTagStoreToken = CustomRoomTagStore.addListener(() => { + this.setState({ + customTags: CustomRoomTagStore.getTags(), + }); }); - }); + } this.refreshRoomList(); @@ -728,7 +731,8 @@ module.exports = React.createClass({ ]; const tagSubLists = Object.keys(this.state.lists) .filter((tagName) => { - return this.state.customTags[tagName] && !tagName.match(STANDARD_TAGS_REGEX); + return (!this.state.customTags || this.state.customTags[tagName]) && + !tagName.match(STANDARD_TAGS_REGEX); }).map((tagName) => { return { list: this.state.lists[tagName], diff --git a/src/components/views/settings/KeyBackupPanel.js b/src/components/views/settings/KeyBackupPanel.js index 15856a75f3..cbbca42927 100644 --- a/src/components/views/settings/KeyBackupPanel.js +++ b/src/components/views/settings/KeyBackupPanel.js @@ -250,19 +250,26 @@ export default class KeyBackupPanel extends React.PureComponent { backupSigStatuses = _t("Backup is not signed by any of your devices"); } + let trustedLocally; + if (this.state.backupSigStatus.trusted_locally) { + trustedLocally = _t("This backup is trusted because it has been restored on this device"); + } + return
- {_t("Backup version: ")}{this.state.backupInfo.version}
- {_t("Algorithm: ")}{this.state.backupInfo.algorithm}
- {clientBackupStatus}
+
{_t("Backup version: ")}{this.state.backupInfo.version}
+
{_t("Algorithm: ")}{this.state.backupInfo.algorithm}
+
{clientBackupStatus}
{uploadStatus} -
{backupSigStatuses}

-
- - { _t("Restore backup") } -     - - { _t("Delete backup") } - +
{backupSigStatuses}
+
{trustedLocally}
+

+ + { _t("Restore backup") } +     + + { _t("Delete backup") } + +

; } else { return
diff --git a/src/components/views/settings/tabs/LabsSettingsTab.js b/src/components/views/settings/tabs/LabsSettingsTab.js index fc64c1bd04..e06f87460b 100644 --- a/src/components/views/settings/tabs/LabsSettingsTab.js +++ b/src/components/views/settings/tabs/LabsSettingsTab.js @@ -18,9 +18,7 @@ import React from 'react'; import {_t} from "../../../../languageHandler"; import PropTypes from "prop-types"; import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore"; -import MatrixClientPeg from "../../../../MatrixClientPeg"; import LabelledToggleSwitch from "../../elements/LabelledToggleSwitch"; -const Modal = require("../../../../Modal"); const sdk = require("../../../../index"); export class LabsSettingToggle extends React.Component { @@ -28,38 +26,7 @@ export class LabsSettingToggle extends React.Component { featureId: PropTypes.string.isRequired, }; - async _onLazyLoadChanging(enabling) { - // don't prevent turning LL off when not supported - if (enabling) { - const supported = await MatrixClientPeg.get().doesServerSupportLazyLoading(); - if (!supported) { - await new Promise((resolve) => { - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - Modal.createDialog(QuestionDialog, { - title: _t("Lazy loading members not supported"), - description: -
- { _t("Lazy loading is not supported by your " + - "current homeserver.") } -
, - button: _t("OK"), - onFinished: resolve, - }); - }); - return false; - } - } - return true; - } - _onChange = async (checked) => { - if (this.props.featureId === "feature_lazyloading") { - const confirmed = await this._onLazyLoadChanging(checked); - if (!confirmed) { - return; - } - } - await SettingsStore.setFeatureEnabled(this.props.featureId, checked); this.forceUpdate(); }; diff --git a/src/components/views/settings/tabs/RolesRoomSettingsTab.js b/src/components/views/settings/tabs/RolesRoomSettingsTab.js index 776ce9f01a..2195017782 100644 --- a/src/components/views/settings/tabs/RolesRoomSettingsTab.js +++ b/src/components/views/settings/tabs/RolesRoomSettingsTab.js @@ -113,6 +113,39 @@ export default class RolesRoomSettingsTab extends React.Component { } } + _onPowerLevelsChanged = (value, powerLevelKey) => { + const client = MatrixClientPeg.get(); + const room = client.getRoom(this.props.roomId); + let plContent = room.currentState.getStateEvents('m.room.power_levels', '').getContent() || {}; + + // Clone the power levels just in case + plContent = Object.assign({}, plContent); + + const eventsLevelPrefix = "event_levels_"; + + value = parseInt(value); + + if (powerLevelKey.startsWith(eventsLevelPrefix)) { + // deep copy "events" object, Object.assign itself won't deep copy + plContent["events"] = Object.assign({}, plContent["events"] || {}); + plContent["events"][powerLevelKey.slice(eventsLevelPrefix.length)] = value; + } else { + const keyPath = powerLevelKey.split('.'); + let parentObj; + let currentObj = plContent; + for (const key of keyPath) { + if (!currentObj[key]) { + currentObj[key] = {}; + } + parentObj = currentObj; + currentObj = currentObj[key]; + } + parentObj[keyPath[keyPath.length - 1]] = value; + } + + client.sendStateEvent(this.props.roomId, "m.room.power_levels", plContent); + }; + render() { const PowerSelector = sdk.getComponent('elements.PowerSelector'); @@ -272,12 +305,12 @@ export default class RolesRoomSettingsTab extends React.Component { controlled={false} disabled={!canChangeLevels || currentUserLevel < value} powerLevelKey={key} // Will be sent as the second parameter to `onChange` - onChange={this.onPowerLevelsChanged} + onChange={this._onPowerLevelsChanged} />
; }); - const eventPowerSelectors = Object.keys(eventsLevels).map(function(eventType, i) { + const eventPowerSelectors = Object.keys(eventsLevels).map((eventType, i) => { let label = plEventsToLabels[eventType]; if (label) { label = _t(label); @@ -296,7 +329,7 @@ export default class RolesRoomSettingsTab extends React.Component { controlled={false} disabled={!canChangeLevels || currentUserLevel < eventsLevels[eventType]} powerLevelKey={"event_levels_" + eventType} - onChange={self.onPowerLevelsChanged} + onChange={this._onPowerLevelsChanged} /> ); diff --git a/src/components/views/settings/tabs/SecurityRoomSettingsTab.js b/src/components/views/settings/tabs/SecurityRoomSettingsTab.js index df6648c4a6..593e8151d2 100644 --- a/src/components/views/settings/tabs/SecurityRoomSettingsTab.js +++ b/src/components/views/settings/tabs/SecurityRoomSettingsTab.js @@ -27,8 +27,27 @@ export default class SecurityRoomSettingsTab extends React.Component { roomId: PropTypes.string.isRequired, }; + constructor() { + super(); + + this.state = { + joinRule: "invite", + guestAccess: "can_join", + history: "shared", + encrypted: false, + }; + } + componentWillMount(): void { MatrixClientPeg.get().on("RoomState.events", this._onStateEvent); + + const room = MatrixClientPeg.get().getRoom(this.props.roomId); + const state = room.currentState; + const joinRule = state.getStateEvents("m.room.join_rules", "").getContent()['join_rule']; + const guestAccess = state.getStateEvents("m.room.guest_access", "").getContent()['guest_access']; + const history = state.getStateEvents("m.room.history_visibility", "").getContent()['history_visibility']; + const encrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.roomId); + this.setState({joinRule, guestAccess, history, encrypted}); } componentWillUnmount(): void { @@ -46,19 +65,37 @@ export default class SecurityRoomSettingsTab extends React.Component { }; _onEncryptionChange = (e) => { + const beforeEncrypted = this.state.encrypted; + this.setState({encrypted: true}); MatrixClientPeg.get().sendStateEvent( this.props.roomId, "m.room.encryption", { algorithm: "m.megolm.v1.aes-sha2" }, - ); + ).catch((e) => { + console.error(e); + this.setState({encrypted: beforeEncrypted}); + }); }; _fixGuestAccess = (e) => { e.preventDefault(); e.stopPropagation(); + const joinRule = "invite"; + const guestAccess = "can_join"; + + const beforeJoinRule = this.state.joinRule; + const beforeGuestAccess = this.state.guestAccess; + this.setState({joinRule, guestAccess}); + const client = MatrixClientPeg.get(); - client.sendStateEvent(this.props.roomId, "m.room.join_rules", {join_rule: "invite"}, ""); - client.sendStateEvent(this.props.roomId, "m.room.guest_access", {guest_access: "can_join"}, ""); + client.sendStateEvent(this.props.roomId, "m.room.join_rules", {join_rule: joinRule}, "").catch((e) => { + console.error(e); + this.setState({joinRule: beforeJoinRule}); + }); + client.sendStateEvent(this.props.roomId, "m.room.guest_access", {guest_access: guestAccess}, "").catch((e) => { + console.error(e); + this.setState({guestAccess: beforeGuestAccess}); + }); }; _onRoomAccessRadioToggle = (ev) => { @@ -92,24 +129,39 @@ export default class SecurityRoomSettingsTab extends React.Component { break; } + const beforeJoinRule = this.state.joinRule; + const beforeGuestAccess = this.state.guestAccess; + this.setState({joinRule, guestAccess}); + const client = MatrixClientPeg.get(); - client.sendStateEvent(this.props.roomId, "m.room.join_rules", {join_rule: joinRule}, ""); - client.sendStateEvent(this.props.roomId, "m.room.guest_access", {guest_access: guestAccess}, ""); + client.sendStateEvent(this.props.roomId, "m.room.join_rules", {join_rule: joinRule}, "").catch((e) => { + console.error(e); + this.setState({joinRule: beforeJoinRule}); + }); + client.sendStateEvent(this.props.roomId, "m.room.guest_access", {guest_access: guestAccess}, "").catch((e) => { + console.error(e); + this.setState({guestAccess: beforeGuestAccess}); + }); }; _onHistoryRadioToggle = (ev) => { + const beforeHistory = this.state.history; + this.setState({history: ev.target.value}); MatrixClientPeg.get().sendStateEvent(this.props.roomId, "m.room.history_visibility", { history_visibility: ev.target.value, - }, ""); + }, "").catch((e) => { + console.error(e); + this.setState({history: beforeHistory}); + }); }; _renderRoomAccess() { const client = MatrixClientPeg.get(); const room = client.getRoom(this.props.roomId); - const joinRule = room.currentState.getStateEvents("m.room.join_rules", "").getContent()['join_rule']; - const guestAccess = room.currentState.getStateEvents("m.room.guest_access", "").getContent()['guest_access']; + const joinRule = this.state.joinRule; + const guestAccess = this.state.guestAccess; const aliasEvents = room.currentState.getStateEvents("m.room.aliases") || []; - const hasAliases = aliasEvents.includes((ev) => (ev.getContent().aliases || []).length); + const hasAliases = !!aliasEvents.find((ev) => (ev.getContent().aliases || []).length > 0); const canChangeAccess = room.currentState.mayClientSendStateEvent("m.room.join_rules", client) && room.currentState.mayClientSendStateEvent("m.room.guest_access", client); @@ -170,9 +222,8 @@ export default class SecurityRoomSettingsTab extends React.Component { _renderHistory() { const client = MatrixClientPeg.get(); - const room = client.getRoom(this.props.roomId); - const state = room.currentState; - const history = state.getStateEvents("m.room.history_visibility", "").getContent()['history_visibility']; + const history = this.state.history; + const state = client.getRoom(this.props.roomId).currentState; const canChangeHistory = state.mayClientSendStateEvent('m.room.history_visibility', client); return ( @@ -218,7 +269,7 @@ export default class SecurityRoomSettingsTab extends React.Component { const client = MatrixClientPeg.get(); const room = client.getRoom(this.props.roomId); - const isEncrypted = client.isRoomEncrypted(this.props.roomId); + const isEncrypted = this.state.encrypted; const hasEncryptionPermission = room.currentState.mayClientSendStateEvent("m.room.encryption", client); const canEnableEncryption = !isEncrypted && hasEncryptionPermission; diff --git a/src/components/views/verification/VerificationShowSas.js b/src/components/views/verification/VerificationShowSas.js index 6f3209989e..0224571d9e 100644 --- a/src/components/views/verification/VerificationShowSas.js +++ b/src/components/views/verification/VerificationShowSas.js @@ -28,19 +28,11 @@ export default class VerificationShowSas extends React.Component { constructor() { super(); - this.state = { - sasVerified: false, - }; - } - - _onVerifiedStateChange = (newVal) => { - this.setState({sasVerified: newVal}); } render() { const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); - const HexVerify = sdk.getComponent('views.elements.HexVerify'); - return
+ return

{_t( "Verify this user by confirming the following number appears on their screen.", )}

@@ -48,15 +40,11 @@ export default class VerificationShowSas extends React.Component { "For maximum security, we recommend you do this in person or use another " + "trusted means of communication.", )}

- -

{_t( - "To continue, click on each pair to confirm it's correct.", - )}

+
+ {this.props.sas} +
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b586d2f8f7..621a00066a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -186,6 +186,12 @@ "%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s removed the room name.", "%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s changed the room name to %(roomName)s.", "%(senderDisplayName)s upgraded this room.": "%(senderDisplayName)s upgraded this room.", + "%(senderDisplayName)s made the room public to whoever knows the link.": "%(senderDisplayName)s made the room public to whoever knows the link.", + "%(senderDisplayName)s made the room invite only.": "%(senderDisplayName)s made the room invite only.", + "%(senderDisplayName)s changed the join rule to %(rule)s": "%(senderDisplayName)s changed the join rule to %(rule)s", + "%(senderDisplayName)s has allowed guests to join the room.": "%(senderDisplayName)s has allowed guests to join the room.", + "%(senderDisplayName)s has prevented guests from joining the room.": "%(senderDisplayName)s has prevented guests from joining the room.", + "%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s changed guest access to %(rule)s", "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s sent an image.", "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s added %(addedAddresses)s as addresses for this room.", "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s added %(addedAddresses)s as an address for this room.", @@ -264,7 +270,7 @@ "Failed to join room": "Failed to join room", "Message Pinning": "Message Pinning", "Custom user status messages": "Custom user status messages", - "Increase performance by only loading room members on first view": "Increase performance by only loading room members on first view", + "Group & filter rooms by custom tags (refresh to apply changes)": "Group & filter rooms by custom tags (refresh to apply changes)", "Backup of encryption keys to server": "Backup of encryption keys to server", "Render simple counters in room header": "Render simple counters in room header", "Two-way device verification using short text": "Two-way device verification using short text", @@ -385,6 +391,7 @@ "Backup has an invalid signature from unverified device ": "Backup has an invalid signature from unverified device ", "Verify...": "Verify...", "Backup is not signed by any of your devices": "Backup is not signed by any of your devices", + "This backup is trusted because it has been restored on this device": "This backup is trusted because it has been restored on this device", "Backup version: ": "Backup version: ", "Algorithm: ": "Algorithm: ", "Restore backup": "Restore backup", @@ -476,8 +483,6 @@ "Identity Server is": "Identity Server is", "Access Token:": "Access Token:", "click to reveal": "click to reveal", - "Lazy loading members not supported": "Lazy loading members not supported", - "Lazy loading is not supported by your current homeserver.": "Lazy loading is not supported by your current homeserver.", "Labs": "Labs", "Notifications": "Notifications", "Start automatically after system login": "Start automatically after system login", @@ -1095,11 +1100,12 @@ "\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contains devices that you haven't seen before.", "Unknown devices": "Unknown devices", "Unable to load backup status": "Unable to load backup status", + "Recovery Key Mismatch": "Recovery Key Mismatch", + "Backup could not be decrypted with this key: please verify that you entered the correct recovery key.": "Backup could not be decrypted with this key: please verify that you entered the correct recovery key.", + "Incorrect Recovery Passphrase": "Incorrect Recovery Passphrase", + "Backup could not be decrypted with this passphrase: please verify that you entered the correct recovery passphrase.": "Backup could not be decrypted with this passphrase: please verify that you entered the correct recovery passphrase.", "Unable to restore backup": "Unable to restore backup", "No backup found!": "No backup found!", - "Error Restoring Backup": "Error Restoring Backup", - "Backup could not be decrypted with this key: please verify that you entered the correct recovery key.": "Backup could not be decrypted with this key: please verify that you entered the correct recovery key.", - "Backup could not be decrypted with this passphrase: please verify that you entered the correct recovery passphrase.": "Backup could not be decrypted with this passphrase: please verify that you entered the correct recovery passphrase.", "Backup Restored": "Backup Restored", "Failed to decrypt %(failedCount)s sessions!": "Failed to decrypt %(failedCount)s sessions!", "Restored %(sessionCount)s session keys": "Restored %(sessionCount)s session keys", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index c5de7298de..02c2bad14b 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -21,7 +21,6 @@ import { NotificationBodyEnabledController, NotificationsEnabledController, } from "./controllers/NotificationControllers"; -import LazyLoadingController from "./controllers/LazyLoadingController"; import CustomStatusController from "./controllers/CustomStatusController"; // These are just a bunch of helper arrays to avoid copy/pasting a bunch of times @@ -100,12 +99,11 @@ export const SETTINGS = { default: false, controller: new CustomStatusController(), }, - "feature_lazyloading": { + "feature_custom_tags": { isFeature: true, - displayName: _td("Increase performance by only loading room members on first view"), + displayName: _td("Group & filter rooms by custom tags (refresh to apply changes)"), supportedLevels: LEVELS_FEATURE, - controller: new LazyLoadingController(), - default: true, + default: false, }, "feature_keybackup": { isFeature: true, diff --git a/src/settings/controllers/LazyLoadingController.js b/src/settings/controllers/LazyLoadingController.js deleted file mode 100644 index 90f095c9ca..0000000000 --- a/src/settings/controllers/LazyLoadingController.js +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright 2018 New Vector - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import SettingController from "./SettingController"; -import MatrixClientPeg from "../../MatrixClientPeg"; -import PlatformPeg from "../../PlatformPeg"; - -export default class LazyLoadingController extends SettingController { - async onChange(level, roomId, newValue) { - if (!PlatformPeg.get()) return; - - MatrixClientPeg.get().stopClient(); - await MatrixClientPeg.get().store.deleteAllData(); - PlatformPeg.get().reload(); - } -} diff --git a/src/stores/CustomRoomTagStore.js b/src/stores/CustomRoomTagStore.js index 3e109aef2d..0f7f99aad9 100644 --- a/src/stores/CustomRoomTagStore.js +++ b/src/stores/CustomRoomTagStore.js @@ -17,6 +17,8 @@ import dis from '../dispatcher'; import * as RoomNotifs from '../RoomNotifs'; import RoomListStore from './RoomListStore'; import EventEmitter from 'events'; +import { throttle } from "lodash"; +import SettingsStore from "../settings/SettingsStore"; const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/; @@ -50,6 +52,14 @@ class CustomRoomTagStore extends EventEmitter { // Initialise state this._state = {tags: {}}; + // as RoomListStore gets updated by every timeline event + // throttle this to only run every 500ms + this._getUpdatedTags = throttle( + this._getUpdatedTags, 500, { + leading: true, + trailing: true, + }, + ); this._roomListStoreToken = RoomListStore.addListener(() => { this._setState({tags: this._getUpdatedTags()}); }); @@ -125,6 +135,10 @@ class CustomRoomTagStore extends EventEmitter { } _getUpdatedTags() { + if (!SettingsStore.isFeatureEnabled("feature_custom_tags")) { + return; + } + const newTagNames = Object.keys(RoomListStore.getRoomLists()) .filter((tagName) => { return !tagName.match(STANDARD_TAGS_REGEX); diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js index 61e17821bd..d98adc5cae 100644 --- a/src/stores/RoomListStore.js +++ b/src/stores/RoomListStore.js @@ -202,6 +202,8 @@ class RoomListStore extends Store { // If somehow we dispatched a RoomListActions.tagRoom.failure before a MatrixActions.sync if (!this._matrixClient) return; + const isCustomTagsEnabled = SettingsStore.isFeatureEnabled("feature_custom_tags"); + this._matrixClient.getRooms().forEach((room, index) => { const myUserId = this._matrixClient.getUserId(); const membership = room.getMyMembership(); @@ -226,7 +228,7 @@ class RoomListStore extends Store { // ignore any m. tag names we don't know about tagNames = tagNames.filter((t) => { - return !t.startsWith('m.') || lists[t] !== undefined; + return (isCustomTagsEnabled && !t.startsWith('m.')) || lists[t] !== undefined; }); if (tagNames.length) {