From ee9558cb0b548afd37330ed24b63c6eddf315096 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 9 Mar 2020 16:26:43 +0100 Subject: [PATCH 01/11] change state from domainToAliases to local and alt aliases --- .../views/room_settings/AliasSettings.js | 163 ++++++------------ src/i18n/strings/en_EN.json | 2 +- 2 files changed, 55 insertions(+), 110 deletions(-) diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index a52fa09c87..79317d3d1b 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -87,91 +87,59 @@ export default class AliasSettings extends React.Component { super(props); const state = { - domainToAliases: {}, // { domain.com => [#alias1:domain.com, #alias2:domain.com] } - remoteDomains: [], // [ domain.com, foobar.com ] - canonicalAlias: null, // #canonical:domain.com + altAliases: [], // [ #alias:domain.tld, ... ] + localAliases: [], // [ #alias:my-hs.tld, ... ] + canonicalAlias: null, // #canonical:domain.tld updatingCanonicalAlias: false, - localAliasesLoading: true, + localAliasesLoading: false, }; if (props.canonicalAliasEvent) { - state.canonicalAlias = props.canonicalAliasEvent.getContent().alias; + const content = props.canonicalAliasEvent.getContent(); + const altAliases = content.alt_aliases; + if (Array.isArray(altAliases)) { + state.altAliases = altAliases.slice(); + } + state.canonicalAlias = content.alias; } this.state = state; } - async componentWillMount() { - const cli = MatrixClientPeg.get(); + componentDidMount() { + return this.loadLocalAliases(); + } + + async loadLocalAliases() { + this.setState({ localAliasesLoading: true }); try { + const cli = MatrixClientPeg.get(); + let localAliases = []; if (await cli.doesServerSupportUnstableFeature("org.matrix.msc2432")) { const response = await cli.unstableGetLocalAliases(this.props.roomId); - const localAliases = response.aliases; - const localDomain = cli.getDomain(); - const domainToAliases = Object.assign( - {}, - // FIXME, any localhost alt_aliases will be ignored as they are overwritten by localAliases - this.aliasesToDictionary(this._getAltAliases()), - {[localDomain]: localAliases || []}, - ); - const remoteDomains = Object.keys(domainToAliases).filter((domain) => { - return domain !== localDomain && domainToAliases[domain].length > 0; - }); - this.setState({ domainToAliases, remoteDomains }); - } else { - const state = {}; - const localDomain = cli.getDomain(); - state.domainToAliases = this.aliasEventsToDictionary(this.props.aliasEvents || []); - state.remoteDomains = Object.keys(state.domainToAliases).filter((domain) => { - return domain !== localDomain && state.domainToAliases[domain].length > 0; - }); - this.setState(state); + if (Array.isArray(response.aliases)) { + localAliases = response.aliases; + } } + this.setState({ localAliases }); } finally { - this.setState({localAliasesLoading: false}); + this.setState({ localAliasesLoading: false }); } } - aliasesToDictionary(aliases) { - return aliases.reduce((dict, alias) => { - const domain = alias.split(":")[1]; - dict[domain] = dict[domain] || []; - dict[domain].push(alias); - return dict; - }, {}); - } - - aliasEventsToDictionary(aliasEvents) { // m.room.alias events - const dict = {}; - aliasEvents.forEach((event) => { - dict[event.getStateKey()] = ( - (event.getContent().aliases || []).slice() // shallow-copy - ); - }); - return dict; - } - - _getAltAliases() { - if (this.props.canonicalAliasEvent) { - const altAliases = this.props.canonicalAliasEvent.getContent().alt_aliases; - if (Array.isArray(altAliases)) { - return altAliases; - } - } - return []; - } - changeCanonicalAlias(alias) { if (!this.props.canSetCanonicalAlias) return; + const oldAlias = this.state.canonicalAlias; this.setState({ canonicalAlias: alias, updatingCanonicalAlias: true, }); - const eventContent = {}; - const altAliases = this._getAltAliases(); - if (altAliases) eventContent["alt_aliases"] = altAliases; + const eventContent = { + alt_aliases: this.state.altAliases, + }; + if (alias) eventContent["alias"] = alias; MatrixClientPeg.get().sendStateEvent(this.props.roomId, "m.room.canonical_alias", @@ -184,6 +152,7 @@ export default class AliasSettings extends React.Component { "or a temporary failure occurred.", ), }); + this.setState({canonicalAlias: oldAlias}); }).finally(() => { this.setState({updatingCanonicalAlias: false}); }); @@ -200,16 +169,10 @@ export default class AliasSettings extends React.Component { if (!alias.includes(':')) alias += ':' + localDomain; 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: domainAliases, - // Reset the add field - newAlias: "", + localAliases: this.state.localAliases.concat(alias), + newAlias: null, }); - if (!this.state.canonicalAlias) { this.changeCanonicalAlias(alias); } @@ -226,18 +189,13 @@ export default class AliasSettings extends React.Component { }; onLocalAliasDeleted = (index) => { - const localDomain = MatrixClientPeg.get().getDomain(); - - const alias = this.state.domainToAliases[localDomain][index]; - + const alias = this.state.localAliases[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; - - this.setState({domainToAliases: domainAliases}); + const localAliases = this.state.localAliases.slice(); + localAliases.splice(index); + this.setState({localAliases}); if (this.state.canonicalAlias === alias) { this.changeCanonicalAlias(null); @@ -258,6 +216,15 @@ export default class AliasSettings extends React.Component { this.changeCanonicalAlias(event.target.value); }; + _getAliases() { + return this.state.altAliases.concat(this._getLocalNonAltAliases()); + } + + _getLocalNonAltAliases() { + const {altAliases} = this.state; + return this.state.localAliases.filter(alias => !altAliases.includes(alias)); + } + render() { const localDomain = MatrixClientPeg.get().getDomain(); @@ -269,15 +236,13 @@ export default class AliasSettings extends React.Component { element='select' id='canonicalAlias' label={_t('Main address')}> { - Object.keys(this.state.domainToAliases).map((domain, i) => { - return this.state.domainToAliases[domain].map((alias, j) => { - if (alias === this.state.canonicalAlias) found = true; - return ( - - ); - }); + this._getAliases().map((alias, i) => { + if (alias === this.state.canonicalAlias) found = true; + return ( + + ); }) } { @@ -289,53 +254,33 @@ export default class AliasSettings extends React.Component { ); - let remoteAliasesSection; - if (this.state.remoteDomains.length) { - remoteAliasesSection = ( -
-
- { _t("Remote addresses for this room:") } -
- -
- ); - } - let localAliasesList; if (this.state.localAliasesLoading) { const Spinner = sdk.getComponent("elements.Spinner"); localAliasesList = ; } else { - localAliasesList = ; + />); } return (
{canonicalAliasSection} {localAliasesList} - {remoteAliasesSection}
); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 01ba458a2f..ccf232d86d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1149,10 +1149,10 @@ "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", - "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", "New address (e.g. #foo:%(localDomain)s)": "New address (e.g. #foo:%(localDomain)s)", + "New address (e.g. #foo:domain)": "New address (e.g. #foo:domain)", "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", From 38e99f3a68c3debfa56a732835346c40d631998a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 9 Mar 2020 16:29:04 +0100 Subject: [PATCH 02/11] add editable list for alt aliases --- .../views/room_settings/AliasSettings.js | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index 79317d3d1b..7c766e3e31 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -158,6 +158,38 @@ export default class AliasSettings extends React.Component { }); } + changeAltAliases(altAliases) { + if (!this.props.canSetCanonicalAlias) return; + + this.setState({ + updatingCanonicalAlias: true, + altAliases, + }); + + const eventContent = {}; + + if (this.state.canonicalAlias) { + eventContent.alias = this.state.canonicalAlias; + } + if (altAliases) { + eventContent["alt_aliases"] = altAliases; + } + + MatrixClientPeg.get().sendStateEvent(this.props.roomId, "m.room.canonical_alias", + eventContent, "").catch((err) => { + console.error(err); + Modal.createTrackedDialog('Error updating alternative addresses', '', ErrorDialog, { + title: _t("Error updating main address"), + description: _t( + "There was an error updating the room's alternative addresses. It may not be allowed by the server " + + "or a temporary failure occurred.", + ), + }); + }).finally(() => { + this.setState({updatingCanonicalAlias: false}); + }); + } + onNewAliasChanged = (value) => { this.setState({newAlias: value}); }; @@ -216,6 +248,25 @@ export default class AliasSettings extends React.Component { this.changeCanonicalAlias(event.target.value); }; + onNewAltAliasChanged = (value) => { + this.setState({newAltAlias: value}); + } + + onAltAliasAdded = (alias) => { + const altAliases = this.state.altAliases.slice(); + if (!altAliases.some(a => a.trim() === alias.trim())) { + altAliases.push(alias.trim()); + this.changeAltAliases(altAliases); + this.setState({newAltAlias: ""}); + } + } + + onAltAliasDeleted = (index) => { + const altAliases = this.state.altAliases.slice(); + altAliases.splice(index, 1); + this.changeAltAliases(altAliases); + } + _getAliases() { return this.state.altAliases.concat(this._getLocalNonAltAliases()); } @@ -281,6 +332,22 @@ export default class AliasSettings extends React.Component {
{canonicalAliasSection} {localAliasesList} +
); } From eb539537f2d1bc75022a6d496f220fc8202b0e9b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 9 Mar 2020 16:31:07 +0100 Subject: [PATCH 03/11] add recommendations for alt aliases --- src/components/views/elements/EditableItemList.js | 3 ++- src/components/views/elements/Field.js | 5 ++++- src/components/views/room_settings/AliasSettings.js | 6 ++++++ src/i18n/strings/en_EN.json | 3 +++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/EditableItemList.js b/src/components/views/elements/EditableItemList.js index c74fd6a4ee..3eb30c808c 100644 --- a/src/components/views/elements/EditableItemList.js +++ b/src/components/views/elements/EditableItemList.js @@ -123,7 +123,8 @@ export default class EditableItemList extends React.Component {
+ autoComplete="off" value={this.props.newItem || ""} onChange={this._onNewItemChanged} + list={this.props.suggestionsListId} /> {_t("Add")} diff --git a/src/components/views/elements/Field.js b/src/components/views/elements/Field.js index 7d5ccbb72d..8583c91a01 100644 --- a/src/components/views/elements/Field.js +++ b/src/components/views/elements/Field.js @@ -32,6 +32,8 @@ export default class Field extends React.PureComponent { element: PropTypes.oneOf(["input", "select", "textarea"]), // The field's type (when used as an ). Defaults to "text". type: PropTypes.string, + // id of a element for suggestions + list: PropTypes.string, // The field's label string. label: PropTypes.string, // The field's placeholder string. Defaults to the label. @@ -157,7 +159,7 @@ export default class Field extends React.PureComponent { render() { const { element, prefix, postfix, className, onValidate, children, - tooltipContent, flagInvalid, tooltipClassName, ...inputProps} = this.props; + tooltipContent, flagInvalid, tooltipClassName, list, ...inputProps} = this.props; const inputElement = element || "input"; @@ -169,6 +171,7 @@ export default class Field extends React.PureComponent { inputProps.onFocus = this.onFocus; inputProps.onChange = this.onChange; inputProps.onBlur = this.onBlur; + inputProps.list = list; const fieldInput = React.createElement(inputElement, inputProps, children); diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index 7c766e3e31..aa6e6069aa 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -332,6 +332,11 @@ export default class AliasSettings extends React.Component {
{canonicalAliasSection} {localAliasesList} + + {this._getLocalNonAltAliases().map(alias => { + return Date: Mon, 9 Mar 2020 16:31:30 +0100 Subject: [PATCH 04/11] make domain postfix optional (not needed for alt aliases) --- src/components/views/room_settings/AliasSettings.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index aa6e6069aa..e7986cc1f5 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -46,6 +46,11 @@ class EditableAliasesList extends EditableItemList { }; _renderNewItemField() { + // if we don't need the RoomAliasField, + // we don't need to overriden version of _renderNewItemField + if (!this.props.domain) { + return super._renderNewItemField(); + } const RoomAliasField = sdk.getComponent('views.elements.RoomAliasField'); const onChange = (alias) => this._onNewItemChanged({target: {value: alias}}); return ( From 7079d70f188e3f17a8acff647611022ce81bfd3b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 9 Mar 2020 16:32:13 +0100 Subject: [PATCH 05/11] hide local aliases by default --- src/components/views/room_settings/AliasSettings.js | 5 ++++- src/i18n/strings/en_EN.json | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index e7986cc1f5..d2a2c0e5f7 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -336,7 +336,6 @@ export default class AliasSettings extends React.Component { return (
{canonicalAliasSection} - {localAliasesList} {this._getLocalNonAltAliases().map(alias => { return
); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 6174ec7b15..4fa7df4c0f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1156,6 +1156,7 @@ "Alternative addresses for this room:": "Alternative addresses for this room:", "This room has no alternative addresses": "This room has no alternative addresses", "New address (e.g. #foo:domain)": "New address (e.g. #foo:domain)", + "Local addresses (unmoderated content)": "Local addresses (unmoderated content)", "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", From 053ba1110af9ebb1621add803f956913d72d98a4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 9 Mar 2020 16:32:31 +0100 Subject: [PATCH 06/11] make pressing enter not reload riot when editable list is in form --- src/components/views/elements/EditableItemList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/EditableItemList.js b/src/components/views/elements/EditableItemList.js index 3eb30c808c..ae3473ef0d 100644 --- a/src/components/views/elements/EditableItemList.js +++ b/src/components/views/elements/EditableItemList.js @@ -125,7 +125,7 @@ export default class EditableItemList extends React.Component { - + {_t("Add")} From f7cb633e3d004b60695f1757126529672f157daf Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 9 Mar 2020 16:43:52 +0100 Subject: [PATCH 07/11] only load local aliases on mount when you can edit the canonical alias --- .../views/room_settings/AliasSettings.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js index d2a2c0e5f7..c6e4d1e3ba 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -112,7 +112,11 @@ export default class AliasSettings extends React.Component { } componentDidMount() { - return this.loadLocalAliases(); + if (this.props.canSetCanonicalAlias) { + // load local aliases for providing recommendations + // for the canonical alias and alt_aliases + this.loadLocalAliases(); + } } async loadLocalAliases() { @@ -249,6 +253,16 @@ export default class AliasSettings extends React.Component { }); }; + onLocalAliasesToggled = (event) => { + // expanded + if (event.target.open) { + // if local aliases haven't been preloaded yet at component mount + if (!this.props.canSetCanonicalAlias && this.state.localAliases.length === 0) { + this.loadLocalAliases(); + } + } + }; + onCanonicalAliasChange = (event) => { this.changeCanonicalAlias(event.target.value); }; @@ -358,7 +372,7 @@ export default class AliasSettings extends React.Component { 'New address (e.g. #foo:domain)', )} /> -
+
{_t('Local addresses (unmoderated content)')} {localAliasesList}
From dcbbf31f042c5a07e0ef74124a39c9810287e3ab Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 9 Mar 2020 16:50:34 +0100 Subject: [PATCH 08/11] fix lint --- 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 c6e4d1e3ba..0a7bd1d333 100644 --- a/src/components/views/room_settings/AliasSettings.js +++ b/src/components/views/room_settings/AliasSettings.js @@ -190,8 +190,8 @@ export default class AliasSettings extends React.Component { Modal.createTrackedDialog('Error updating alternative addresses', '', ErrorDialog, { title: _t("Error updating main address"), description: _t( - "There was an error updating the room's alternative addresses. It may not be allowed by the server " + - "or a temporary failure occurred.", + "There was an error updating the room's alternative addresses. " + + "It may not be allowed by the server or a temporary failure occurred.", ), }); }).finally(() => { From 8ee714f86dec46fd85c2f38522dc0599c6c6a635 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 9 Mar 2020 16:50:48 +0100 Subject: [PATCH 09/11] show hand cursor over summary --- res/css/views/room_settings/_AliasSettings.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/res/css/views/room_settings/_AliasSettings.scss b/res/css/views/room_settings/_AliasSettings.scss index d4ae58e5b0..294902a1f0 100644 --- a/res/css/views/room_settings/_AliasSettings.scss +++ b/res/css/views/room_settings/_AliasSettings.scss @@ -26,3 +26,7 @@ limitations under the License. outline: none; box-shadow: none; } + +.mx_AliasSettings summary { + cursor: pointer; +} From 9961f810f12fe11eb4bec46af16b240078d139be Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 9 Mar 2020 16:54:00 +0100 Subject: [PATCH 10/11] update i18n --- src/i18n/strings/en_EN.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4fa7df4c0f..2d9fd74ad0 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1150,7 +1150,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", - "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)", "Alternative addresses for this room:": "Alternative addresses for this room:", From 5eaf03c3da11944b7636758f38f89d94024cecf0 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 9 Mar 2020 17:03:50 +0100 Subject: [PATCH 11/11] update e2e tests to expand local aliases when adding one --- test/end-to-end-tests/src/usecases/room-settings.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/end-to-end-tests/src/usecases/room-settings.js b/test/end-to-end-tests/src/usecases/room-settings.js index f526312f8a..ab6d66ea6d 100644 --- a/test/end-to-end-tests/src/usecases/room-settings.js +++ b/test/end-to-end-tests/src/usecases/room-settings.js @@ -52,9 +52,11 @@ module.exports = async function changeRoomSettings(session, settings) { if (settings.alias) { session.log.step(`sets alias to ${settings.alias}`); - const aliasField = await session.query(".mx_RoomSettingsDialog .mx_AliasSettings input[type=text]"); + const summary = await session.query(".mx_RoomSettingsDialog .mx_AliasSettings summary"); + await summary.click(); + const aliasField = await session.query(".mx_RoomSettingsDialog .mx_AliasSettings details input[type=text]"); await session.replaceInputText(aliasField, settings.alias.substring(1, settings.alias.lastIndexOf(":"))); - const addButton = await session.query(".mx_RoomSettingsDialog .mx_AliasSettings .mx_AccessibleButton"); + const addButton = await session.query(".mx_RoomSettingsDialog .mx_AliasSettings details .mx_AccessibleButton"); await addButton.click(); await session.delay(10); // delay to give time for the validator to run and check the alias session.log.done();