/* Copyright 2016 OpenMarket Ltd Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ import Promise from 'bluebird'; const React = require('react'); import PropTypes from 'prop-types'; const ObjectUtils = require("../../../ObjectUtils"); const MatrixClientPeg = require('../../../MatrixClientPeg'); const sdk = require("../../../index"); import { _t } from '../../../languageHandler'; const Modal = require("../../../Modal"); module.exports = React.createClass({ displayName: 'AliasSettings', 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: [], }; }, getInitialState: function() { return this.recalculateState(this.props.aliasEvents, this.props.canonicalAliasEvent); }, recalculateState: function(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 }; const localDomain = MatrixClientPeg.get().getDomain(); state.domainToAliases = this.aliasEventsToDictionary(aliasEvents); state.remoteDomains = Object.keys(state.domainToAliases).filter((domain) => { return domain !== localDomain && state.domainToAliases[domain].length > 0; }); if (canonicalAliasEvent) { state.canonicalAlias = canonicalAliasEvent.getContent().alias; } return state; }, saveSettings: function() { 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; } let 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; }, aliasEventsToDictionary: function(aliasEvents) { // m.room.alias events const dict = {}; aliasEvents.forEach((event) => { dict[event.getStateKey()] = ( (event.getContent().aliases || []).slice() // shallow-copy ); }); return dict; }, isAliasValid: function(alias) { // XXX: FIXME SPEC-1 return (alias.match(/^#([^\/:,]+?):(.+)$/) && encodeURI(alias) === alias); }, getAliasOperations: function() { const oldAliases = this.aliasEventsToDictionary(this.props.aliasEvents); return ObjectUtils.getKeyValueArrayDiffs(oldAliases, this.state.domainToAliases); }, onNewAliasChanged: function(value) { this.setState({newAlias: value}); }, onLocalAliasAdded: function(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); this.setState({ domainToAliases: this.state.domainToAliases, // Reset the add field newAlias: "", }); } else { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Invalid alias format', '', ErrorDialog, { title: _t('Invalid alias format'), 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) { 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, }); } }, onCanonicalAliasChange: function(event) { this.setState({ canonicalAlias: event.target.value, }); }, render: function() { const self = this; const EditableText = sdk.getComponent("elements.EditableText"); const EditableItemList = sdk.getComponent("elements.EditableItemList"); const localDomain = MatrixClientPeg.get().getDomain(); let canonical_alias_section; if (this.props.canSetCanonicalAlias) { let found = false; canonical_alias_section = ( ); } else { canonical_alias_section = ( { this.state.canonicalAlias || _t('not set') } ); } let remote_aliases_section; if (this.state.remoteDomains.length) { remote_aliases_section = (