From 2903a0e71218830c8b1c9ef072f1e8d98f589a67 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 8 Feb 2019 09:11:30 -0700 Subject: [PATCH 01/12] Rework EditableItemList to support mxField Also improves upon the general UX to be a bit friendlier for direct manipulation things. --- res/css/views/elements/_EditableItemList.scss | 59 +++-- .../views/elements/EditableItemList.js | 212 +++++++++--------- .../views/room_settings/AliasSettings.js | 16 +- 3 files changed, 131 insertions(+), 156 deletions(-) diff --git a/res/css/views/elements/_EditableItemList.scss b/res/css/views/elements/_EditableItemList.scss index 9fbb39aa17..be96d811d3 100644 --- a/res/css/views/elements/_EditableItemList.scss +++ b/res/css/views/elements/_EditableItemList.scss @@ -1,5 +1,5 @@ /* -Copyright 2017 New Vector Ltd. +Copyright 2017, 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. @@ -16,47 +16,38 @@ limitations under the License. .mx_EditableItemList { margin-top: 12px; - margin-bottom: 0px; + margin-bottom: 10px; } .mx_EditableItem { - display: flex; - margin-left: 56px; + margin-bottom: 5px; + margin-left: 15px; } -.mx_EditableItem .mx_EditableItem_editable { - border: 0px; - border-bottom: 1px solid $strong-input-border-color; - padding: 0px; - min-width: 240px; - max-width: 400px; - margin-bottom: 16px; -} - -.mx_EditableItem .mx_EditableItem_editable:focus { - border-bottom: 1px solid $accent-color; - outline: none; - box-shadow: none; -} - -.mx_EditableItem .mx_EditableItem_editablePlaceholder { - color: $settings-grey-fg-color; -} - -.mx_EditableItem .mx_EditableItem_addButton, -.mx_EditableItem .mx_EditableItem_removeButton { - padding-left: 0.5em; - position: relative; +.mx_EditableItem_delete { + margin-right: 5px; cursor: pointer; - - visibility: hidden; + vertical-align: middle; } -.mx_EditableItem:hover .mx_EditableItem_addButton, -.mx_EditableItem:hover .mx_EditableItem_removeButton { - visibility: visible; +.mx_EditableItem_email { + vertical-align: middle; +} + +.mx_EditableItem_promptText { + margin-right: 10px; +} + +.mx_EditableItem_confirmBtn { + margin-right: 5px; +} + +.mx_EditableItemList_newItem .mx_Field input { + // Use 100% of the space available for the input, but don't let the 10px + // padding on either side of the input to push it out of alignment. + width: calc(100% - 20px); } .mx_EditableItemList_label { - margin-bottom: 8px; -} + margin-bottom: 5px; +} \ No newline at end of file diff --git a/src/components/views/elements/EditableItemList.js b/src/components/views/elements/EditableItemList.js index 7d96b1fd20..b87b2e78a5 100644 --- a/src/components/views/elements/EditableItemList.js +++ b/src/components/views/elements/EditableItemList.js @@ -1,5 +1,5 @@ /* -Copyright 2017 New Vector Ltd. +Copyright 2017, 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. @@ -18,140 +18,132 @@ import React from 'react'; import PropTypes from 'prop-types'; import sdk from '../../../index'; import {_t} from '../../../languageHandler.js'; +import Field from "./Field"; +import AccessibleButton from "./AccessibleButton"; -const EditableItem = React.createClass({ - displayName: 'EditableItem', - - propTypes: { - initialValue: PropTypes.string, +export class EditableItem extends React.Component { + static propTypes = { index: PropTypes.number, + value: PropTypes.string, + onRemove: PropTypes.func, + }; + + constructor() { + super(); + + this.state = { + verifyRemove: false, + }; + } + + _onRemove = (e) => { + e.stopPropagation(); + e.preventDefault(); + + this.setState({verifyRemove: true}); + }; + + _onDontRemove = (e) => { + e.stopPropagation(); + e.preventDefault(); + + this.setState({verifyRemove: false}); + }; + + _onActuallyRemove = (e) => { + e.stopPropagation(); + e.preventDefault(); + + if (this.props.onRemove) this.props.onRemove(this.props.index); + this.setState({verifyRemove: false}); + }; + + render() {if (this.state.verifyRemove) { + return ( +
+ + {_t("Are you sure?")} + + + {_t("Yes")} + + + {_t("No")} + +
+ ); + } + + return ( +
+ {_t("Remove")} + {this.props.value} +
+ ); + } +} + +export default class EditableItemList extends React.Component{ + static propTypes = { + items: PropTypes.arrayOf(PropTypes.string).isRequired, + itemsLabel: PropTypes.string, + noItemsLabel: PropTypes.string, placeholder: PropTypes.string, - onChange: PropTypes.func, - onRemove: PropTypes.func, - onAdd: PropTypes.func, - - addOnChange: PropTypes.bool, - }, - - onChange: function(value) { - this.setState({ value }); - if (this.props.onChange) this.props.onChange(value, this.props.index); - if (this.props.addOnChange && this.props.onAdd) this.props.onAdd(value); - }, - - onRemove: function() { - if (this.props.onRemove) this.props.onRemove(this.props.index); - }, - - onAdd: function() { - if (this.props.onAdd) this.props.onAdd(this.state.value); - }, - - render: function() { - const EditableText = sdk.getComponent('elements.EditableText'); - return
- - { this.props.onAdd ? -
- {_t("Add")} -
- : -
- {_t("Delete")} -
- } -
; - }, -}); - -// TODO: Make this use the new Field element -module.exports = React.createClass({ - displayName: 'EditableItemList', - - propTypes: { - items: PropTypes.arrayOf(PropTypes.string).isRequired, - onNewItemChanged: PropTypes.func, onItemAdded: PropTypes.func, - onItemEdited: PropTypes.func, onItemRemoved: PropTypes.func, canEdit: PropTypes.bool, - }, + }; - getDefaultProps: function() { - return { - onItemAdded: () => {}, - onItemEdited: () => {}, - onItemRemoved: () => {}, - onNewItemChanged: () => {}, - }; - }, + _onItemAdded = (e) => { + e.stopPropagation(); + e.preventDefault(); - onItemAdded: function(value) { - this.props.onItemAdded(value); - }, + if (!this.refs.newItem) return; - onItemEdited: function(value, index) { - if (value.length === 0) { - this.onItemRemoved(index); - } else { - this.props.onItemEdited(value, index); - } - }, + const value = this.refs.newItem.value; + if (this.props.onItemAdded) this.props.onItemAdded(value); + }; - onItemRemoved: function(index) { - this.props.onItemRemoved(index); - }, + _onItemRemoved = (index) => { + if (this.props.onItemRemoved) this.props.onItemRemoved(index); + }; - onNewItemChanged: function(value) { - this.props.onNewItemChanged(value); - }, + _renderNewItemField() { + return ( +
+ + + {_t("Add")} + + + ) + } - render: function() { + render() { const editableItems = this.props.items.map((item, index) => { return ; }); - const label = this.props.items.length > 0 ? - this.props.itemsLabel : this.props.noItemsLabel; + const label = this.props.items.length > 0 ? this.props.itemsLabel : this.props.noItemsLabel; return (
{ label }
{ editableItems } - { this.props.canEdit ? - // This is slightly evil; we want a new instance of - // EditableItem when the list grows. To make sure it's - // reset to its initial state. - :
- } + { this.props.canEdit ? this._renderNewItemField() :
}
); - }, -}); + } +} diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index 827656e770..a56b4903bc 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -259,21 +259,13 @@ module.exports = React.createClass({
{ _t("Remote addresses for this room:") }
-
+
    { this.state.remoteDomains.map((domain, i) => { - return this.state.domainToAliases[domain].map(function(alias, j) { - return ( -
    - -
    - ); + return this.state.domainToAliases[domain].map((alias, j) => { + return
  • {alias}
  • ; }); }) } -
+
); } From 5d2f17c49a7493acc5f1cd52b74bccd80c050282 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 20 Feb 2019 16:13:35 -0700 Subject: [PATCH 02/12] Make adding aliases direct manipulation --- .../views/elements/EditableItemList.js | 19 ++- .../views/room_settings/AliasSettings.js | 136 +++++++++--------- src/i18n/strings/en_EN.json | 5 +- 3 files changed, 78 insertions(+), 82 deletions(-) diff --git a/src/components/views/elements/EditableItemList.js b/src/components/views/elements/EditableItemList.js index b87b2e78a5..1fdae69edd 100644 --- a/src/components/views/elements/EditableItemList.js +++ b/src/components/views/elements/EditableItemList.js @@ -92,9 +92,11 @@ export default class EditableItemList extends React.Component{ itemsLabel: PropTypes.string, noItemsLabel: PropTypes.string, placeholder: PropTypes.string, + newItem: PropTypes.string, onItemAdded: PropTypes.func, onItemRemoved: PropTypes.func, + onNewItemChanged: PropTypes.func, canEdit: PropTypes.bool, }; @@ -103,22 +105,25 @@ export default class EditableItemList extends React.Component{ e.stopPropagation(); e.preventDefault(); - if (!this.refs.newItem) return; - - const value = this.refs.newItem.value; - if (this.props.onItemAdded) this.props.onItemAdded(value); + if (this.props.onItemAdded) this.props.onItemAdded(this.props.newItem); }; _onItemRemoved = (index) => { if (this.props.onItemRemoved) this.props.onItemRemoved(index); }; + _onNewItemChanged = (e) => { + if (this.props.onNewItemChanged) this.props.onNewItemChanged(e.target.value); + }; + _renderNewItemField() { return ( -
- + {_t("Add")} diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index a56b4903bc..2757c15515 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -1,6 +1,6 @@ /* Copyright 2016 OpenMarket Ltd -Copyright 2018 New Vector Ltd +Copyright 2018, 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. @@ -23,32 +23,32 @@ const MatrixClientPeg = require('../../../MatrixClientPeg'); const sdk = require("../../../index"); import { _t } from '../../../languageHandler'; import Field from "../elements/Field"; +import ErrorDialog from "../dialogs/ErrorDialog"; const Modal = require("../../../Modal"); -module.exports = React.createClass({ - displayName: 'AliasSettings', - - propTypes: { +export default class AliasSettings extends React.Component { + static propTypes = { roomId: PropTypes.string.isRequired, canSetCanonicalAlias: PropTypes.bool.isRequired, canSetAliases: PropTypes.bool.isRequired, aliasEvents: PropTypes.array, // [MatrixEvent] canonicalAliasEvent: PropTypes.object, // MatrixEvent - }, + }; - getDefaultProps: function() { - return { - canSetAliases: false, - canSetCanonicalAlias: false, - aliasEvents: [], - }; - }, + static defaultProps = { + canSetAliases: false, + canSetCanonicalAlias: false, + aliasEvents: [], + }; - getInitialState: function() { - return this.recalculateState(this.props.aliasEvents, this.props.canonicalAliasEvent); - }, + constructor(props) { + super(props); - recalculateState: function(aliasEvents, canonicalAliasEvent) { + const aliasState = this.recalculateState(props.aliasEvents, props.canonicalAliasEvent); + this.state = Object.assign({newItem: ""}, aliasState); + } + + recalculateState(aliasEvents, canonicalAliasEvent) { aliasEvents = aliasEvents || []; const state = { @@ -69,9 +69,9 @@ module.exports = React.createClass({ } return state; - }, + } - saveSettings: function() { + saveSettings() { let promises = []; // save new aliases for m.room.aliases @@ -118,9 +118,9 @@ module.exports = React.createClass({ } return promises; - }, + } - aliasEventsToDictionary: function(aliasEvents) { // m.room.alias events + aliasEventsToDictionary(aliasEvents) { // m.room.alias events const dict = {}; aliasEvents.forEach((event) => { dict[event.getStateKey()] = ( @@ -128,35 +128,53 @@ module.exports = React.createClass({ ); }); return dict; - }, + } - isAliasValid: function(alias) { + isAliasValid(alias) { // XXX: FIXME https://github.com/matrix-org/matrix-doc/issues/668 return (alias.match(/^#([^\/:,]+?):(.+)$/) && encodeURI(alias) === alias); - }, + } - getAliasOperations: function() { + getAliasOperations() { const oldAliases = this.aliasEventsToDictionary(this.props.aliasEvents); return ObjectUtils.getKeyValueArrayDiffs(oldAliases, this.state.domainToAliases); - }, + } - onNewAliasChanged: function(value) { + onNewAliasChanged = (value) => { this.setState({newAlias: value}); - }, + }; - onLocalAliasAdded: function(alias) { + onLocalAliasAdded = (alias) => { if (!alias || alias.length === 0) return; // ignore attempts to create blank aliases const localDomain = MatrixClientPeg.get().getDomain(); if (!alias.includes(':')) alias += ':' + localDomain; if (this.isAliasValid(alias) && alias.endsWith(localDomain)) { - this.state.domainToAliases[localDomain] = this.state.domainToAliases[localDomain] || []; - this.state.domainToAliases[localDomain].push(alias); + MatrixClientPeg.get().createAlias(alias, this.props.roomId).then(() => { + const localAliases = this.state.domainToAliases[localDomain] || []; + const domainAliases = Object.assign({}, this.state.domainToAliases); + domainAliases[localDomain] = [...localAliases, alias]; - this.setState({ - domainToAliases: this.state.domainToAliases, - // Reset the add field - newAlias: "", + this.setState({ + domainToAliases: domainAliases, + // Reset the add field + newAlias: "", + }); + + if (!this.props.canonicalAlias) { + this.setState({ + canonicalAlias: alias, + }); + } + }).catch((err) => { + console.error(err); + Modal.createTrackedDialog('Error creating alias', '', ErrorDialog, { + title: _t("Error creating alias"), + description: _t( + "There was an error creating that alias. It may not be allowed by the server " + + "or a temporary failure occurred." + ), + }); }); } else { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); @@ -165,30 +183,9 @@ module.exports = React.createClass({ description: _t('\'%(alias)s\' is not a valid format for an alias', { alias: alias }), }); } + }; - if (!this.props.canonicalAlias) { - this.setState({ - canonicalAlias: alias, - }); - } - }, - - onLocalAliasChanged: function(alias, index) { - if (alias === "") return; // hit the delete button to delete please - const localDomain = MatrixClientPeg.get().getDomain(); - if (!alias.includes(':')) alias += ':' + localDomain; - if (this.isAliasValid(alias) && alias.endsWith(localDomain)) { - this.state.domainToAliases[localDomain][index] = alias; - } else { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Invalid address format', '', ErrorDialog, { - title: _t('Invalid address format'), - description: _t('\'%(alias)s\' is not a valid format for an address', { alias: alias }), - }); - } - }, - - onLocalAliasDeleted: function(index) { + onLocalAliasDeleted = (index) => { const localDomain = MatrixClientPeg.get().getDomain(); // It's a bit naughty to directly manipulate this.state, and React would // normally whine at you, but it can't see us doing the splice. Given we @@ -204,17 +201,15 @@ module.exports = React.createClass({ canonicalAlias: null, }); } - }, + }; - onCanonicalAliasChange: function(event) { + onCanonicalAliasChange = (event) => { this.setState({ canonicalAlias: event.target.value, }); - }, + }; - render: function() { - const self = this; - const EditableText = sdk.getComponent("elements.EditableText"); + render() { const EditableItemList = sdk.getComponent("elements.EditableItemList"); const localDomain = MatrixClientPeg.get().getDomain(); @@ -227,8 +222,8 @@ module.exports = React.createClass({ element='select' id='canonicalAlias' label={_t('Main address')}> { - Object.keys(self.state.domainToAliases).map((domain, i) => { - return self.state.domainToAliases[domain].map((alias, j) => { + Object.keys(this.state.domainToAliases).map((domain, i) => { + return this.state.domainToAliases[domain].map((alias, j) => { if (alias === this.state.canonicalAlias) found = true; return ( @@ -280,7 +275,6 @@ module.exports = React.createClass({ onNewItemChanged={this.onNewAliasChanged} canEdit={this.props.canSetAliases} onItemAdded={this.onLocalAliasAdded} - onItemEdited={this.onLocalAliasChanged} onItemRemoved={this.onLocalAliasDeleted} itemsLabel={_t('Local addresses for this room:')} noItemsLabel={_t('This room has no local addresses')} @@ -288,10 +282,8 @@ module.exports = React.createClass({ 'New address (e.g. #foo:%(localDomain)s)', {localDomain: localDomain}, )} /> - { remote_aliases_section } -
); - }, -}); + } +}; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0048636411..f322c56bd9 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -804,10 +804,10 @@ "Hide Stickers": "Hide Stickers", "Show Stickers": "Show Stickers", "Jump to first unread message.": "Jump to first unread message.", + "Error creating alias": "Error creating alias", + "There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.": "There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.", "Invalid alias format": "Invalid alias format", "'%(alias)s' is not a valid format for an alias": "'%(alias)s' is not a valid format for an alias", - "Invalid address format": "Invalid address format", - "'%(alias)s' is not a valid format for an address": "'%(alias)s' is not a valid format for an address", "Main address": "Main address", "not specified": "not specified", "not set": "not set", @@ -925,7 +925,6 @@ "Verify...": "Verify...", "Join": "Join", "No results": "No results", - "Delete": "Delete", "Communities": "Communities", "Home": "Home", "You cannot delete this image. (%(code)s)": "You cannot delete this image. (%(code)s)", From 278e48cfc98113e92d529b74f2a44a1750ef00cc Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 20 Feb 2019 20:44:08 -0700 Subject: [PATCH 03/12] Make removing aliases direct manipulation --- .../views/room_settings/AliasSettings.js | 62 +++++++++++-------- src/i18n/strings/en_EN.json | 2 + 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index 2757c15515..97f08a8cf2 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -155,17 +155,18 @@ export default class AliasSettings extends React.Component { const domainAliases = Object.assign({}, this.state.domainToAliases); domainAliases[localDomain] = [...localAliases, alias]; - this.setState({ + const newState = { domainToAliases: domainAliases, // Reset the add field newAlias: "", - }); + }; + // TODO: How should we do direct manipulation for this? if (!this.props.canonicalAlias) { - this.setState({ - canonicalAlias: alias, - }); + newState.canonicalAlias = alias; } + + this.setState(newState); }).catch((err) => { console.error(err); Modal.createTrackedDialog('Error creating alias', '', ErrorDialog, { @@ -187,20 +188,31 @@ export default class AliasSettings extends React.Component { onLocalAliasDeleted = (index) => { const localDomain = MatrixClientPeg.get().getDomain(); - // It's a bit naughty to directly manipulate this.state, and React would - // normally whine at you, but it can't see us doing the splice. Given we - // promptly setState anyway, it's just about acceptable. The alternative - // would be to arbitrarily deepcopy to a temp variable and then setState - // that, but why bother when we can cut this corner. - const alias = this.state.domainToAliases[localDomain].splice(index, 1); - this.setState({ - domainToAliases: this.state.domainToAliases, - }); - if (this.props.canonicalAlias === alias) { - this.setState({ - canonicalAlias: null, + + const alias = this.state.domainToAliases[localDomain][index]; + + // TODO: In future, we should probably be making sure that the alias actually belongs + // to this room. See https://github.com/vector-im/riot-web/issues/7353 + MatrixClientPeg.get().deleteAlias(alias).then(() => { + const localAliases = this.state.domainToAliases[localDomain].filter((a) => a !== alias); + const domainAliases = Object.assign({}, this.state.domainToAliases); + domainAliases[localDomain] = localAliases; + + const newState = {domainToAliases: domainAliases}; + if (this.props.canonicalAlias === alias) { + newState.canonicalAlias = null; + } + this.setState(newState); + }).catch((err) => { + console.error(err); + Modal.createTrackedDialog('Error removing alias', '', ErrorDialog, { + title: _t("Error removing alias"), + description: _t( + "There was an error removing that alias. It may no longer exist or a temporary " + + "error occurred." + ), }); - } + }); }; onCanonicalAliasChange = (event) => { @@ -213,11 +225,11 @@ export default class AliasSettings extends React.Component { const EditableItemList = sdk.getComponent("elements.EditableItemList"); const localDomain = MatrixClientPeg.get().getDomain(); - let canonical_alias_section; + let canonicalAliasSection; if (this.props.canSetCanonicalAlias) { let found = false; const canonicalValue = this.state.canonicalAlias || ""; - canonical_alias_section = ( + canonicalAliasSection = ( @@ -242,14 +254,14 @@ export default class AliasSettings extends React.Component { ); } else { - canonical_alias_section = ( + canonicalAliasSection = ( { this.state.canonicalAlias || _t('not set') } ); } - let remote_aliases_section; + let remoteAliasesSection; if (this.state.remoteDomains.length) { - remote_aliases_section = ( + remoteAliasesSection = (
{ _t("Remote addresses for this room:") } @@ -267,7 +279,7 @@ export default class AliasSettings extends React.Component { return (
- {canonical_alias_section} + {canonicalAliasSection} - { remote_aliases_section } + {remoteAliasesSection}
); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f322c56bd9..d373076eb7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -808,6 +808,8 @@ "There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.": "There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.", "Invalid alias format": "Invalid alias format", "'%(alias)s' is not a valid format for an alias": "'%(alias)s' is not a valid format for an alias", + "Error removing alias": "Error removing alias", + "There was an error removing that alias. It may no longer exist or a temporary error occurred.": "There was an error removing that alias. It may no longer exist or a temporary error occurred.", "Main address": "Main address", "not specified": "not specified", "not set": "not set", From 750c9202ccc5d63059d360e43f80cd24ed97d842 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Feb 2019 16:11:09 -0700 Subject: [PATCH 04/12] Make the canonical alias direct manipulation --- .../views/room_settings/AliasSettings.js | 53 +++++++++++++------ src/i18n/strings/en_EN.json | 2 + 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index 97f08a8cf2..ee4fc73593 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -55,6 +55,7 @@ export default class AliasSettings extends React.Component { domainToAliases: {}, // { domain.com => [#alias1:domain.com, #alias2:domain.com] } remoteDomains: [], // [ domain.com, foobar.com ] canonicalAlias: null, // #canonical:domain.com + updatingCanonicalAlias: false, }; const localDomain = MatrixClientPeg.get().getDomain(); @@ -140,6 +141,32 @@ export default class AliasSettings extends React.Component { return ObjectUtils.getKeyValueArrayDiffs(oldAliases, this.state.domainToAliases); } + changeCanonicalAlias(alias) { + if (!this.props.canSetCanonicalAlias) return; + + this.setState({ + canonicalAlias: alias, + updatingCanonicalAlias: true, + }); + + const eventContent = {}; + if (alias) eventContent["alias"] = alias; + + MatrixClientPeg.get().sendStateEvent(this.props.roomId, "m.room.canonical_alias", + eventContent, "").catch((err) => { + console.error(err); + Modal.createTrackedDialog('Error updating main address', '', ErrorDialog, { + title: _t("Error updating main address"), + description: _t( + "There was an error updating the room's main address. It may not be allowed by the server " + + "or a temporary failure occurred." + ), + }); + }).finally(() => { + this.setState({updatingCanonicalAlias: false}); + }); + } + onNewAliasChanged = (value) => { this.setState({newAlias: value}); }; @@ -155,18 +182,15 @@ export default class AliasSettings extends React.Component { const domainAliases = Object.assign({}, this.state.domainToAliases); domainAliases[localDomain] = [...localAliases, alias]; - const newState = { + this.setState({ domainToAliases: domainAliases, // Reset the add field newAlias: "", - }; + }); - // TODO: How should we do direct manipulation for this? - if (!this.props.canonicalAlias) { - newState.canonicalAlias = alias; + if (!this.state.canonicalAlias) { + this.changeCanonicalAlias(alias); } - - this.setState(newState); }).catch((err) => { console.error(err); Modal.createTrackedDialog('Error creating alias', '', ErrorDialog, { @@ -198,11 +222,11 @@ export default class AliasSettings extends React.Component { const domainAliases = Object.assign({}, this.state.domainToAliases); domainAliases[localDomain] = localAliases; - const newState = {domainToAliases: domainAliases}; - if (this.props.canonicalAlias === alias) { - newState.canonicalAlias = null; + this.setState({domainToAliases: domainAliases}); + + if (this.state.canonicalAlias === alias) { + this.changeCanonicalAlias(null); } - this.setState(newState); }).catch((err) => { console.error(err); Modal.createTrackedDialog('Error removing alias', '', ErrorDialog, { @@ -216,9 +240,7 @@ export default class AliasSettings extends React.Component { }; onCanonicalAliasChange = (event) => { - this.setState({ - canonicalAlias: event.target.value, - }); + this.changeCanonicalAlias(event.target.value); }; render() { @@ -231,7 +253,8 @@ export default class AliasSettings extends React.Component { const canonicalValue = this.state.canonicalAlias || ""; canonicalAliasSection = ( + disabled={this.state.updatingCanonicalAlias} element='select' + id='canonicalAlias' label={_t('Main address')}> { Object.keys(this.state.domainToAliases).map((domain, i) => { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d373076eb7..b74f14a99e 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -804,6 +804,8 @@ "Hide Stickers": "Hide Stickers", "Show Stickers": "Show Stickers", "Jump to first unread message.": "Jump to first unread message.", + "Error updating main address": "Error updating main address", + "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.", "Error creating alias": "Error creating alias", "There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.": "There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.", "Invalid alias format": "Invalid alias format", From 2990cf41e7d6b3886e22f206273ec20b97c3c9f5 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Feb 2019 16:27:49 -0700 Subject: [PATCH 05/12] Remove old "click to save" functionality from AliasSettings --- .../views/room_settings/AliasSettings.js | 73 ++----------------- .../settings/tabs/GeneralRoomSettingsTab.js | 11 +-- 2 files changed, 7 insertions(+), 77 deletions(-) diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index ee4fc73593..d6a6b608e2 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -44,81 +44,25 @@ export default class AliasSettings extends React.Component { constructor(props) { super(props); - const aliasState = this.recalculateState(props.aliasEvents, props.canonicalAliasEvent); - this.state = Object.assign({newItem: ""}, aliasState); - } - - recalculateState(aliasEvents, canonicalAliasEvent) { - aliasEvents = aliasEvents || []; - const state = { domainToAliases: {}, // { domain.com => [#alias1:domain.com, #alias2:domain.com] } remoteDomains: [], // [ domain.com, foobar.com ] canonicalAlias: null, // #canonical:domain.com updatingCanonicalAlias: false, + newItem: "", }; + const localDomain = MatrixClientPeg.get().getDomain(); - - state.domainToAliases = this.aliasEventsToDictionary(aliasEvents); - + state.domainToAliases = this.aliasEventsToDictionary(props.aliasEvents || []); state.remoteDomains = Object.keys(state.domainToAliases).filter((domain) => { return domain !== localDomain && state.domainToAliases[domain].length > 0; }); - if (canonicalAliasEvent) { - state.canonicalAlias = canonicalAliasEvent.getContent().alias; + if (props.canonicalAliasEvent) { + state.canonicalAlias = props.canonicalAliasEvent.getContent().alias; } - return state; - } - - saveSettings() { - let promises = []; - - // save new aliases for m.room.aliases - const aliasOperations = this.getAliasOperations(); - for (let i = 0; i < aliasOperations.length; i++) { - const alias_operation = aliasOperations[i]; - console.log("alias %s %s", alias_operation.place, alias_operation.val); - switch (alias_operation.place) { - case 'add': - promises.push( - MatrixClientPeg.get().createAlias( - alias_operation.val, this.props.roomId, - ), - ); - break; - case 'del': - promises.push( - MatrixClientPeg.get().deleteAlias( - alias_operation.val, - ), - ); - break; - default: - console.log("Unknown alias operation, ignoring: " + alias_operation.place); - } - } - - let oldCanonicalAlias = null; - if (this.props.canonicalAliasEvent) { - oldCanonicalAlias = this.props.canonicalAliasEvent.getContent().alias; - } - - const newCanonicalAlias = this.state.canonicalAlias; - - if (this.props.canSetCanonicalAlias && oldCanonicalAlias !== newCanonicalAlias) { - console.log("AliasSettings: Updating canonical alias"); - promises = [Promise.all(promises).then( - MatrixClientPeg.get().sendStateEvent( - this.props.roomId, "m.room.canonical_alias", { - alias: newCanonicalAlias, - }, "", - ), - )]; - } - - return promises; + this.state = state; } aliasEventsToDictionary(aliasEvents) { // m.room.alias events @@ -136,11 +80,6 @@ export default class AliasSettings extends React.Component { return (alias.match(/^#([^\/:,]+?):(.+)$/) && encodeURI(alias) === alias); } - getAliasOperations() { - const oldAliases = this.aliasEventsToDictionary(this.props.aliasEvents); - return ObjectUtils.getKeyValueArrayDiffs(oldAliases, this.state.domainToAliases); - } - changeCanonicalAlias(alias) { if (!this.props.canSetCanonicalAlias) return; diff --git a/src/components/views/settings/tabs/GeneralRoomSettingsTab.js b/src/components/views/settings/tabs/GeneralRoomSettingsTab.js index 75373a69bc..0ce2c174e4 100644 --- a/src/components/views/settings/tabs/GeneralRoomSettingsTab.js +++ b/src/components/views/settings/tabs/GeneralRoomSettingsTab.js @@ -68,12 +68,6 @@ export default class GeneralRoomSettingsTab extends React.Component { }); }; - _saveAliases = (e) => { - // TODO: Live modification? - if (!this.refs.aliasSettings) return; - this.refs.aliasSettings.saveSettings(); - }; - _saveGroups = (e) => { // TODO: Live modification? if (!this.refs.flairSettings) return; @@ -113,12 +107,9 @@ export default class GeneralRoomSettingsTab extends React.Component { {_t("Room Addresses")}
- - - {_t("Save")} -
Date: Thu, 21 Feb 2019 16:33:02 -0700 Subject: [PATCH 06/12] Disable main address dropdown when lacking permissions Instead of rendering it as a span with no label. --- .../views/room_settings/AliasSettings.js | 61 ++++++++----------- src/i18n/strings/en_EN.json | 1 - 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index d6a6b608e2..4859174b28 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -186,40 +186,33 @@ export default class AliasSettings extends React.Component { const EditableItemList = sdk.getComponent("elements.EditableItemList"); const localDomain = MatrixClientPeg.get().getDomain(); - let canonicalAliasSection; - if (this.props.canSetCanonicalAlias) { - let found = false; - const canonicalValue = this.state.canonicalAlias || ""; - canonicalAliasSection = ( - - - { - Object.keys(this.state.domainToAliases).map((domain, i) => { - return this.state.domainToAliases[domain].map((alias, j) => { - if (alias === this.state.canonicalAlias) found = true; - return ( - - ); - }); - }) - } - { - found || !this.state.canonicalAlias ? '' : - - } - - ); - } else { - canonicalAliasSection = ( - { this.state.canonicalAlias || _t('not set') } - ); - } + let found = false; + const canonicalValue = this.state.canonicalAlias || ""; + const canonicalAliasSection = ( + + + { + Object.keys(this.state.domainToAliases).map((domain, i) => { + return this.state.domainToAliases[domain].map((alias, j) => { + if (alias === this.state.canonicalAlias) found = true; + return ( + + ); + }); + }) + } + { + found || !this.state.canonicalAlias ? '' : + + } + + ); let remoteAliasesSection; if (this.state.remoteDomains.length) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b74f14a99e..c32b604f7e 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -814,7 +814,6 @@ "There was an error removing that alias. It may no longer exist or a temporary error occurred.": "There was an error removing that alias. It may no longer exist or a temporary error occurred.", "Main address": "Main address", "not specified": "not specified", - "not set": "not set", "Remote addresses for this room:": "Remote addresses for this room:", "Local addresses for this room:": "Local addresses for this room:", "This room has no local addresses": "This room has no local addresses", From 10daa35263eb88b539cb41aec4cdd73e1a44dc3e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Feb 2019 16:40:12 -0700 Subject: [PATCH 07/12] Make related groups direct manipulation --- .../room_settings/RelatedGroupSettings.js | 48 +++++++------------ .../settings/tabs/GeneralRoomSettingsTab.js | 11 +---- 2 files changed, 19 insertions(+), 40 deletions(-) diff --git a/src/components/views/room_settings/RelatedGroupSettings.js b/src/components/views/room_settings/RelatedGroupSettings.js index 91a538ca93..51268426d8 100644 --- a/src/components/views/room_settings/RelatedGroupSettings.js +++ b/src/components/views/room_settings/RelatedGroupSettings.js @@ -1,5 +1,5 @@ /* -Copyright 2017 New Vector Ltd. +Copyright 2017, 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. @@ -21,6 +21,7 @@ import sdk from '../../../index'; import { _t } from '../../../languageHandler'; import Modal from '../../../Modal'; import isEqual from 'lodash/isEqual'; +import ErrorDialog from "../dialogs/ErrorDialog"; const GROUP_ID_REGEX = /\+\S+:\S+/; @@ -46,7 +47,7 @@ module.exports = React.createClass({ getInitialState: function() { return { newGroupsList: this.getInitialGroupList(), - newGroupId: null, + newGroupId: "", }; }, @@ -54,24 +55,19 @@ module.exports = React.createClass({ return this.props.relatedGroupsEvent ? (this.props.relatedGroupsEvent.getContent().groups || []) : []; }, - needsSaving: function() { - const cli = this.context.matrixClient; - const room = cli.getRoom(this.props.roomId); - if (!room.currentState.maySendStateEvent('m.room.related_groups', cli.getUserId())) return false; - return !isEqual(this.getInitialGroupList(), this.state.newGroupsList); - }, - - saveSettings: function() { - if (!this.needsSaving()) return Promise.resolve(); - - return this.context.matrixClient.sendStateEvent( - this.props.roomId, - 'm.room.related_groups', - { - groups: this.state.newGroupsList, - }, - '', - ); + updateGroups: function() { + this.context.matrixClient.sendStateEvent(this.props.roomId, 'm.room.related_groups', { + groups: this.state.newGroupsList, + }, '').catch((err) => { + console.error(err); + Modal.createTrackedDialog('Error updating flair', '', ErrorDialog, { + title: _t("Error updating flair"), + description: _t( + "There was an error updating the flair for this room. The server may not allow it or " + + "a temporary error occurred." + ), + }); + }) }, validateGroupId: function(groupId) { @@ -98,21 +94,14 @@ module.exports = React.createClass({ newGroupsList: this.state.newGroupsList.concat([groupId]), newGroupId: '', }); - }, - - onGroupEdited: function(groupId, index) { - if (groupId.length === 0 || !this.validateGroupId(groupId)) { - return; - } - this.setState({ - newGroupsList: Object.assign(this.state.newGroupsList, {[index]: groupId}), - }); + this.updateGroups(); }, onGroupDeleted: function(index) { const newGroupsList = this.state.newGroupsList.slice(); newGroupsList.splice(index, 1); this.setState({ newGroupsList }); + this.updateGroups(); }, render: function() { @@ -126,7 +115,6 @@ module.exports = React.createClass({ canEdit={this.props.canSetRelatedGroups} onNewItemChanged={this.onNewGroupChanged} onItemAdded={this.onGroupAdded} - onItemEdited={this.onGroupEdited} onItemRemoved={this.onGroupDeleted} itemsLabel={_t('Showing flair for these communities:')} noItemsLabel={_t('This room is not showing flair for any communities')} diff --git a/src/components/views/settings/tabs/GeneralRoomSettingsTab.js b/src/components/views/settings/tabs/GeneralRoomSettingsTab.js index 0ce2c174e4..f43fc8a682 100644 --- a/src/components/views/settings/tabs/GeneralRoomSettingsTab.js +++ b/src/components/views/settings/tabs/GeneralRoomSettingsTab.js @@ -68,12 +68,6 @@ export default class GeneralRoomSettingsTab extends React.Component { }); }; - _saveGroups = (e) => { - // TODO: Live modification? - if (!this.refs.flairSettings) return; - this.refs.flairSettings.saveSettings(); - }; - _onLeaveClick = () => { dis.dispatch({ action: 'leave_room', @@ -122,12 +116,9 @@ export default class GeneralRoomSettingsTab extends React.Component { {_t("Flair")}
- - - {_t("Save")} -
{_t("URL Previews")} From 96eeab23af4321102c9db9a6c09053ab4df95521 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Feb 2019 16:45:57 -0700 Subject: [PATCH 08/12] Convert RelatedGroupSettings to a class component --- .../room_settings/RelatedGroupSettings.js | 73 +++++++++---------- src/i18n/strings/en_EN.json | 2 + 2 files changed, 36 insertions(+), 39 deletions(-) diff --git a/src/components/views/room_settings/RelatedGroupSettings.js b/src/components/views/room_settings/RelatedGroupSettings.js index 51268426d8..3381bdd0ff 100644 --- a/src/components/views/room_settings/RelatedGroupSettings.js +++ b/src/components/views/room_settings/RelatedGroupSettings.js @@ -25,39 +25,33 @@ import ErrorDialog from "../dialogs/ErrorDialog"; const GROUP_ID_REGEX = /\+\S+:\S+/; -module.exports = React.createClass({ - displayName: 'RelatedGroupSettings', - - propTypes: { +export default class RelatedGroupSettings extends React.Component { + static propTypes = { roomId: PropTypes.string.isRequired, canSetRelatedGroups: PropTypes.bool.isRequired, relatedGroupsEvent: PropTypes.instanceOf(MatrixEvent), - }, + }; - contextTypes: { + static contextTypes = { matrixClient: PropTypes.instanceOf(MatrixClient), - }, + }; - getDefaultProps: function() { - return { - canSetRelatedGroups: false, - }; - }, + static defaultProps = { + canSetRelatedGroups: false, + }; - getInitialState: function() { - return { - newGroupsList: this.getInitialGroupList(), + constructor(props) { + super(props); + + this.state = { newGroupId: "", + newGroupsList: props.relatedGroupsEvent ? (props.relatedGroupsEvent.getContent().groups || []) : [], }; - }, + } - getInitialGroupList: function() { - return this.props.relatedGroupsEvent ? (this.props.relatedGroupsEvent.getContent().groups || []) : []; - }, - - updateGroups: function() { + updateGroups(newGroupsList) { this.context.matrixClient.sendStateEvent(this.props.roomId, 'm.room.related_groups', { - groups: this.state.newGroupsList, + groups: newGroupsList, }, '').catch((err) => { console.error(err); Modal.createTrackedDialog('Error updating flair', '', ErrorDialog, { @@ -68,9 +62,9 @@ module.exports = React.createClass({ ), }); }) - }, + } - validateGroupId: function(groupId) { + validateGroupId(groupId) { if (!GROUP_ID_REGEX.test(groupId)) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Invalid related community ID', '', ErrorDialog, { @@ -80,31 +74,32 @@ module.exports = React.createClass({ return false; } return true; - }, + } - onNewGroupChanged: function(newGroupId) { + onNewGroupChanged = (newGroupId) => { this.setState({ newGroupId }); - }, + }; - onGroupAdded: function(groupId) { + onGroupAdded = (groupId) => { if (groupId.length === 0 || !this.validateGroupId(groupId)) { return; } + const newGroupsList = [...this.state.newGroupsList, groupId]; this.setState({ - newGroupsList: this.state.newGroupsList.concat([groupId]), + newGroupsList: newGroupsList, newGroupId: '', }); - this.updateGroups(); - }, + this.updateGroups(newGroupsList); + }; - onGroupDeleted: function(index) { - const newGroupsList = this.state.newGroupsList.slice(); - newGroupsList.splice(index, 1); + onGroupDeleted = (index) => { + const group = this.state.newGroupsList[index]; + const newGroupsList = this.state.newGroupsList.filter((g) => g !== group); this.setState({ newGroupsList }); - this.updateGroups(); - }, + this.updateGroups(newGroupsList); + }; - render: function() { + render() { const localDomain = this.context.matrixClient.getDomain(); const EditableItemList = sdk.getComponent('elements.EditableItemList'); return
@@ -123,5 +118,5 @@ module.exports = React.createClass({ )} />
; - }, -}); + } +}; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c32b604f7e..c292e6ee85 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -818,6 +818,8 @@ "Local addresses for this room:": "Local addresses for this room:", "This room has no local addresses": "This room has no local addresses", "New address (e.g. #foo:%(localDomain)s)": "New address (e.g. #foo:%(localDomain)s)", + "Error updating flair": "Error updating flair", + "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.": "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.", "Invalid community ID": "Invalid community ID", "'%(groupId)s' is not a valid community ID": "'%(groupId)s' is not a valid community ID", "Showing flair for these communities:": "Showing flair for these communities:", From 003d0eb0bf396e54956cf4795807aad243318541 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Feb 2019 17:03:15 -0700 Subject: [PATCH 09/12] Show changes to related groups (flair) in the timeline --- src/TextForEvent.js | 31 +++++++++++++++++++++++++ src/components/views/rooms/EventTile.js | 1 + src/i18n/strings/en_EN.json | 3 +++ 3 files changed, 35 insertions(+) diff --git a/src/TextForEvent.js b/src/TextForEvent.js index ac0af82ff1..030c346ccc 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -166,6 +166,36 @@ function textForGuestAccessEvent(ev) { } } +function textForRelatedGroupsEvent(ev) { + const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); + const groups = ev.getContent().groups || []; + const prevGroups = ev.getPrevContent().groups || []; + const added = groups.filter((g) => !prevGroups.includes(g)); + const removed = prevGroups.filter((g) => !groups.includes(g)); + + if (added.length && !removed.length) { + return _t('%(senderDisplayName)s enabled flair for %(groups)s in this room.', { + senderDisplayName, + groups: added.join(', '), + }); + } else if (!added.length && removed.length) { + return _t('%(senderDisplayName)s disabled flair for %(groups)s in this room.', { + senderDisplayName, + groups: removed.join(', '), + }); + } else if (added.length && removed.length) { + return _t('%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for ' + + '%(oldGroups)s in this room.', { + senderDisplayName, + newGroups: added.join(', '), + oldGroups: removed.join(', '), + }); + } else { + // Don't bother rendering this change (because there were no changes) + return ''; + } +} + function textForServerACLEvent(ev) { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const prevContent = ev.getPrevContent(); @@ -473,6 +503,7 @@ const stateHandlers = { 'm.room.tombstone': textForTombstoneEvent, 'm.room.join_rules': textForJoinRulesEvent, 'm.room.guest_access': textForGuestAccessEvent, + 'm.room.related_groups': textForRelatedGroupsEvent, 'im.vector.modular.widgets': textForWidgetEvent, }; diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 8e1fb5af9f..15b8357b24 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -65,6 +65,7 @@ const stateEventTileTypes = { 'm.room.tombstone': 'messages.TextualEvent', 'm.room.join_rules': 'messages.TextualEvent', 'm.room.guest_access': 'messages.TextualEvent', + 'm.room.related_groups': 'messages.TextualEvent', }; function getHandlerTile(ev) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c292e6ee85..9b456f6721 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -192,6 +192,9 @@ "%(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 enabled flair for %(groups)s in this room.": "%(senderDisplayName)s enabled flair for %(groups)s in this room.", + "%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s disabled flair for %(groups)s in this room.", + "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.", "%(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.", From 1e6594ceba06664d7484519ce33d76206cda0a7e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Feb 2019 17:15:25 -0700 Subject: [PATCH 10/12] Disable removal of items if the user doesn't have permission --- .../views/elements/EditableItemList.js | 39 +++++++++++-------- .../views/room_settings/AliasSettings.js | 1 + .../room_settings/RelatedGroupSettings.js | 1 + 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/components/views/elements/EditableItemList.js b/src/components/views/elements/EditableItemList.js index 1fdae69edd..66b5962426 100644 --- a/src/components/views/elements/EditableItemList.js +++ b/src/components/views/elements/EditableItemList.js @@ -58,28 +58,29 @@ export class EditableItem extends React.Component { this.setState({verifyRemove: false}); }; - render() {if (this.state.verifyRemove) { - return ( -
+ render() { + if (this.state.verifyRemove) { + return ( +
{_t("Are you sure?")} - - {_t("Yes")} - - - {_t("No")} - -
- ); - } + + {_t("Yes")} + + + {_t("No")} + +
+ ); + } return (
{_t("Remove")} + onClick={this._onRemove} className="mx_EditableItem_delete" alt={_t("Remove")}/> {this.props.value}
); @@ -99,6 +100,7 @@ export default class EditableItemList extends React.Component{ onNewItemChanged: PropTypes.func, canEdit: PropTypes.bool, + canRemove: PropTypes.bool, }; _onItemAdded = (e) => { @@ -133,6 +135,10 @@ export default class EditableItemList extends React.Component{ render() { const editableItems = this.props.items.map((item, index) => { + if (!this.props.canRemove) { + return
  • {item}
  • + } + return ; }); + const editableItemsSection = this.props.canRemove ? editableItems :
      {editableItems}
    ; const label = this.props.items.length > 0 ? this.props.itemsLabel : this.props.noItemsLabel; return (
    { label }
    - { editableItems } + { editableItemsSection } { this.props.canEdit ? this._renderNewItemField() :
    }
    ); } diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index 4859174b28..1e399ec01b 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -240,6 +240,7 @@ export default class AliasSettings extends React.Component { items={this.state.domainToAliases[localDomain] || []} newItem={this.state.newAlias} onNewItemChanged={this.onNewAliasChanged} + canRemove={this.props.canSetAliases} canEdit={this.props.canSetAliases} onItemAdded={this.onLocalAliasAdded} onItemRemoved={this.onLocalAliasDeleted} diff --git a/src/components/views/room_settings/RelatedGroupSettings.js b/src/components/views/room_settings/RelatedGroupSettings.js index 3381bdd0ff..64025b261e 100644 --- a/src/components/views/room_settings/RelatedGroupSettings.js +++ b/src/components/views/room_settings/RelatedGroupSettings.js @@ -107,6 +107,7 @@ export default class RelatedGroupSettings extends React.Component { items={this.state.newGroupsList} className={"mx_RelatedGroupSettings"} newItem={this.state.newGroupId} + canRemove={this.props.canSetRelatedGroups} canEdit={this.props.canSetRelatedGroups} onNewItemChanged={this.onNewGroupChanged} onItemAdded={this.onGroupAdded} From 9795161f40d17cacc4ac89b6115262e561d9dc10 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Feb 2019 17:53:29 -0700 Subject: [PATCH 11/12] Misc linter cleanup --- .eslintignore.errorfiles | 1 - src/components/views/elements/EditableItemList.js | 9 ++++----- src/components/views/room_settings/AliasSettings.js | 8 +++----- .../views/room_settings/RelatedGroupSettings.js | 7 +++---- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles index 4830f8235a..d5eeebf1f2 100644 --- a/.eslintignore.errorfiles +++ b/.eslintignore.errorfiles @@ -31,7 +31,6 @@ src/components/views/globals/UpdateCheckBar.js src/components/views/messages/MFileBody.js src/components/views/messages/RoomAvatarEvent.js src/components/views/messages/TextualBody.js -src/components/views/room_settings/AliasSettings.js src/components/views/room_settings/ColorSettings.js src/components/views/rooms/Autocomplete.js src/components/views/rooms/AuxPanel.js diff --git a/src/components/views/elements/EditableItemList.js b/src/components/views/elements/EditableItemList.js index 66b5962426..28c6e10612 100644 --- a/src/components/views/elements/EditableItemList.js +++ b/src/components/views/elements/EditableItemList.js @@ -16,7 +16,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import sdk from '../../../index'; import {_t} from '../../../languageHandler.js'; import Field from "./Field"; import AccessibleButton from "./AccessibleButton"; @@ -80,14 +79,14 @@ export class EditableItem extends React.Component { return (
    {_t("Remove")}/ + onClick={this._onRemove} className="mx_EditableItem_delete" alt={_t("Remove")} /> {this.props.value}
    ); } } -export default class EditableItemList extends React.Component{ +export default class EditableItemList extends React.Component { static propTypes = { items: PropTypes.arrayOf(PropTypes.string).isRequired, itemsLabel: PropTypes.string, @@ -130,13 +129,13 @@ export default class EditableItemList extends React.Component{ {_t("Add")} - ) + ); } render() { const editableItems = this.props.items.map((item, index) => { if (!this.props.canRemove) { - return
  • {item}
  • + return
  • {item}
  • ; } return { @@ -136,7 +134,7 @@ export default class AliasSettings extends React.Component { title: _t("Error creating alias"), description: _t( "There was an error creating that alias. It may not be allowed by the server " + - "or a temporary failure occurred." + "or a temporary failure occurred.", ), }); }); @@ -172,7 +170,7 @@ export default class AliasSettings extends React.Component { title: _t("Error removing alias"), description: _t( "There was an error removing that alias. It may no longer exist or a temporary " + - "error occurred." + "error occurred.", ), }); }); diff --git a/src/components/views/room_settings/RelatedGroupSettings.js b/src/components/views/room_settings/RelatedGroupSettings.js index 64025b261e..b42ae1a8d7 100644 --- a/src/components/views/room_settings/RelatedGroupSettings.js +++ b/src/components/views/room_settings/RelatedGroupSettings.js @@ -20,7 +20,6 @@ import {MatrixEvent, MatrixClient} from 'matrix-js-sdk'; import sdk from '../../../index'; import { _t } from '../../../languageHandler'; import Modal from '../../../Modal'; -import isEqual from 'lodash/isEqual'; import ErrorDialog from "../dialogs/ErrorDialog"; const GROUP_ID_REGEX = /\+\S+:\S+/; @@ -58,10 +57,10 @@ export default class RelatedGroupSettings extends React.Component { title: _t("Error updating flair"), description: _t( "There was an error updating the flair for this room. The server may not allow it or " + - "a temporary error occurred." + "a temporary error occurred.", ), }); - }) + }); } validateGroupId(groupId) { @@ -120,4 +119,4 @@ export default class RelatedGroupSettings extends React.Component { />
    ; } -}; +} From 75a2593523dc4aa97ad9b72a851b51d0fe23a142 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Feb 2019 18:18:12 -0700 Subject: [PATCH 12/12] More misc linter cleanup --- src/components/views/room_settings/AliasSettings.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index a2b125eb4f..df427171f1 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -75,7 +75,7 @@ export default class AliasSettings extends React.Component { isAliasValid(alias) { // XXX: FIXME https://github.com/matrix-org/matrix-doc/issues/668 - return (alias.match(/^#([^\/:,]+?):(.+)$/) && encodeURI(alias) === alias); + return (alias.match(/^#([^/:,]+?):(.+)$/) && encodeURI(alias) === alias); } changeCanonicalAlias(alias) { @@ -252,4 +252,4 @@ export default class AliasSettings extends React.Component {
    ); } -}; +}