diff --git a/css/base.css b/css/base.css
index 994b3dc9..2b651a79 100644
--- a/css/base.css
+++ b/css/base.css
@@ -1382,6 +1382,7 @@ ul.tabbar li.active > a:after {
padding: 0 0.15rem 0 0.15rem;
margin-left: 0.1rem;
margin-right: 0.1rem;
+ margin-bottom: 0.1rem;
border-radius: 0.15rem;
display: inline-block;
align-self: flex-start;
diff --git a/index.html b/index.html
index e8153e9b..d8b502a4 100644
--- a/index.html
+++ b/index.html
@@ -286,6 +286,23 @@
return undefined;
}
+ // Deep-shallow compare two arrays.
+ function arrayEqual(a, b) {
+ // Compare lengths first.
+ if (a.length != b.length) {
+ return false;
+ }
+ // Order of elements is ignored.
+ a.sort();
+ b.sort();
+ for (var i = 0, l = a.length; i < l; i++) {
+ if (a[i] !== b[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
// Convert a number of bytes to human-readable format.
function bytesToHumanSize(bytes) {
if (!bytes || bytes == 0) {
@@ -795,9 +812,12 @@
componentWillReceiveProps: function(newProps) {
this.setState({
sortedChips: this.sortChips(newProps.chips, newProps.required),
- chipIndex: this.indexChips(newProps.chips),
- input: ''
+ chipIndex: this.indexChips(newProps.chips)
});
+ if (newProps.chips.length > this.props.chips.length) {
+ // Chip added: clear input.
+ this.setState({input: ''});
+ }
},
// Map chip index to user name
@@ -1296,6 +1316,7 @@
:
@@ -1364,7 +1385,7 @@
avatar: null,
login: undefined,
password: undefined,
- tagInput: '',
+ previousOnTags: undefined,
tags: []
};
},
@@ -1377,12 +1398,20 @@
address: Tinode.getCurrentUserID(),
auth: defacs.auth,
anon: defacs.anon,
- tags: me.tags()
+ tags: me.tags(),
+ previousOnTags: me.onTagsUpdated,
+ showTagManager: false
});
+ me.onTagsUpdated = this.tnNewTags;
},
componentWillUnmount: function() {
- // Not needed?
+ var me = Tinode.getMeTopic();
+ me.onTagsUpdated = this.state.previousOnTags;
+ },
+
+ tnNewTags: function(tags) {
+ this.setState({tags: tags});
},
handleFullNameUpdate: function(fn) {
@@ -1398,34 +1427,30 @@
this.props.onSubmit(null, vcard(this.state.fullName, img));
},
- handleTagInput: function(text) {
- // Check if user entered ',', ' ' or ';'
- if (text.length > 0) {
- var last = text[text.length-1];
- if (last == ',' || last == ' ' || last == ';') {
- this.handleAddTag(text.substr(0, text.length-1));
- }
+ handleShowTagManager: function() {
+ this.setState({showTagManager: true});
+ },
+
+ handleHideTagManager: function() {
+ this.setState({showTagManager: false});
+ },
+
+ handleTagsUpdated: function(tags) {
+ this.setState({showTagManager: false});
+ // Check if tags have actually changed.
+ if (arrayEqual(this.state.tags.slice(0), tags.slice(0))) {
+ return;
}
- },
- handleAddTag: function(tag) {
- var tags = this.state.tags.slice(0);
- tags.push(tag);
- this.setState({tags: tags});
- },
- handleRemoveTag: function(tag, index) {
- var tags = this.state.tags.slice(0);
- tags.splice(index, 1);
- this.setState({tags: tags});
+ this.props.onUpdateTags(tags);
},
render: function() {
var tags = [];
- var instance = this;
this.state.tags.map(function(tag) {
- tags.push({user: tag});
+ tags.push({tag});
});
if (tags.length == 0) {
- tags = none;
+ tags = No tags defined. Add some.;
}
return (
@@ -1468,22 +1493,114 @@
-
-
-
-
+ {this.state.showTagManager ?
+
+ :
+
+ }
);
}
});
/* END EditAccount */
+ /* BEGIN TagManager: edit topic or user tags */
+ var TagManager = React.createClass({
+ getInitialState: function() {
+ return {
+ tags: this.wrapTags(this.props.tags),
+ tagInput: '',
+ noTagsMessage: 'Add some tags'
+ };
+ },
+
+ wrapTags: function(tags) {
+ var wrapped = [];
+ tags.map(function(tag) {
+ wrapped.push({user: tag});
+ });
+ return wrapped;
+ },
+
+ unwrapTags: function(tags) {
+ var unwrapped = [];
+ tags.map(function(tag) {
+ unwrapped.push(tag.user);
+ });
+ return unwrapped;
+ },
+
+ handleTagInput: function(text) {
+ this.setState({tagInput: text});
+ // Check if user entered ',', ' ' or ';'
+ if (text.length > 0) {
+ var last = text[text.length-1];
+ if (last == ',' || last == ' ' || last == ';') {
+ this.handleAddTag(text.substr(0, text.length-1));
+ }
+ }
+ },
+
+ handleAddTag: function(tag) {
+ tag = tag.trim();
+ if (tag.length > 0) {
+ var tags = this.state.tags.slice(0);
+ tags.push({user: tag});
+ this.setState({tags: tags, tagInput: ''});
+ }
+ },
+
+ handleRemoveTag: function(tag, index) {
+ var tags = this.state.tags.slice(0);
+ tags.splice(index, 1);
+ this.setState({tags: tags});
+ },
+
+ handleSubmit: function() {
+ var tags = this.unwrapTags(this.state.tags);
+ var inp = this.state.tagInput.trim();
+ if (inp.length > 0) {
+ tags.push(inp);
+ }
+ this.props.onSubmit(tags);
+ },
+
+ handleCancel: function() {
+ this.props.onCancel();
+ },
+
+ render: function() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+ });
+ /* END TagManager */
+
/* BEGIN Contact list (list of topics) */
/* A single topic */;
var Contact = React.createClass({
@@ -3607,6 +3724,14 @@
});
}
},
+
+ handleUpdateAccountTagsRequest: function(tags) {
+ var instance = this;
+ Tinode.getMeTopic().setMeta({tags: tags}).catch(function(err) {
+ instance.handleError(err.message, "err");
+ });
+ },
+
// User chose Settings menu item.
handleSettings: function() {
this.setState({sidePanelSelected: this.state.myUserId ? 'edit-account' : 'settings'});
@@ -3825,6 +3950,7 @@
onLoginRequest={this.handleLoginRequest}
onCreateAccount={this.handleNewAccountRequest}
onUpdateAccount={this.handleUpdateAccountRequest}
+ onUpdateAccountTags={this.handleUpdateAccountTagsRequest}
onTopicSelected={this.handleTopicSelected}
onCreateTopic={this.handleNewTopicRequest}
onNewTopic={this.handleNewTopic}