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;
+}
diff --git a/src/components/views/elements/EditableItemList.js b/src/components/views/elements/EditableItemList.js
index c74fd6a4ee..ae3473ef0d 100644
--- a/src/components/views/elements/EditableItemList.js
+++ b/src/components/views/elements/EditableItemList.js
@@ -123,8 +123,9 @@ export default class EditableItemList extends React.Component {
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 a52fa09c87..0a7bd1d333 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 (
@@ -87,91 +92,63 @@ 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() {
+ if (this.props.canSetCanonicalAlias) {
+ // load local aliases for providing recommendations
+ // for the canonical alias and alt_aliases
+ 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 +161,39 @@ export default class AliasSettings extends React.Component {
"or a temporary failure occurred.",
),
});
+ this.setState({canonicalAlias: oldAlias});
+ }).finally(() => {
+ this.setState({updatingCanonicalAlias: false});
+ });
+ }
+
+ 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});
});
@@ -200,16 +210,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 +230,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);
@@ -254,10 +253,48 @@ 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);
};
+ 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());
+ }
+
+ _getLocalNonAltAliases() {
+ const {altAliases} = this.state;
+ return this.state.localAliases.filter(alias => !altAliases.includes(alias));
+ }
+
render() {
const localDomain = MatrixClientPeg.get().getDomain();
@@ -269,15 +306,13 @@ export default class AliasSettings extends React.Component {
element='select' id='canonicalAlias' label={_t('Main address')}>
{ _t('not specified') }
{
- Object.keys(this.state.domainToAliases).map((domain, i) => {
- return this.state.domainToAliases[domain].map((alias, j) => {
- if (alias === this.state.canonicalAlias) found = true;
- return (
-
- { alias }
-
- );
- });
+ this._getAliases().map((alias, i) => {
+ if (alias === this.state.canonicalAlias) found = true;
+ return (
+
+ { alias }
+
+ );
})
}
{
@@ -289,53 +324,58 @@ export default class AliasSettings extends React.Component {
);
- let remoteAliasesSection;
- if (this.state.remoteDomains.length) {
- remoteAliasesSection = (
-
-
- { _t("Remote addresses for this room:") }
-
-
- { this.state.remoteDomains.map((domain, i) => {
- return this.state.domainToAliases[domain].map((alias, j) => {
- return {alias} ;
- });
- }) }
-
-
- );
- }
-
let localAliasesList;
if (this.state.localAliasesLoading) {
const Spinner = sdk.getComponent("elements.Spinner");
localAliasesList = ;
} else {
- localAliasesList = ;
+ />);
}
return (
{canonicalAliasSection}
- {localAliasesList}
- {remoteAliasesSection}
+
+ {this._getLocalNonAltAliases().map(alias => {
+ return ;
+ })};
+
+
+
+ {_t('Local addresses (unmoderated content)')}
+ {localAliasesList}
+
);
}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 68be1a3e88..15a050d783 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1141,16 +1141,19 @@
"Mark all as read": "Mark all as read",
"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.",
+ "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.",
"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.",
"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",
- "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)",
+ "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",
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();