Merge pull request #2679 from matrix-org/travis/settings/field-editable-list
Fix AliasSettings and RelatedGroups UX
This commit is contained in:
commit
d6f89f422b
9 changed files with 370 additions and 423 deletions
|
@ -31,7 +31,6 @@ src/components/views/globals/UpdateCheckBar.js
|
||||||
src/components/views/messages/MFileBody.js
|
src/components/views/messages/MFileBody.js
|
||||||
src/components/views/messages/RoomAvatarEvent.js
|
src/components/views/messages/RoomAvatarEvent.js
|
||||||
src/components/views/messages/TextualBody.js
|
src/components/views/messages/TextualBody.js
|
||||||
src/components/views/room_settings/AliasSettings.js
|
|
||||||
src/components/views/room_settings/ColorSettings.js
|
src/components/views/room_settings/ColorSettings.js
|
||||||
src/components/views/rooms/Autocomplete.js
|
src/components/views/rooms/Autocomplete.js
|
||||||
src/components/views/rooms/AuxPanel.js
|
src/components/views/rooms/AuxPanel.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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -16,47 +16,38 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_EditableItemList {
|
.mx_EditableItemList {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
margin-bottom: 0px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EditableItem {
|
.mx_EditableItem {
|
||||||
display: flex;
|
margin-bottom: 5px;
|
||||||
margin-left: 56px;
|
margin-left: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EditableItem .mx_EditableItem_editable {
|
.mx_EditableItem_delete {
|
||||||
border: 0px;
|
margin-right: 5px;
|
||||||
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;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
vertical-align: middle;
|
||||||
visibility: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EditableItem:hover .mx_EditableItem_addButton,
|
.mx_EditableItem_email {
|
||||||
.mx_EditableItem:hover .mx_EditableItem_removeButton {
|
vertical-align: middle;
|
||||||
visibility: visible;
|
}
|
||||||
|
|
||||||
|
.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 {
|
.mx_EditableItemList_label {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
|
@ -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) {
|
function textForServerACLEvent(ev) {
|
||||||
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
|
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
|
||||||
const prevContent = ev.getPrevContent();
|
const prevContent = ev.getPrevContent();
|
||||||
|
@ -473,6 +503,7 @@ const stateHandlers = {
|
||||||
'm.room.tombstone': textForTombstoneEvent,
|
'm.room.tombstone': textForTombstoneEvent,
|
||||||
'm.room.join_rules': textForJoinRulesEvent,
|
'm.room.join_rules': textForJoinRulesEvent,
|
||||||
'm.room.guest_access': textForGuestAccessEvent,
|
'm.room.guest_access': textForGuestAccessEvent,
|
||||||
|
'm.room.related_groups': textForRelatedGroupsEvent,
|
||||||
|
|
||||||
'im.vector.modular.widgets': textForWidgetEvent,
|
'im.vector.modular.widgets': textForWidgetEvent,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -16,142 +16,145 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import sdk from '../../../index';
|
|
||||||
import {_t} from '../../../languageHandler.js';
|
import {_t} from '../../../languageHandler.js';
|
||||||
|
import Field from "./Field";
|
||||||
|
import AccessibleButton from "./AccessibleButton";
|
||||||
|
|
||||||
const EditableItem = React.createClass({
|
export class EditableItem extends React.Component {
|
||||||
displayName: 'EditableItem',
|
static propTypes = {
|
||||||
|
|
||||||
propTypes: {
|
|
||||||
initialValue: PropTypes.string,
|
|
||||||
index: PropTypes.number,
|
index: PropTypes.number,
|
||||||
placeholder: PropTypes.string,
|
value: PropTypes.string,
|
||||||
|
|
||||||
onChange: PropTypes.func,
|
|
||||||
onRemove: PropTypes.func,
|
onRemove: PropTypes.func,
|
||||||
onAdd: PropTypes.func,
|
};
|
||||||
|
|
||||||
addOnChange: PropTypes.bool,
|
constructor() {
|
||||||
},
|
super();
|
||||||
|
|
||||||
onChange: function(value) {
|
this.state = {
|
||||||
this.setState({ value });
|
verifyRemove: false,
|
||||||
if (this.props.onChange) this.props.onChange(value, this.props.index);
|
};
|
||||||
if (this.props.addOnChange && this.props.onAdd) this.props.onAdd(value);
|
}
|
||||||
},
|
|
||||||
|
_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();
|
||||||
|
|
||||||
onRemove: function() {
|
|
||||||
if (this.props.onRemove) this.props.onRemove(this.props.index);
|
if (this.props.onRemove) this.props.onRemove(this.props.index);
|
||||||
},
|
this.setState({verifyRemove: false});
|
||||||
|
};
|
||||||
|
|
||||||
onAdd: function() {
|
render() {
|
||||||
if (this.props.onAdd) this.props.onAdd(this.state.value);
|
if (this.state.verifyRemove) {
|
||||||
},
|
return (
|
||||||
|
<div className="mx_EditableItem">
|
||||||
render: function() {
|
<span className="mx_EditableItem_promptText">
|
||||||
const EditableText = sdk.getComponent('elements.EditableText');
|
{_t("Are you sure?")}
|
||||||
return <div className="mx_EditableItem">
|
</span>
|
||||||
<EditableText
|
<AccessibleButton onClick={this._onActuallyRemove} kind="primary_sm"
|
||||||
className="mx_EditableItem_editable"
|
className="mx_EditableItem_confirmBtn">
|
||||||
placeholderClassName="mx_EditableItem_editablePlaceholder"
|
{_t("Yes")}
|
||||||
placeholder={this.props.placeholder}
|
</AccessibleButton>
|
||||||
blurToCancel={false}
|
<AccessibleButton onClick={this._onDontRemove} kind="danger_sm"
|
||||||
editable={true}
|
className="mx_EditableItem_confirmBtn">
|
||||||
initialValue={this.props.initialValue}
|
{_t("No")}
|
||||||
onValueChanged={this.onChange} />
|
</AccessibleButton>
|
||||||
{ this.props.onAdd ?
|
|
||||||
<div className="mx_EditableItem_addButton">
|
|
||||||
<img className="mx_filterFlipColor"
|
|
||||||
src={require("../../../../res/img/plus.svg")} width="14" height="14"
|
|
||||||
alt={_t("Add")} onClick={this.onAdd} />
|
|
||||||
</div>
|
</div>
|
||||||
:
|
);
|
||||||
<div className="mx_EditableItem_removeButton">
|
}
|
||||||
<img className="mx_filterFlipColor"
|
|
||||||
src={require("../../../../res/img/cancel-small.svg")} width="14" height="14"
|
|
||||||
alt={_t("Delete")} onClick={this.onRemove} />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: Make this use the new Field element
|
return (
|
||||||
module.exports = React.createClass({
|
<div className="mx_EditableItem">
|
||||||
displayName: 'EditableItemList',
|
<img src={require("../../../../res/img/feather-icons/cancel.svg")} width={14} height={14}
|
||||||
|
onClick={this._onRemove} className="mx_EditableItem_delete" alt={_t("Remove")} />
|
||||||
|
<span className="mx_EditableItem_item">{this.props.value}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
propTypes: {
|
export default class EditableItemList extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
items: PropTypes.arrayOf(PropTypes.string).isRequired,
|
items: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
onNewItemChanged: PropTypes.func,
|
itemsLabel: PropTypes.string,
|
||||||
|
noItemsLabel: PropTypes.string,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
|
newItem: PropTypes.string,
|
||||||
|
|
||||||
onItemAdded: PropTypes.func,
|
onItemAdded: PropTypes.func,
|
||||||
onItemEdited: PropTypes.func,
|
|
||||||
onItemRemoved: PropTypes.func,
|
onItemRemoved: PropTypes.func,
|
||||||
|
onNewItemChanged: PropTypes.func,
|
||||||
|
|
||||||
canEdit: PropTypes.bool,
|
canEdit: PropTypes.bool,
|
||||||
},
|
canRemove: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
getDefaultProps: function() {
|
_onItemAdded = (e) => {
|
||||||
return {
|
e.stopPropagation();
|
||||||
onItemAdded: () => {},
|
e.preventDefault();
|
||||||
onItemEdited: () => {},
|
|
||||||
onItemRemoved: () => {},
|
|
||||||
onNewItemChanged: () => {},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
onItemAdded: function(value) {
|
if (this.props.onItemAdded) this.props.onItemAdded(this.props.newItem);
|
||||||
this.props.onItemAdded(value);
|
};
|
||||||
},
|
|
||||||
|
|
||||||
onItemEdited: function(value, index) {
|
_onItemRemoved = (index) => {
|
||||||
if (value.length === 0) {
|
if (this.props.onItemRemoved) this.props.onItemRemoved(index);
|
||||||
this.onItemRemoved(index);
|
};
|
||||||
} else {
|
|
||||||
this.props.onItemEdited(value, index);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onItemRemoved: function(index) {
|
_onNewItemChanged = (e) => {
|
||||||
this.props.onItemRemoved(index);
|
if (this.props.onNewItemChanged) this.props.onNewItemChanged(e.target.value);
|
||||||
},
|
};
|
||||||
|
|
||||||
onNewItemChanged: function(value) {
|
_renderNewItemField() {
|
||||||
this.props.onNewItemChanged(value);
|
return (
|
||||||
},
|
<form onSubmit={this._onItemAdded} autoComplete={false}
|
||||||
|
noValidate={true} className="mx_EditableItemList_newItem">
|
||||||
|
<Field id="newEmailAddress" label={this.props.placeholder}
|
||||||
|
type="text" autoComplete="off" value={this.props.newItem}
|
||||||
|
onChange={this._onNewItemChanged}
|
||||||
|
/>
|
||||||
|
<AccessibleButton onClick={this._onItemAdded} kind="primary">
|
||||||
|
{_t("Add")}
|
||||||
|
</AccessibleButton>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render: function() {
|
render() {
|
||||||
const editableItems = this.props.items.map((item, index) => {
|
const editableItems = this.props.items.map((item, index) => {
|
||||||
|
if (!this.props.canRemove) {
|
||||||
|
return <li>{item}</li>;
|
||||||
|
}
|
||||||
|
|
||||||
return <EditableItem
|
return <EditableItem
|
||||||
key={index}
|
key={index}
|
||||||
index={index}
|
index={index}
|
||||||
initialValue={item}
|
value={item}
|
||||||
onChange={this.onItemEdited}
|
onRemove={this._onItemRemoved}
|
||||||
onRemove={this.onItemRemoved}
|
|
||||||
placeholder={this.props.placeholder}
|
|
||||||
/>;
|
/>;
|
||||||
});
|
});
|
||||||
|
|
||||||
const label = this.props.items.length > 0 ?
|
const editableItemsSection = this.props.canRemove ? editableItems : <ul>{editableItems}</ul>;
|
||||||
this.props.itemsLabel : this.props.noItemsLabel;
|
const label = this.props.items.length > 0 ? this.props.itemsLabel : this.props.noItemsLabel;
|
||||||
|
|
||||||
return (<div className="mx_EditableItemList">
|
return (<div className="mx_EditableItemList">
|
||||||
<div className="mx_EditableItemList_label">
|
<div className="mx_EditableItemList_label">
|
||||||
{ label }
|
{ label }
|
||||||
</div>
|
</div>
|
||||||
{ editableItems }
|
{ editableItemsSection }
|
||||||
{ this.props.canEdit ?
|
{ this.props.canEdit ? this._renderNewItemField() : <div /> }
|
||||||
// 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.
|
|
||||||
<EditableItem
|
|
||||||
key={editableItems.length}
|
|
||||||
initialValue={this.props.newItem}
|
|
||||||
onAdd={this.onItemAdded}
|
|
||||||
onChange={this.onNewItemChanged}
|
|
||||||
addOnChange={true}
|
|
||||||
placeholder={this.props.placeholder}
|
|
||||||
/> : <div />
|
|
||||||
}
|
|
||||||
</div>);
|
</div>);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 OpenMarket Ltd
|
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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -15,112 +15,55 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
const ObjectUtils = require("../../../ObjectUtils");
|
|
||||||
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||||
const sdk = require("../../../index");
|
const sdk = require("../../../index");
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import Field from "../elements/Field";
|
import Field from "../elements/Field";
|
||||||
|
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||||
const Modal = require("../../../Modal");
|
const Modal = require("../../../Modal");
|
||||||
|
|
||||||
module.exports = React.createClass({
|
export default class AliasSettings extends React.Component {
|
||||||
displayName: 'AliasSettings',
|
static propTypes = {
|
||||||
|
|
||||||
propTypes: {
|
|
||||||
roomId: PropTypes.string.isRequired,
|
roomId: PropTypes.string.isRequired,
|
||||||
canSetCanonicalAlias: PropTypes.bool.isRequired,
|
canSetCanonicalAlias: PropTypes.bool.isRequired,
|
||||||
canSetAliases: PropTypes.bool.isRequired,
|
canSetAliases: PropTypes.bool.isRequired,
|
||||||
aliasEvents: PropTypes.array, // [MatrixEvent]
|
aliasEvents: PropTypes.array, // [MatrixEvent]
|
||||||
canonicalAliasEvent: PropTypes.object, // MatrixEvent
|
canonicalAliasEvent: PropTypes.object, // MatrixEvent
|
||||||
},
|
};
|
||||||
|
|
||||||
getDefaultProps: function() {
|
static defaultProps = {
|
||||||
return {
|
canSetAliases: false,
|
||||||
canSetAliases: false,
|
canSetCanonicalAlias: false,
|
||||||
canSetCanonicalAlias: false,
|
aliasEvents: [],
|
||||||
aliasEvents: [],
|
};
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
constructor(props) {
|
||||||
return this.recalculateState(this.props.aliasEvents, this.props.canonicalAliasEvent);
|
super(props);
|
||||||
},
|
|
||||||
|
|
||||||
recalculateState: function(aliasEvents, canonicalAliasEvent) {
|
|
||||||
aliasEvents = aliasEvents || [];
|
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
domainToAliases: {}, // { domain.com => [#alias1:domain.com, #alias2:domain.com] }
|
domainToAliases: {}, // { domain.com => [#alias1:domain.com, #alias2:domain.com] }
|
||||||
remoteDomains: [], // [ domain.com, foobar.com ]
|
remoteDomains: [], // [ domain.com, foobar.com ]
|
||||||
canonicalAlias: null, // #canonical:domain.com
|
canonicalAlias: null, // #canonical:domain.com
|
||||||
|
updatingCanonicalAlias: false,
|
||||||
|
newItem: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const localDomain = MatrixClientPeg.get().getDomain();
|
const localDomain = MatrixClientPeg.get().getDomain();
|
||||||
|
state.domainToAliases = this.aliasEventsToDictionary(props.aliasEvents || []);
|
||||||
state.domainToAliases = this.aliasEventsToDictionary(aliasEvents);
|
|
||||||
|
|
||||||
state.remoteDomains = Object.keys(state.domainToAliases).filter((domain) => {
|
state.remoteDomains = Object.keys(state.domainToAliases).filter((domain) => {
|
||||||
return domain !== localDomain && state.domainToAliases[domain].length > 0;
|
return domain !== localDomain && state.domainToAliases[domain].length > 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (canonicalAliasEvent) {
|
if (props.canonicalAliasEvent) {
|
||||||
state.canonicalAlias = canonicalAliasEvent.getContent().alias;
|
state.canonicalAlias = props.canonicalAliasEvent.getContent().alias;
|
||||||
}
|
}
|
||||||
|
|
||||||
return state;
|
this.state = state;
|
||||||
},
|
}
|
||||||
|
|
||||||
saveSettings: function() {
|
aliasEventsToDictionary(aliasEvents) { // m.room.alias events
|
||||||
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;
|
|
||||||
},
|
|
||||||
|
|
||||||
aliasEventsToDictionary: function(aliasEvents) { // m.room.alias events
|
|
||||||
const dict = {};
|
const dict = {};
|
||||||
aliasEvents.forEach((event) => {
|
aliasEvents.forEach((event) => {
|
||||||
dict[event.getStateKey()] = (
|
dict[event.getStateKey()] = (
|
||||||
|
@ -128,35 +71,72 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return dict;
|
return dict;
|
||||||
},
|
}
|
||||||
|
|
||||||
isAliasValid: function(alias) {
|
isAliasValid(alias) {
|
||||||
// XXX: FIXME https://github.com/matrix-org/matrix-doc/issues/668
|
// XXX: FIXME https://github.com/matrix-org/matrix-doc/issues/668
|
||||||
return (alias.match(/^#([^\/:,]+?):(.+)$/) && encodeURI(alias) === alias);
|
return (alias.match(/^#([^/:,]+?):(.+)$/) && encodeURI(alias) === alias);
|
||||||
},
|
}
|
||||||
|
|
||||||
getAliasOperations: function() {
|
changeCanonicalAlias(alias) {
|
||||||
const oldAliases = this.aliasEventsToDictionary(this.props.aliasEvents);
|
if (!this.props.canSetCanonicalAlias) return;
|
||||||
return ObjectUtils.getKeyValueArrayDiffs(oldAliases, this.state.domainToAliases);
|
|
||||||
},
|
|
||||||
|
|
||||||
onNewAliasChanged: function(value) {
|
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});
|
this.setState({newAlias: value});
|
||||||
},
|
};
|
||||||
|
|
||||||
onLocalAliasAdded: function(alias) {
|
onLocalAliasAdded = (alias) => {
|
||||||
if (!alias || alias.length === 0) return; // ignore attempts to create blank aliases
|
if (!alias || alias.length === 0) return; // ignore attempts to create blank aliases
|
||||||
|
|
||||||
const localDomain = MatrixClientPeg.get().getDomain();
|
const localDomain = MatrixClientPeg.get().getDomain();
|
||||||
if (!alias.includes(':')) alias += ':' + localDomain;
|
if (!alias.includes(':')) alias += ':' + localDomain;
|
||||||
if (this.isAliasValid(alias) && alias.endsWith(localDomain)) {
|
if (this.isAliasValid(alias) && alias.endsWith(localDomain)) {
|
||||||
this.state.domainToAliases[localDomain] = this.state.domainToAliases[localDomain] || [];
|
MatrixClientPeg.get().createAlias(alias, this.props.roomId).then(() => {
|
||||||
this.state.domainToAliases[localDomain].push(alias);
|
const localAliases = this.state.domainToAliases[localDomain] || [];
|
||||||
|
const domainAliases = Object.assign({}, this.state.domainToAliases);
|
||||||
|
domainAliases[localDomain] = [...localAliases, alias];
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
domainToAliases: this.state.domainToAliases,
|
domainToAliases: domainAliases,
|
||||||
// Reset the add field
|
// Reset the add field
|
||||||
newAlias: "",
|
newAlias: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!this.state.canonicalAlias) {
|
||||||
|
this.changeCanonicalAlias(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 {
|
} else {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
@ -165,130 +145,102 @@ module.exports = React.createClass({
|
||||||
description: _t('\'%(alias)s\' is not a valid format for an alias', { alias: alias }),
|
description: _t('\'%(alias)s\' is not a valid format for an alias', { alias: alias }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (!this.props.canonicalAlias) {
|
onLocalAliasDeleted = (index) => {
|
||||||
this.setState({
|
|
||||||
canonicalAlias: alias,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onLocalAliasChanged: function(alias, index) {
|
|
||||||
if (alias === "") return; // hit the delete button to delete please
|
|
||||||
const localDomain = MatrixClientPeg.get().getDomain();
|
const localDomain = MatrixClientPeg.get().getDomain();
|
||||||
if (!alias.includes(':')) alias += ':' + localDomain;
|
|
||||||
if (this.isAliasValid(alias) && alias.endsWith(localDomain)) {
|
const alias = this.state.domainToAliases[localDomain][index];
|
||||||
this.state.domainToAliases[localDomain][index] = alias;
|
|
||||||
} else {
|
// TODO: In future, we should probably be making sure that the alias actually belongs
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
// to this room. See https://github.com/vector-im/riot-web/issues/7353
|
||||||
Modal.createTrackedDialog('Invalid address format', '', ErrorDialog, {
|
MatrixClientPeg.get().deleteAlias(alias).then(() => {
|
||||||
title: _t('Invalid address format'),
|
const localAliases = this.state.domainToAliases[localDomain].filter((a) => a !== alias);
|
||||||
description: _t('\'%(alias)s\' is not a valid format for an address', { alias: alias }),
|
const domainAliases = Object.assign({}, this.state.domainToAliases);
|
||||||
|
domainAliases[localDomain] = localAliases;
|
||||||
|
|
||||||
|
this.setState({domainToAliases: domainAliases});
|
||||||
|
|
||||||
|
if (this.state.canonicalAlias === alias) {
|
||||||
|
this.changeCanonicalAlias(null);
|
||||||
|
}
|
||||||
|
}).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.",
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
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) {
|
onCanonicalAliasChange = (event) => {
|
||||||
this.setState({
|
this.changeCanonicalAlias(event.target.value);
|
||||||
canonicalAlias: event.target.value,
|
};
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
render() {
|
||||||
const self = this;
|
|
||||||
const EditableText = sdk.getComponent("elements.EditableText");
|
|
||||||
const EditableItemList = sdk.getComponent("elements.EditableItemList");
|
const EditableItemList = sdk.getComponent("elements.EditableItemList");
|
||||||
const localDomain = MatrixClientPeg.get().getDomain();
|
const localDomain = MatrixClientPeg.get().getDomain();
|
||||||
|
|
||||||
let canonical_alias_section;
|
let found = false;
|
||||||
if (this.props.canSetCanonicalAlias) {
|
const canonicalValue = this.state.canonicalAlias || "";
|
||||||
let found = false;
|
const canonicalAliasSection = (
|
||||||
const canonicalValue = this.state.canonicalAlias || "";
|
<Field onChange={this.onCanonicalAliasChange} value={canonicalValue}
|
||||||
canonical_alias_section = (
|
disabled={this.state.updatingCanonicalAlias || !this.props.canSetCanonicalAlias}
|
||||||
<Field onChange={this.onCanonicalAliasChange} value={canonicalValue}
|
element='select' id='canonicalAlias' label={_t('Main address')}>
|
||||||
element='select' id='canonicalAlias' label={_t('Main address')}>
|
<option value="" key="unset">{ _t('not specified') }</option>
|
||||||
<option value="" key="unset">{ _t('not specified') }</option>
|
{
|
||||||
{
|
Object.keys(this.state.domainToAliases).map((domain, i) => {
|
||||||
Object.keys(self.state.domainToAliases).map((domain, i) => {
|
return this.state.domainToAliases[domain].map((alias, j) => {
|
||||||
return self.state.domainToAliases[domain].map((alias, j) => {
|
if (alias === this.state.canonicalAlias) found = true;
|
||||||
if (alias === this.state.canonicalAlias) found = true;
|
return (
|
||||||
return (
|
<option value={alias} key={i + "_" + j}>
|
||||||
<option value={alias} key={i + "_" + j}>
|
{ alias }
|
||||||
{ alias }
|
</option>
|
||||||
</option>
|
);
|
||||||
);
|
});
|
||||||
});
|
})
|
||||||
})
|
}
|
||||||
}
|
{
|
||||||
{
|
found || !this.state.canonicalAlias ? '' :
|
||||||
found || !this.stateCanonicalAlias ? '' :
|
<option value={ this.state.canonicalAlias } key='arbitrary'>
|
||||||
<option value={ this.state.canonicalAlias } key='arbitrary'>
|
{ this.state.canonicalAlias }
|
||||||
{ this.state.canonicalAlias }
|
</option>
|
||||||
</option>
|
}
|
||||||
}
|
</Field>
|
||||||
</Field>
|
);
|
||||||
);
|
|
||||||
} else {
|
|
||||||
canonical_alias_section = (
|
|
||||||
<b>{ this.state.canonicalAlias || _t('not set') }</b>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let remote_aliases_section;
|
let remoteAliasesSection;
|
||||||
if (this.state.remoteDomains.length) {
|
if (this.state.remoteDomains.length) {
|
||||||
remote_aliases_section = (
|
remoteAliasesSection = (
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
{ _t("Remote addresses for this room:") }
|
{ _t("Remote addresses for this room:") }
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<ul>
|
||||||
{ this.state.remoteDomains.map((domain, i) => {
|
{ this.state.remoteDomains.map((domain, i) => {
|
||||||
return this.state.domainToAliases[domain].map(function(alias, j) {
|
return this.state.domainToAliases[domain].map((alias, j) => {
|
||||||
return (
|
return <li key={i + "_" + j}>{alias}</li>;
|
||||||
<div key={i + "_" + j}>
|
|
||||||
<EditableText
|
|
||||||
className="mx_AliasSettings_alias mx_AliasSettings_editable"
|
|
||||||
blurToCancel={false}
|
|
||||||
editable={false}
|
|
||||||
initialValue={alias} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}) }
|
}) }
|
||||||
</div>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mx_AliasSettings'>
|
<div className='mx_AliasSettings'>
|
||||||
{canonical_alias_section}
|
{canonicalAliasSection}
|
||||||
<EditableItemList
|
<EditableItemList
|
||||||
className={"mx_RoomSettings_localAliases"}
|
className={"mx_RoomSettings_localAliases"}
|
||||||
items={this.state.domainToAliases[localDomain] || []}
|
items={this.state.domainToAliases[localDomain] || []}
|
||||||
newItem={this.state.newAlias}
|
newItem={this.state.newAlias}
|
||||||
onNewItemChanged={this.onNewAliasChanged}
|
onNewItemChanged={this.onNewAliasChanged}
|
||||||
|
canRemove={this.props.canSetAliases}
|
||||||
canEdit={this.props.canSetAliases}
|
canEdit={this.props.canSetAliases}
|
||||||
onItemAdded={this.onLocalAliasAdded}
|
onItemAdded={this.onLocalAliasAdded}
|
||||||
onItemEdited={this.onLocalAliasChanged}
|
|
||||||
onItemRemoved={this.onLocalAliasDeleted}
|
onItemRemoved={this.onLocalAliasDeleted}
|
||||||
itemsLabel={_t('Local addresses for this room:')}
|
itemsLabel={_t('Local addresses for this room:')}
|
||||||
noItemsLabel={_t('This room has no local addresses')}
|
noItemsLabel={_t('This room has no local addresses')}
|
||||||
|
@ -296,10 +248,8 @@ module.exports = React.createClass({
|
||||||
'New address (e.g. #foo:%(localDomain)s)', {localDomain: localDomain},
|
'New address (e.g. #foo:%(localDomain)s)', {localDomain: localDomain},
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
{remoteAliasesSection}
|
||||||
{ remote_aliases_section }
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
|
@ -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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -20,61 +20,50 @@ import {MatrixEvent, MatrixClient} from 'matrix-js-sdk';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import isEqual from 'lodash/isEqual';
|
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||||
|
|
||||||
const GROUP_ID_REGEX = /\+\S+:\S+/;
|
const GROUP_ID_REGEX = /\+\S+:\S+/;
|
||||||
|
|
||||||
module.exports = React.createClass({
|
export default class RelatedGroupSettings extends React.Component {
|
||||||
displayName: 'RelatedGroupSettings',
|
static propTypes = {
|
||||||
|
|
||||||
propTypes: {
|
|
||||||
roomId: PropTypes.string.isRequired,
|
roomId: PropTypes.string.isRequired,
|
||||||
canSetRelatedGroups: PropTypes.bool.isRequired,
|
canSetRelatedGroups: PropTypes.bool.isRequired,
|
||||||
relatedGroupsEvent: PropTypes.instanceOf(MatrixEvent),
|
relatedGroupsEvent: PropTypes.instanceOf(MatrixEvent),
|
||||||
},
|
};
|
||||||
|
|
||||||
contextTypes: {
|
static contextTypes = {
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||||
},
|
};
|
||||||
|
|
||||||
getDefaultProps: function() {
|
static defaultProps = {
|
||||||
return {
|
canSetRelatedGroups: false,
|
||||||
canSetRelatedGroups: false,
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
newGroupId: "",
|
||||||
|
newGroupsList: props.relatedGroupsEvent ? (props.relatedGroupsEvent.getContent().groups || []) : [],
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
|
|
||||||
getInitialState: function() {
|
updateGroups(newGroupsList) {
|
||||||
return {
|
this.context.matrixClient.sendStateEvent(this.props.roomId, 'm.room.related_groups', {
|
||||||
newGroupsList: this.getInitialGroupList(),
|
groups: newGroupsList,
|
||||||
newGroupId: null,
|
}, '').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.",
|
||||||
|
),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getInitialGroupList: function() {
|
validateGroupId(groupId) {
|
||||||
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,
|
|
||||||
},
|
|
||||||
'',
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
validateGroupId: function(groupId) {
|
|
||||||
if (!GROUP_ID_REGEX.test(groupId)) {
|
if (!GROUP_ID_REGEX.test(groupId)) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createTrackedDialog('Invalid related community ID', '', ErrorDialog, {
|
Modal.createTrackedDialog('Invalid related community ID', '', ErrorDialog, {
|
||||||
|
@ -84,38 +73,32 @@ module.exports = React.createClass({
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
}
|
||||||
|
|
||||||
onNewGroupChanged: function(newGroupId) {
|
onNewGroupChanged = (newGroupId) => {
|
||||||
this.setState({ newGroupId });
|
this.setState({ newGroupId });
|
||||||
},
|
};
|
||||||
|
|
||||||
onGroupAdded: function(groupId) {
|
onGroupAdded = (groupId) => {
|
||||||
if (groupId.length === 0 || !this.validateGroupId(groupId)) {
|
if (groupId.length === 0 || !this.validateGroupId(groupId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const newGroupsList = [...this.state.newGroupsList, groupId];
|
||||||
this.setState({
|
this.setState({
|
||||||
newGroupsList: this.state.newGroupsList.concat([groupId]),
|
newGroupsList: newGroupsList,
|
||||||
newGroupId: '',
|
newGroupId: '',
|
||||||
});
|
});
|
||||||
},
|
this.updateGroups(newGroupsList);
|
||||||
|
};
|
||||||
|
|
||||||
onGroupEdited: function(groupId, index) {
|
onGroupDeleted = (index) => {
|
||||||
if (groupId.length === 0 || !this.validateGroupId(groupId)) {
|
const group = this.state.newGroupsList[index];
|
||||||
return;
|
const newGroupsList = this.state.newGroupsList.filter((g) => g !== group);
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
newGroupsList: Object.assign(this.state.newGroupsList, {[index]: groupId}),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onGroupDeleted: function(index) {
|
|
||||||
const newGroupsList = this.state.newGroupsList.slice();
|
|
||||||
newGroupsList.splice(index, 1);
|
|
||||||
this.setState({ newGroupsList });
|
this.setState({ newGroupsList });
|
||||||
},
|
this.updateGroups(newGroupsList);
|
||||||
|
};
|
||||||
|
|
||||||
render: function() {
|
render() {
|
||||||
const localDomain = this.context.matrixClient.getDomain();
|
const localDomain = this.context.matrixClient.getDomain();
|
||||||
const EditableItemList = sdk.getComponent('elements.EditableItemList');
|
const EditableItemList = sdk.getComponent('elements.EditableItemList');
|
||||||
return <div>
|
return <div>
|
||||||
|
@ -123,10 +106,10 @@ module.exports = React.createClass({
|
||||||
items={this.state.newGroupsList}
|
items={this.state.newGroupsList}
|
||||||
className={"mx_RelatedGroupSettings"}
|
className={"mx_RelatedGroupSettings"}
|
||||||
newItem={this.state.newGroupId}
|
newItem={this.state.newGroupId}
|
||||||
|
canRemove={this.props.canSetRelatedGroups}
|
||||||
canEdit={this.props.canSetRelatedGroups}
|
canEdit={this.props.canSetRelatedGroups}
|
||||||
onNewItemChanged={this.onNewGroupChanged}
|
onNewItemChanged={this.onNewGroupChanged}
|
||||||
onItemAdded={this.onGroupAdded}
|
onItemAdded={this.onGroupAdded}
|
||||||
onItemEdited={this.onGroupEdited}
|
|
||||||
onItemRemoved={this.onGroupDeleted}
|
onItemRemoved={this.onGroupDeleted}
|
||||||
itemsLabel={_t('Showing flair for these communities:')}
|
itemsLabel={_t('Showing flair for these communities:')}
|
||||||
noItemsLabel={_t('This room is not showing flair for any communities')}
|
noItemsLabel={_t('This room is not showing flair for any communities')}
|
||||||
|
@ -135,5 +118,5 @@ module.exports = React.createClass({
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>;
|
</div>;
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
|
@ -65,6 +65,7 @@ const stateEventTileTypes = {
|
||||||
'm.room.tombstone': 'messages.TextualEvent',
|
'm.room.tombstone': 'messages.TextualEvent',
|
||||||
'm.room.join_rules': 'messages.TextualEvent',
|
'm.room.join_rules': 'messages.TextualEvent',
|
||||||
'm.room.guest_access': 'messages.TextualEvent',
|
'm.room.guest_access': 'messages.TextualEvent',
|
||||||
|
'm.room.related_groups': 'messages.TextualEvent',
|
||||||
};
|
};
|
||||||
|
|
||||||
function getHandlerTile(ev) {
|
function getHandlerTile(ev) {
|
||||||
|
|
|
@ -68,18 +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;
|
|
||||||
this.refs.flairSettings.saveSettings();
|
|
||||||
};
|
|
||||||
|
|
||||||
_onLeaveClick = () => {
|
_onLeaveClick = () => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'leave_room',
|
action: 'leave_room',
|
||||||
|
@ -113,12 +101,9 @@ export default class GeneralRoomSettingsTab extends React.Component {
|
||||||
|
|
||||||
<span className='mx_SettingsTab_subheading'>{_t("Room Addresses")}</span>
|
<span className='mx_SettingsTab_subheading'>{_t("Room Addresses")}</span>
|
||||||
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
|
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
|
||||||
<AliasSettings ref="aliasSettings" roomId={this.props.roomId}
|
<AliasSettings roomId={this.props.roomId}
|
||||||
canSetCanonicalAlias={canSetCanonical} canSetAliases={canSetAliases}
|
canSetCanonicalAlias={canSetCanonical} canSetAliases={canSetAliases}
|
||||||
canonicalAliasEvent={canonicalAliasEv} aliasEvents={aliasEvents} />
|
canonicalAliasEvent={canonicalAliasEv} aliasEvents={aliasEvents} />
|
||||||
<AccessibleButton onClick={this._saveAliases} kind='primary'>
|
|
||||||
{_t("Save")}
|
|
||||||
</AccessibleButton>
|
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_SettingsTab_section'>
|
<div className='mx_SettingsTab_section'>
|
||||||
<LabelledToggleSwitch value={this.state.isRoomPublished}
|
<LabelledToggleSwitch value={this.state.isRoomPublished}
|
||||||
|
@ -131,12 +116,9 @@ export default class GeneralRoomSettingsTab extends React.Component {
|
||||||
|
|
||||||
<span className='mx_SettingsTab_subheading'>{_t("Flair")}</span>
|
<span className='mx_SettingsTab_subheading'>{_t("Flair")}</span>
|
||||||
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
|
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
|
||||||
<RelatedGroupSettings ref="flairSettings" roomId={room.roomId}
|
<RelatedGroupSettings roomId={room.roomId}
|
||||||
canSetRelatedGroups={canChangeGroups}
|
canSetRelatedGroups={canChangeGroups}
|
||||||
relatedGroupsEvent={groupsEvent} />
|
relatedGroupsEvent={groupsEvent} />
|
||||||
<AccessibleButton onClick={this._saveGroups} kind='primary'>
|
|
||||||
{_t("Save")}
|
|
||||||
</AccessibleButton>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span className='mx_SettingsTab_subheading'>{_t("URL Previews")}</span>
|
<span className='mx_SettingsTab_subheading'>{_t("URL Previews")}</span>
|
||||||
|
|
|
@ -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 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 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 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.",
|
"%(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.|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.",
|
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s added %(addedAddresses)s as an address for this room.",
|
||||||
|
@ -804,17 +807,22 @@
|
||||||
"Hide Stickers": "Hide Stickers",
|
"Hide Stickers": "Hide Stickers",
|
||||||
"Show Stickers": "Show Stickers",
|
"Show Stickers": "Show Stickers",
|
||||||
"Jump to first unread message.": "Jump to first unread message.",
|
"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",
|
"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",
|
"'%(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",
|
"Error removing alias": "Error removing alias",
|
||||||
"'%(alias)s' is not a valid format for an address": "'%(alias)s' is not a valid format for an address",
|
"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",
|
"Main address": "Main address",
|
||||||
"not specified": "not specified",
|
"not specified": "not specified",
|
||||||
"not set": "not set",
|
|
||||||
"Remote addresses for this room:": "Remote addresses for this room:",
|
"Remote addresses for this room:": "Remote addresses for this room:",
|
||||||
"Local addresses for this room:": "Local 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",
|
"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:%(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",
|
"Invalid community ID": "Invalid community ID",
|
||||||
"'%(groupId)s' is not a valid community ID": "'%(groupId)s' is not a valid 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:",
|
"Showing flair for these communities:": "Showing flair for these communities:",
|
||||||
|
@ -925,7 +933,6 @@
|
||||||
"Verify...": "Verify...",
|
"Verify...": "Verify...",
|
||||||
"Join": "Join",
|
"Join": "Join",
|
||||||
"No results": "No results",
|
"No results": "No results",
|
||||||
"Delete": "Delete",
|
|
||||||
"Communities": "Communities",
|
"Communities": "Communities",
|
||||||
"Home": "Home",
|
"Home": "Home",
|
||||||
"You cannot delete this image. (%(code)s)": "You cannot delete this image. (%(code)s)",
|
"You cannot delete this image. (%(code)s)": "You cannot delete this image. (%(code)s)",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue