lets of bug fixes
This commit is contained in:
@@ -16,14 +16,14 @@ Background pattern from http://subtlepatterns.com/, commercial and non-commercia
|
|||||||
|
|
||||||
## Missing functionality
|
## Missing functionality
|
||||||
|
|
||||||
* Cannot manage group members.
|
* Cannot manage members of group chats.
|
||||||
* Editing chat parameters is incomplete and buggy.
|
|
||||||
* Cannot send images or other media.
|
* Cannot send images or other media.
|
||||||
* No typing indicators.
|
* No typing indicators.
|
||||||
* No indicators of chat status (muted, blocked, etc.).
|
* No indication of chat status (muted, blocked, etc.).
|
||||||
* Long polling as a wire transport does not work.
|
* Long polling as a wire transport does not work.
|
||||||
* Browser push notifications are missing.
|
* Browser push notifications are missing.
|
||||||
* No encryption.
|
* No encryption.
|
||||||
|
* Editing chat parameters is incomplete.
|
||||||
|
|
||||||
## Responsive design
|
## Responsive design
|
||||||
|
|
||||||
|
|||||||
+36
-8
@@ -37,6 +37,29 @@ a:focus {
|
|||||||
outline-offset: -2px;
|
outline-offset: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.red:hover,
|
||||||
|
a.red:focus {
|
||||||
|
color: #F44336;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.flat-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0.15rem 0.35rem;
|
||||||
|
height: 2em;
|
||||||
|
line-height: 2em;
|
||||||
|
font-weight: normal;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
touch-action: manipulation;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
a.flat-button:hover,
|
||||||
|
a.flat-button:focus {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
form {
|
form {
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.5rem 0.75rem;
|
||||||
@@ -226,6 +249,16 @@ ul.menu > li.separator:hover {
|
|||||||
}
|
}
|
||||||
/* END popup/dropdown menu */
|
/* END popup/dropdown menu */
|
||||||
|
|
||||||
|
/* Generic text colors */
|
||||||
|
.blue {
|
||||||
|
color: #2196f3; /* blue 500 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.red {
|
||||||
|
color: #D32F2F; /* red 700 */
|
||||||
|
}
|
||||||
|
/* END generic text colors */
|
||||||
|
|
||||||
/* END of basic elements */
|
/* END of basic elements */
|
||||||
|
|
||||||
#mountPoint {
|
#mountPoint {
|
||||||
@@ -447,9 +480,9 @@ ul.menu > li.separator:hover {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contacts-panel {
|
.scrollable-panel {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
margin-top: 1rem;:
|
margin-top: 0.25rem;
|
||||||
margin-bottom: auto;
|
margin-bottom: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
@@ -1035,11 +1068,6 @@ ul.tabbar li.active > a:after {
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Color of buttons */
|
|
||||||
.blue {
|
|
||||||
color: #2196f3; /* blue 500 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Letter tile base style, responsive square box */
|
/* Letter tile base style, responsive square box */
|
||||||
.lettertile {
|
.lettertile {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -1178,7 +1206,7 @@ ul.tabbar li.active > a:after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#self-avatar.material-icons, .avatar-box .material-icons {
|
#self-avatar.material-icons, .avatar-box .material-icons {
|
||||||
font-size: 3rem;
|
font-size: 2.4rem;
|
||||||
}
|
}
|
||||||
.avatar-box .material-icons {
|
.avatar-box .material-icons {
|
||||||
color: rgba(250, 250, 250, 0.8); /* #fafafa, gray 50 */
|
color: rgba(250, 250, 250, 0.8); /* #fafafa, gray 50 */
|
||||||
|
|||||||
+200
-144
@@ -262,7 +262,8 @@
|
|||||||
handleEditingFinished: function() {
|
handleEditingFinished: function() {
|
||||||
this.setState({active: false});
|
this.setState({active: false});
|
||||||
var text = this.state.text.trim();
|
var text = this.state.text.trim();
|
||||||
if (text !== this.props.text) {
|
if ((text || this.props.text) && (text !== this.props.text)) {
|
||||||
|
console.log("'" + text + "' != '" + this.props.text + "'");
|
||||||
this.props.onFinished(text);
|
this.props.onFinished(text);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -452,7 +453,7 @@
|
|||||||
submitClasses += " disabled";
|
submitClasses += " disabled";
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<form className="panel-form" onSubmit={this.handleSubmit}>
|
<div className="panel-form">
|
||||||
<div className="panel-form-row">
|
<div className="panel-form-row">
|
||||||
<div className="panel-form-column">
|
<div className="panel-form-column">
|
||||||
<input type="text" placeholder="Login"
|
<input type="text" placeholder="Login"
|
||||||
@@ -464,12 +465,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<AvatarUpload onImageChanged={this.handleImageChanged} />
|
<AvatarUpload onImageChanged={this.handleImageChanged} />
|
||||||
</div>
|
</div>
|
||||||
<input type="text" placeholder="Full name, e.g. John Doe"
|
<div className="panel-form-row">
|
||||||
value={this.state.fn} onChange={this.handleFnChange} />
|
<input type="text" placeholder="Full name, e.g. John Doe"
|
||||||
<div className="dialog-buttons">
|
value={this.state.fn} onChange={this.handleFnChange} />
|
||||||
<button className={submitClasses} type="submit">Sign up</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
<div className="dialog-buttons">
|
||||||
|
<button className={submitClasses} onClick={this.handleSubmit}>Sign up</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -841,11 +844,10 @@
|
|||||||
} else if (title.length > 30) {
|
} else if (title.length > 30) {
|
||||||
title = title.substring(0, 28) + "...";
|
title = title.substring(0, 28) + "...";
|
||||||
}
|
}
|
||||||
var selected = (this.props.selected === this.props.topic) ? "selected" : null;
|
|
||||||
var online = this.props.now ? "online" : "offline";
|
var online = this.props.now ? "online" : "offline";
|
||||||
var avatar = this.props.avatar ? this.props.avatar : true;
|
var avatar = this.props.avatar ? this.props.avatar : true;
|
||||||
return (
|
return (
|
||||||
<li className={selected} onClick={this.handleClick}>
|
<li className={this.props.selected ? "selected" : null} onClick={this.handleClick}>
|
||||||
<div className="avatar-box">
|
<div className="avatar-box">
|
||||||
<LetterTile
|
<LetterTile
|
||||||
avatar={avatar} title={this.props.title} topic={this.props.topic} />
|
avatar={avatar} title={this.props.title} topic={this.props.topic} />
|
||||||
@@ -983,19 +985,37 @@
|
|||||||
var ContactList = React.createClass({
|
var ContactList = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
var contactNodes = [];
|
var contactNodes = [];
|
||||||
|
var instance = this;
|
||||||
if (this.props.contacts && this.props.contacts.length > 0) {
|
if (this.props.contacts && this.props.contacts.length > 0) {
|
||||||
this.props.contacts.map(function(c) {
|
this.props.contacts.map(function(c) {
|
||||||
var key = c.topic ? c.topic : c.user;
|
var key = c.topic ? c.topic : c.user;
|
||||||
if (!key) {
|
if (!key) {
|
||||||
console.log("empty key!");
|
console.log("empty key!");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If filter function is provided, filter out the items
|
||||||
|
// which don't satisfy the condition.
|
||||||
|
if (instance.props.filterFunc && instance.props.filter {
|
||||||
|
var content = [key];
|
||||||
|
if (c.private) {
|
||||||
|
content.push(c.private);
|
||||||
|
}
|
||||||
|
if (c.public && c.public.fn) {
|
||||||
|
content.push(c.public.fn);
|
||||||
|
}
|
||||||
|
if (!instance.props.filter(instance.props.filter, content)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
contactNodes.push(
|
contactNodes.push(
|
||||||
<Contact
|
<Contact
|
||||||
title={c.public ? c.public.fn : null}
|
title={c.public ? c.public.fn : null}
|
||||||
avatar={makeImageUrl(c.public ? c.public.photo : null)}
|
avatar={makeImageUrl(c.public ? c.public.photo : null)}
|
||||||
comment={c.private}
|
comment={c.private}
|
||||||
unread={c.unread} now={c.online}
|
unread={c.unread} now={c.online}
|
||||||
selected={this.props.topicSelected}
|
selected={this.props.topicSelected === key}
|
||||||
showOnline={this.props.showOnline}
|
showOnline={this.props.showOnline}
|
||||||
onSelected={this.props.onTopicSelected}
|
onSelected={this.props.onTopicSelected}
|
||||||
showContextMenu={this.props.showContextMenu}
|
showContextMenu={this.props.showContextMenu}
|
||||||
@@ -1006,7 +1026,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="contacts-panel">
|
<div className={this.props.noScroll ? null : "scrollable-panel"}>
|
||||||
{contactNodes.length > 0 ?
|
{contactNodes.length > 0 ?
|
||||||
<ul className="contact-box">
|
<ul className="contact-box">
|
||||||
{contactNodes}
|
{contactNodes}
|
||||||
@@ -1027,6 +1047,7 @@
|
|||||||
return {
|
return {
|
||||||
tabSelected: "p2p",
|
tabSelected: "p2p",
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
|
contactList: [],
|
||||||
contactSelected: null
|
contactSelected: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -1036,6 +1057,7 @@
|
|||||||
fnd.onMetaDesc = this.fndMetaDesc;
|
fnd.onMetaDesc = this.fndMetaDesc;
|
||||||
if (fnd.isSubscribed()) {
|
if (fnd.isSubscribed()) {
|
||||||
this.setState({searchQuery: fnd.private ? fnd.private.toString() : ''});
|
this.setState({searchQuery: fnd.private ? fnd.private.toString() : ''});
|
||||||
|
this.fndSubsUpdated();
|
||||||
} else {
|
} else {
|
||||||
fnd.subscribe({get: {sub: {}, desc: {}}});
|
fnd.subscribe({get: {sub: {}, desc: {}}});
|
||||||
}
|
}
|
||||||
@@ -1051,8 +1073,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
fndSubsUpdated: function() {
|
fndSubsUpdated: function() {
|
||||||
//console.log(keys);
|
var contacts = [];
|
||||||
this.forceUpdate();
|
// Don't attempt to create P2P topics which already exist. Server will reject the duplicates.
|
||||||
|
Tinode.getFndTopic().subscribers(function(s) {
|
||||||
|
contacts.push(s);
|
||||||
|
});
|
||||||
|
this.setState({contactList: contacts});
|
||||||
},
|
},
|
||||||
handleTabClick: function(e) {
|
handleTabClick: function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -1086,7 +1112,13 @@
|
|||||||
<NewTopicView.SearchContacts type="p2p"
|
<NewTopicView.SearchContacts type="p2p"
|
||||||
searchQuery={this.state.searchQuery}
|
searchQuery={this.state.searchQuery}
|
||||||
onSearchContacts={this.props.onSearchContacts} />
|
onSearchContacts={this.props.onSearchContacts} />
|
||||||
<NewTopicView.ContactsList onContactSelected={this.handleContactSelected} type="p2p" />
|
<ContactList
|
||||||
|
contacts={this.state.contactList}
|
||||||
|
emptyListMessage="Use search to find contacts"
|
||||||
|
topicSelected={this.state.selectedContact}
|
||||||
|
showOnline={false}
|
||||||
|
showContextMenu={false}
|
||||||
|
onTopicSelected={this.handleContactSelected} />
|
||||||
</div>}
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -1097,19 +1129,24 @@
|
|||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
fn: '', // full/formatted name
|
fn: '', // full/formatted name
|
||||||
|
private: '',
|
||||||
imageDataUrl: null
|
imageDataUrl: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
handleFnChange: function(e) {
|
handleFnChange: function(e) {
|
||||||
this.setState({fn: e.target.value});
|
this.setState({fn: e.target.value});
|
||||||
},
|
},
|
||||||
|
handlePrivateChange: function(e) {
|
||||||
|
this.setState({private: e.target.value});
|
||||||
|
},
|
||||||
handleImageChanged: function(img) {
|
handleImageChanged: function(img) {
|
||||||
this.setState({imageDataUrl: img});
|
this.setState({imageDataUrl: img});
|
||||||
},
|
},
|
||||||
handleSubmit: function(e) {
|
handleSubmit: function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (this.state.fn && this.state.fn.trim()) {
|
if (this.state.fn && this.state.fn.trim()) {
|
||||||
this.props.onSubmit(this.state.fn.trim(), this.state.imageDataUrl);
|
this.props.onSubmit(this.state.fn.trim(),
|
||||||
|
this.state.imageDataUrl, this.state.private.trim());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
@@ -1118,18 +1155,22 @@
|
|||||||
submitClasses += " disabled";
|
submitClasses += " disabled";
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<form className="panel-form" onSubmit={this.handleSubmit}>
|
<div className="panel-form">
|
||||||
<div className="panel-form-row">
|
<div className="panel-form-row">
|
||||||
<div className="panel-form-column">
|
<div className="panel-form-column">
|
||||||
<input type="text" placeholder="Name of the topic to create"
|
<label className="small" htmlFor="new-topic-fn">Group name</label>
|
||||||
|
<input type="text" id="new-topic-fn" placeholder="Freeform name of the group"
|
||||||
value={this.state.fn} onChange={this.handleFnChange} autoFocus required />
|
value={this.state.fn} onChange={this.handleFnChange} autoFocus required />
|
||||||
<div className="dialog-buttons">
|
<label className="small" htmlFor="new-topic-priv">Private comment</label>
|
||||||
<button className={submitClasses} type="submit">Create</button>
|
<input type="text" id="new-topic-priv" placeholder="Visible to you only"
|
||||||
</div>
|
value={this.state.private} onChange={this.handlePrivateChange} />
|
||||||
</div>
|
</div>
|
||||||
<AvatarUpload onImageChanged={this.handleImageChanged} />
|
<AvatarUpload onImageChanged={this.handleImageChanged} />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
<div className="dialog-buttons">
|
||||||
|
<button className={submitClasses} onClick={this.handleSubmit}>Create</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1161,55 +1202,15 @@
|
|||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<form className="panel-form" onSubmit={this.handleFindContacts}>
|
<div className="panel-form">
|
||||||
<div className="panel-form-row">
|
<div className="panel-form-row">
|
||||||
<input type="text" placeholder="Comma separated list: email:alice@example.com, tel:17025550003..."
|
<input type="text" placeholder="List like email:alice@example.com, tel:17025550003..."
|
||||||
value={this.state.search} onChange={this.handleSearchChange} onKeyPress={this.handleKeyPress} required />
|
value={this.state.search} onChange={this.handleSearchChange}
|
||||||
|
onKeyPress={this.handleKeyPress} required />
|
||||||
<a href="#" onClick={this.handleSearch}>
|
<a href="#" onClick={this.handleSearch}>
|
||||||
<i className="material-icons">search</i>
|
<i className="material-icons">search</i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
NewTopicView.ContactsList = React.createClass({
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
selectedUser: null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
handleSelectionChange: function(sel) {
|
|
||||||
if (this.props.type === "p2p") {
|
|
||||||
this.props.onContactSelected(sel);
|
|
||||||
} else {
|
|
||||||
console.log(sel);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render: function() {
|
|
||||||
var contactNodes = [];
|
|
||||||
// Don't attempt to create P2P topics which already exist. Server will reject the duplicates.
|
|
||||||
var me = Tinode.getMeTopic();
|
|
||||||
Tinode.getFndTopic().subscribers(function(s) {
|
|
||||||
var cont = me ? me.getContact(s.user) : null;
|
|
||||||
var key = cont ? cont.topic : s.user;
|
|
||||||
|
|
||||||
contactNodes.push(
|
|
||||||
<Contact
|
|
||||||
title={((s.public && s.public.fn) || s.private)}
|
|
||||||
avatar={makeImageUrl(s.public ? s.public.photo : null)}
|
|
||||||
selected={this.state.selectedUser}
|
|
||||||
onSelected={this.handleSelectionChange}
|
|
||||||
topic={key}
|
|
||||||
key={key} />
|
|
||||||
);
|
|
||||||
}, this);
|
|
||||||
return (
|
|
||||||
<div className="contacts-panel">
|
|
||||||
<ul className="contact-box">
|
|
||||||
{contactNodes}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1228,6 +1229,9 @@
|
|||||||
private: null,
|
private: null,
|
||||||
selectedContact: null,
|
selectedContact: null,
|
||||||
muted: undefined,
|
muted: undefined,
|
||||||
|
access: null,
|
||||||
|
auth: null,
|
||||||
|
anon: null,
|
||||||
contactList: []
|
contactList: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -1267,14 +1271,17 @@
|
|||||||
var acs = topic.getAccessMode();
|
var acs = topic.getAccessMode();
|
||||||
this.setState({
|
this.setState({
|
||||||
owner: acs && acs.isOwner(),
|
owner: acs && acs.isOwner(),
|
||||||
|
sharer: acs && (acs.isAdmin() || acs.isSharer()),
|
||||||
|
muted: acs && acs.isMuted(),
|
||||||
|
|
||||||
fullName: topic.public ? topic.public.fn : undefined,
|
fullName: topic.public ? topic.public.fn : undefined,
|
||||||
avatar: makeImageUrl(topic.public ? topic.public.photo : null),
|
avatar: makeImageUrl(topic.public ? topic.public.photo : null),
|
||||||
private: topic.private,
|
private: topic.private,
|
||||||
address: topic.name,
|
address: topic.name,
|
||||||
groupTopic: (topic.getType() === "grp"),
|
groupTopic: (topic.getType() === "grp"),
|
||||||
|
access: acs.getMode(),
|
||||||
auth: defacs.auth,
|
auth: defacs.auth,
|
||||||
anon: defacs.anon,
|
anon: defacs.anon
|
||||||
muted: acs && acs.isMuted()
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1295,24 +1302,28 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
handleFullNameUpdate: function(fn) {
|
handleFullNameUpdate: function(fn) {
|
||||||
this.setState({fullName: fn});
|
if (this.state.fullName !== fn) {
|
||||||
this.props.onSubmit(this.props.topic,
|
this.setState({fullName: fn});
|
||||||
vcard(fn, this.state.avatar),
|
this.props.onSubmit(this.props.topic,
|
||||||
null);
|
vcard(fn, this.state.avatar),
|
||||||
|
null);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handlePrivateUpdate: function(priv) {
|
handlePrivateUpdate: function(priv) {
|
||||||
this.setState({private: priv});
|
if (this.state.priv !== priv) {
|
||||||
this.props.onSubmit(this.props.topic,
|
this.setState({private: priv});
|
||||||
null,
|
this.props.onSubmit(this.props.topic,
|
||||||
priv);
|
null,
|
||||||
|
priv);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleImageChanged: function(img) {
|
handleImageChanged: function(img) {
|
||||||
this.setState({avatar: img});
|
this.setState({avatar: img});
|
||||||
this.props.onSubmit(this.props.topic,
|
this.props.onSubmit(this.props.topic,
|
||||||
vcard(this.state.fullName, img),
|
vcard(this.state.fullName, img),
|
||||||
priv);
|
null);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleMuted: function(e) {
|
handleMuted: function(e) {
|
||||||
@@ -1321,6 +1332,14 @@
|
|||||||
this.props.onChangePermissions(this.props.topic, checked ? "-P" : "+P");
|
this.props.onChangePermissions(this.props.topic, checked ? "-P" : "+P");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleAddMembers: function() {
|
||||||
|
this.props.onAddMember(this.props.topic);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleLeave: function() {
|
||||||
|
this.props.onLeaveTopic(this.props.topic);
|
||||||
|
},
|
||||||
|
|
||||||
handleMemberSelected: function(uid) {
|
handleMemberSelected: function(uid) {
|
||||||
this.setState({selectedContact: uid});
|
this.setState({selectedContact: uid});
|
||||||
},
|
},
|
||||||
@@ -1329,15 +1348,6 @@
|
|||||||
console.log("context menu: " + item);
|
console.log("context menu: " + item);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSubmit: function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (this.state.updated) {
|
|
||||||
this.props.onSubmit(this.props.topic,
|
|
||||||
vcard(this.state.fullName,this.state.avatar),
|
|
||||||
this.state.private);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<div id="info-view">
|
<div id="info-view">
|
||||||
@@ -1347,63 +1357,89 @@
|
|||||||
<MenuCancel onCancel={this.props.onCancel} />
|
<MenuCancel onCancel={this.props.onCancel} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="panel-form-row">
|
<div id="info-view-content" className="scrollable-panel">
|
||||||
|
<div className="panel-form-row">
|
||||||
|
<div className="panel-form-column">
|
||||||
|
<div><label className="small">Name</label></div>
|
||||||
|
<div><InPlaceEdit
|
||||||
|
placeholder="Group name"
|
||||||
|
readOnly={!this.state.owner}
|
||||||
|
text={this.state.fullName}
|
||||||
|
onFinished={this.handleFullNameUpdate} /></div>
|
||||||
|
<div><label className="small">Private comment</label></div>
|
||||||
|
<div><InPlaceEdit
|
||||||
|
placeholder="Visible to you only"
|
||||||
|
text={this.state.private}
|
||||||
|
onFinished={this.handlePrivateUpdate} /></div>
|
||||||
|
</div>
|
||||||
|
<AvatarUpload
|
||||||
|
avatar={this.state.avatar}
|
||||||
|
readOnly={!this.state.owner}
|
||||||
|
uid={this.props.topic}
|
||||||
|
title={this.state.fullName}
|
||||||
|
onImageChanged={this.handleImageChanged} />
|
||||||
|
</div>
|
||||||
<div className="panel-form-column">
|
<div className="panel-form-column">
|
||||||
<div><label className="small">Name</label></div>
|
<div className="panel-form-row">
|
||||||
<div><InPlaceEdit
|
<label>Address:</label>
|
||||||
placeholder="Group name"
|
<tt>{this.state.address}</tt>
|
||||||
readOnly={!this.state.owner}
|
</div>
|
||||||
text={this.state.fullName}
|
<div className="panel-form-row">
|
||||||
onFinished={this.handleFullNameUpdate} /></div>
|
<label>Permissions:</label>
|
||||||
<div><label className="small">Private comment</label></div>
|
<tt>{this.state.access}</tt>
|
||||||
<div><InPlaceEdit
|
</div>
|
||||||
placeholder="Visible to you only"
|
<div className="panel-form-row">
|
||||||
text={this.state.private}
|
<label htmlFor="info-muted">Muted:</label>
|
||||||
onFinished={this.handlePrivateUpdate} /></div>
|
<input id="info-muted" type="checkbox" checked={this.state.muted}
|
||||||
</div>
|
onChange={this.handleMuted} />
|
||||||
<AvatarUpload
|
</div>
|
||||||
avatar={this.state.avatar}
|
{this.state.auth || this.state.anon ?
|
||||||
readOnly={!this.state.owner}
|
<div>
|
||||||
uid={this.props.topic}
|
<div><label className="small">Default access mode:</label></div>
|
||||||
title={this.state.fullName}
|
<div className="quoted">
|
||||||
onImageChanged={this.handleImageChanged} />
|
<div>Auth: <tt>{this.state.auth}</tt></div>
|
||||||
</div>
|
<div>Anon: <tt>{this.state.anon}</tt></div>
|
||||||
<div className="panel-form-column">
|
</div>
|
||||||
<div className="panel-form-row">
|
|
||||||
<label>Address:</label>
|
|
||||||
<tt>{this.state.address}</tt>
|
|
||||||
</div>
|
|
||||||
<div className="panel-form-row">
|
|
||||||
<label htmlFor="info-muted">Muted:</label>
|
|
||||||
<input id="info-muted" type="checkbox" checked={this.state.muted}
|
|
||||||
onChange={this.handleMuted} />
|
|
||||||
</div>
|
|
||||||
{this.state.auth || this.state.anon ?
|
|
||||||
<div>
|
|
||||||
<div><label className="small">Default access mode:</label></div>
|
|
||||||
<div className="quoted">
|
|
||||||
<div>Auth: <tt>{this.state.auth}</tt></div>
|
|
||||||
<div>Anon: <tt>{this.state.anon}</tt></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
:
|
||||||
|
null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
{this.state.groupTopic ?
|
||||||
|
<div className="panel-form-column">
|
||||||
|
<div className="panel-form-row">
|
||||||
|
<label className="small">Group members:</label>
|
||||||
|
</div>
|
||||||
|
<div className="panel-form-row">
|
||||||
|
{this.state.sharer ?
|
||||||
|
<a href="#" className="flat-button" onClick={this.handleAddMembers}>
|
||||||
|
<i className="material-icons">person_add</i> Add members
|
||||||
|
</a>
|
||||||
|
: null}
|
||||||
|
{!this.state.owner ?
|
||||||
|
<a href="#" className="red flat-button" onClick={this.handleLeave}>
|
||||||
|
<i className="material-icons">exit_to_app</i> Leave
|
||||||
|
</a>
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
<ContactList
|
||||||
|
contacts={this.state.contactList}
|
||||||
|
emptyListMessage="No members"
|
||||||
|
topicSelected={this.state.selectedContact}
|
||||||
|
showOnline={false}
|
||||||
|
noScroll={true}
|
||||||
|
onTopicSelected={this.handleMemberSelected}
|
||||||
|
showContextMenu={this.handleContextMenu}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
null
|
<div className="panel-form-row">
|
||||||
|
<a href="#" className="red flat-button" onClick={this.handleLeave}>
|
||||||
|
<i className="material-icons">exit_to_app</i> Leave
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
{this.state.groupTopic ?
|
|
||||||
<div className="panel-form-row">
|
|
||||||
<label className="small">Group members:</label>
|
|
||||||
</div> : null}
|
|
||||||
{this.state.groupTopic ?
|
|
||||||
<ContactList
|
|
||||||
contacts={this.state.contactList}
|
|
||||||
emptyListMessage="No members"
|
|
||||||
topicSelected={this.state.selectedContact}
|
|
||||||
showOnline={false}
|
|
||||||
onTopicSelected={this.handleMemberSelected}
|
|
||||||
showContextMenu={this.handleContextMenu}
|
|
||||||
/>
|
|
||||||
: null}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2027,7 +2063,8 @@
|
|||||||
errorText: '',
|
errorText: '',
|
||||||
topicSelected: peerName,
|
topicSelected: peerName,
|
||||||
sidePanelSelected: 'contacts',
|
sidePanelSelected: 'contacts',
|
||||||
mobilePanel: 'topic-view'
|
mobilePanel: 'topic-view',
|
||||||
|
showInfoPanel: false
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
var topic = peerName ? Tinode.newTopicWith(peerName) : Tinode.newTopic();
|
var topic = peerName ? Tinode.newTopicWith(peerName) : Tinode.newTopic();
|
||||||
@@ -2041,7 +2078,8 @@
|
|||||||
errorText: '',
|
errorText: '',
|
||||||
topicSelected: topic.name,
|
topicSelected: topic.name,
|
||||||
sidePanelSelected: 'contacts',
|
sidePanelSelected: 'contacts',
|
||||||
mobilePanel: 'topic-view'
|
mobilePanel: 'topic-view',
|
||||||
|
showInfoPanel: false
|
||||||
});
|
});
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
instance.setState({errorText: err.message});
|
instance.setState({errorText: err.message});
|
||||||
@@ -2049,20 +2087,36 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleManageGroupMembers: function(topicName) {
|
||||||
|
// FIXME(gene): launch group member manager
|
||||||
|
},
|
||||||
|
|
||||||
handleTopicUpdateRequest: function(topicName, pub, priv) {
|
handleTopicUpdateRequest: function(topicName, pub, priv) {
|
||||||
var topic = Tinode.getTopic(topicName);
|
var topic = Tinode.getTopic(topicName);
|
||||||
topic.setMeta({desc: {public: pub, private: priv}});
|
if (topic) {
|
||||||
|
topic.setMeta({desc: {public: pub, private: priv}});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleChangePermissions: function(topicName, want) {
|
handleChangePermissions: function(topicName, want) {
|
||||||
|
var topic = Tinode.getTopic(topicName);
|
||||||
|
if (topic) {
|
||||||
|
var am = topic.getAccessMode();
|
||||||
|
am.updateWant(want);
|
||||||
|
topic.setMeta({sub: {mode: am.getWant()}});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleLeaveUnsubRequest: function(topicName) {
|
||||||
var topic = Tinode.getTopic(topicName);
|
var topic = Tinode.getTopic(topicName);
|
||||||
if (!topic) {
|
if (!topic) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var instance = this;
|
||||||
var am = topic.getAccessMode();
|
topic.leave(true).then(function(ctrl) {
|
||||||
am.updateWant(want);
|
// Hide MessageView and InfoView panels.
|
||||||
topic.setMeta({sub: {mode: am.getWant()}});
|
instance.setState({topicSelected: '', showInfoPanel: false});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/** Called when the user enters a contact into the contact search field in the NewAccount panel
|
/** Called when the user enters a contact into the contact search field in the NewAccount panel
|
||||||
@@ -2070,7 +2124,7 @@
|
|||||||
*/
|
*/
|
||||||
handleSearchContacts: function(query) {
|
handleSearchContacts: function(query) {
|
||||||
var find = Tinode.getFndTopic();
|
var find = Tinode.getFndTopic();
|
||||||
find.setMeta({desc: {private: query}}).then(function() {
|
find.setMeta({desc: {private: query}}).then(function(ctrl) {
|
||||||
find.getMeta({sub: {}, desc: {}});
|
find.getMeta({sub: {}, desc: {}});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -2224,6 +2278,8 @@
|
|||||||
onSubmit={this.handleTopicUpdateRequest}
|
onSubmit={this.handleTopicUpdateRequest}
|
||||||
onCancel={this.handleHideInfoView}
|
onCancel={this.handleHideInfoView}
|
||||||
onChangePermissions={this.handleChangePermissions}
|
onChangePermissions={this.handleChangePermissions}
|
||||||
|
onLeaveTopic={this.handleLeaveUnsubRequest}
|
||||||
|
onAddMember={this.handleManageGroupMembers}
|
||||||
/>
|
/>
|
||||||
:
|
:
|
||||||
null
|
null
|
||||||
|
|||||||
Reference in New Issue
Block a user