@@ -2316,48 +2467,62 @@
);
}
- });
+ };
/* END GroupManager */
/* BEGIN Create new topic and invite users or send an invite */
- var NewTopicView = React.createClass({
- getInitialState: function() {
- return {
+ class NewTopicView extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
tabSelected: "p2p",
- searchQuery: this.props.contactsSearchQuery,
- contactList: this.props.foundContacts,
+ searchQuery: props.contactsSearchQuery,
+ contactList: props.foundContacts,
contactSelected: null
};
- },
- componentWillMount: function() {
+
+ this.handleTabClick = this.handleTabClick.bind(this);
+ this.handleContactSelected = this.handleContactSelected.bind(this);
+ this.handleNewGroupSubmit = this.handleNewGroupSubmit.bind(this);
+ this.handleGroupByID = this.handleGroupByID.bind(this);
+ }
+
+ componentWillMount() {
this.props.onInitFind();
- },
- componentWillReceiveProps: function(nextProps) {
+ }
+
+ componentWillReceiveProps(nextProps) {
this.setState({
searchQuery: nextProps.contactsSearchQuery,
contactList: nextProps.foundContacts
});
- },
- handleTabClick: function(e) {
+ }
+
+ handleTabClick(e) {
e.preventDefault();
window.location.hash = addUrlParam(window.location.hash, 'tab', e.currentTarget.dataset.id);
this.setState({tabSelected: e.currentTarget.dataset.id, contactSelected: null});
- },
- handleContactSelected: function(sel) {
+ }
+
+ handleContactSelected(sel) {
if (this.state.tabSelected === "p2p") {
window.location.hash = removeUrlParam(window.location.hash, 'tab');
this.props.onCreateTopic(sel, undefined);
}
- },
- handleNewGroupSubmit: function(name, dataUrl, priv, tags) {
+ }
+
+ handleNewGroupSubmit(name, dataUrl, priv, tags) {
window.location.hash = removeUrlParam(window.location.hash, 'tab');
this.props.onCreateTopic(undefined, vcard(name, dataUrl), priv, tags);
- },
- handleGroupByID: function(topicName) {
+ }
+
+ handleGroupByID(topicName) {
window.location.hash = removeUrlParam(window.location.hash, 'tab');
this.props.onCreateTopic(topicName);
- },
- render: function() {
+ }
+
+ render() {
return (
{this.state.tabSelected === "grp" ?
-
:
+
:
this.state.tabSelected === "byid" ?
-
:
-
);
}
- });
+ };
- NewTopicView.Group = React.createClass({
- getInitialState: function() {
- return {
+ class NewTopicGroup extends React.PureComponent {
+ constructor(props) {
+ super(props);
+
+ this.state = {
fn: '', // full/formatted name
private: '',
imageDataUrl: null,
tags: []
};
- },
- handleFnChange: function(e) {
+
+ this.handleFnChange = this.handleFnChange.bind(this);
+ this.handlePrivateChange = this.handlePrivateChange.bind(this);
+ this.handleImageChanged = this.handleImageChanged.bind(this);
+ this.handleTagsChanged = this.handleTagsChanged.bind(this);
+ this.handleTagsChanged = this.handleTagsChanged.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+ }
+
+ handleFnChange(e) {
this.setState({fn: e.target.value});
- },
- handlePrivateChange: function(e) {
+ }
+
+ handlePrivateChange(e) {
this.setState({private: e.target.value});
- },
- handleImageChanged: function(img) {
+ }
+
+ handleImageChanged(img) {
this.setState({imageDataUrl: img});
- },
- handleTagsChanged: function(tags) {
+ }
+
+ handleTagsChanged(tags) {
this.setState({tags: tags});
- },
- handleSubmit: function(e) {
+ }
+
+ handleSubmit(e) {
e.preventDefault();
if (this.state.fn && this.state.fn.trim()) {
this.props.onSubmit(this.state.fn.trim(),
this.state.imageDataUrl, this.state.private.trim(),
this.state.tags);
}
- },
- render: function() {
+ }
+
+ render() {
var submitClasses = "blue";
if (this.props.disabled) {
submitClasses += " disabled";
@@ -2456,44 +2636,57 @@
);
}
- });
+ };
- NewTopicView.SearchContacts = React.createClass({
- getInitialState: function() {
- return {
+ class SearchContacts extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
edited: false,
- search: this.props.searchQuery
+ search: props.searchQuery
};
- },
- componentWillUnmount: function() {
+
+ this.handleSearchChange = this.handleSearchChange.bind(this);
+ this.handleSearch = this.handleSearch.bind(this);
+ this.handleClear = this.handleClear.bind(this);
+ this.handleKeyPress = this.handleKeyPress.bind(this);
+ }
+
+ componentWillUnmount() {
if (this.state.search) {
this.setState({search: ''});
this.props.onSearchContacts(DEL_CHAR);
}
- },
- handleSearchChange: function(e) {
+ }
+
+ handleSearchChange(e) {
this.setState({search: e.target.value});
- },
- handleSearch: function(e) {
+ }
+
+ handleSearch(e) {
e.preventDefault();
var query = this.state.search.trim();
if (query.length > 0) {
this.setState({edited: true});
this.props.onSearchContacts(query);
}
- },
- handleClear: function() {
+ }
+
+ handleClear() {
if (this.state.edited || this.state.search) {
this.props.onSearchContacts(DEL_CHAR);
}
this.setState({search: '', edited: false});
- },
- handleKeyPress: function(e) {
+ }
+
+ handleKeyPress(e) {
if (e.key === 'Enter') {
this.handleSearch(e);
}
- },
- render: function() {
+ }
+
+ render() {
return (
@@ -2509,23 +2702,32 @@
);
}
- });
+ };
- NewTopicView.ById = React.createClass({
- getInitialState: function() {
- return {
+ class NewTopicById extends React.PureComponent {
+ constructor(props) {
+ super(props);
+
+ this.state = {
groupId: '',
};
- },
- handleChange: function(e) {
+
+ this.handleChange = this.handleChange.bind(this);
+ this.handleKeyPress = this.handleKeyPress.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+ }
+
+ handleChange(e) {
this.setState({groupId: e.target.value});
- },
- handleKeyPress: function(e) {
+ }
+
+ handleKeyPress(e) {
if (e.key === 'Enter') {
this.handleSubmit(e);
}
- },
- handleSubmit: function(e) {
+ }
+
+ handleSubmit(e) {
e.preventDefault();
if (this.state.groupId) {
var name = this.state.groupId.trim();
@@ -2535,8 +2737,9 @@
this.props.onError("Invalid ID", "err");
}
}
- },
- render: function() {
+ }
+
+ render() {
return (
@@ -2550,34 +2753,45 @@
);
}
- });
+ };
/* END Create new topic and invite users or send an invite */
/* BEGIN ValidationView: panel for confirming credentials, like email or phone */
- var ValidationView = React.createClass({
- getInitialState: function() {
- return {
- code: this.props.credCode
+ class ValidationView extends React.PureComponent {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ code: props.credCode
};
- },
- componentWillReceiveProps: function(nextProps) {
+
+ this.handleChange = this.handleChange.bind(this);
+ this.handleKeyPress = this.handleKeyPress.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+ }
+
+ componentWillReceiveProps(nextProps) {
this.setState({code: nextProps.credCode});
- },
- handleChange: function(e) {
+ }
+
+ handleChange(e) {
this.setState({code: e.target.value});
- },
- handleKeyPress: function(e) {
+ }
+
+ handleKeyPress(e) {
if (e.key === 'Enter') {
this.handleSubmit(e);
}
- },
- handleSubmit: function(e) {
+ }
+
+ handleSubmit(e) {
e.preventDefault();
if (this.state.code && this.state.code.trim()) {
this.props.onSubmit(this.props.credMethod, this.state.code.trim());
}
- },
- render: function() {
+ }
+
+ render() {
var methods = {'email': 'email', 'tel': 'phone'};
var method = methods[this.props.credMethod] || this.props.credMethod;
return (
@@ -2598,13 +2812,15 @@
);
}
- });
+ };
/* END ValidationView */
/* BEGIN InfoView: panel with topic/user info */
- var InfoView = React.createClass({
- getInitialState: function() {
- return {
+ class InfoView extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
topic: null,
owner: false,
admin: false,
@@ -2625,16 +2841,38 @@
anon: null,
contactList: [],
tags: [],
- showMemberPanel: this.props.showMemberPanel,
+ showMemberPanel: props.showMemberPanel,
showPermissionEditorFor: undefined,
moreInfoExpanded: false,
previousMetaDesc: undefined,
previousSubsUpdated: undefined,
previousTagsUpdated: undefined
};
- },
- initInfoView: function(props) {
+ this.initInfoView = this.initInfoView.bind(this);
+ this.resetSubs = this.resetSubs.bind(this);
+ this.resetDesc = this.resetDesc.bind(this);
+ this.onMetaDesc = this.onMetaDesc.bind(this);
+ this.onSubsUpdated = this.onSubsUpdated.bind(this);
+ this.onTagsUpdated = this.onTagsUpdated.bind(this);
+ this.handleFullNameUpdate = this.handleFullNameUpdate.bind(this);
+ this.handlePrivateUpdate = this.handlePrivateUpdate.bind(this);
+ this.handleImageChanged = this.handleImageChanged.bind(this);
+ this.handleMuted = this.handleMuted.bind(this);
+ this.handlePermissionsChanged = this.handlePermissionsChanged.bind(this);
+ this.handleLaunchPermissionsEditor = this.handleLaunchPermissionsEditor.bind(this);
+ this.handleHidePermissionsEditor = this.handleHidePermissionsEditor.bind(this);
+ this.handleShowAddMembers = this.handleShowAddMembers.bind(this);
+ this.handleHideAddMembers = this.handleHideAddMembers.bind(this);
+ this.handleMemberUpdateRequest = this.handleMemberUpdateRequest.bind(this);
+ this.handleLeave = this.handleLeave.bind(this);
+ this.handleMemberSelected = this.handleMemberSelected.bind(this);
+ this.handleMoreInfo = this.handleMoreInfo.bind(this);
+ this.handleTagsUpdated = this.handleTagsUpdated.bind(this);
+ this.handleContextMenu = this.handleContextMenu.bind(this);
+ }
+
+ initInfoView(props) {
var topic = Tinode.getTopic(props.topic);
if (!topic) {
return;
@@ -2664,14 +2902,14 @@
this.setState({tags: topic.tags()});
}
}
- },
+ }
// No need to separately handle componentWillMount.
- componentWillReceiveProps: function(nextProps) {
+ componentWillReceiveProps(nextProps) {
this.initInfoView(nextProps);
- },
+ }
- componentWillUnmount: function() {
+ componentWillUnmount() {
var topic = Tinode.getTopic(this.props.topic);
if (!topic) {
return;
@@ -2680,9 +2918,9 @@
topic.onMetaDesc = this.previousMetaDesc;
topic.onSubsUpdated = this.previousSubsUpdated;
topic.onTagsUpdated = this.previousTagsUpdated;
- },
+ }
- resetSubs: function(topic, props) {
+ resetSubs(topic, props) {
var newState = {contactList: []};
if (topic.getType() === "p2p") {
// Fetch the other party in the p2p conversation.
@@ -2699,9 +2937,9 @@
}
this.setState(newState);
- },
+ }
- resetDesc: function(topic, props) {
+ resetDesc(topic, props) {
var defacs = topic.getDefaultAccess() || {};
var acs = topic.getAccessMode();
this.setState({
@@ -2721,9 +2959,9 @@
auth: defacs.auth,
anon: defacs.anon
});
- },
+ }
- onMetaDesc: function(desc) {
+ onMetaDesc(desc) {
var topic = Tinode.getTopic(this.props.topic);
if (!topic) {
return;
@@ -2733,9 +2971,9 @@
if (this.previousMetaDesc && this.previousMetaDesc != this.onMetaDesc) {
this.previousMetaDesc(desc);
}
- },
+ }
- onSubsUpdated: function() {
+ onSubsUpdated() {
var topic = Tinode.getTopic(this.props.topic);
if (!topic) {
return;
@@ -2745,47 +2983,47 @@
if (this.previousSubsUpdated && this.previousSubsUpdated != this.onSubsUpdated) {
this.previousSubsUpdated();
}
- },
+ }
- onTagsUpdated: function(tags) {
+ onTagsUpdated(tags) {
this.setState({tags: tags});
if (this.previousTagsUpdated && this.previousTagsUpdated != this.onTagsUpdated) {
this.previousTagsUpdated();
}
- },
+ }
- handleFullNameUpdate: function(fn) {
+ handleFullNameUpdate(fn) {
if (this.state.fullName !== fn) {
this.setState({fullName: fn});
this.props.onTopicDescUpdate(this.props.topic,
vcard(fn, this.state.avatar),
null);
}
- },
+ }
- handlePrivateUpdate: function(priv) {
+ handlePrivateUpdate(priv) {
if (this.state.priv !== priv) {
this.setState({private: priv});
this.props.onTopicDescUpdate(this.props.topic,
null,
priv);
}
- },
+ }
- handleImageChanged: function(img) {
+ handleImageChanged(img) {
this.setState({avatar: img});
this.props.onTopicDescUpdate(this.props.topic,
vcard(this.state.fullName, img),
null);
- },
+ }
- handleMuted: function(ignored, checked) {
+ handleMuted(ignored, checked) {
this.setState({muted: checked});
this.props.onChangePermissions(this.props.topic, checked ? "-P" : "+P");
- },
+ }
- handlePermissionsChanged: function(perm) {
+ handlePermissionsChanged(perm) {
switch (this.state.showPermissionEditorFor) {
case 'auth':
this.props.onTopicDescUpdate(this.props.topic, null, null, {auth: perm});
@@ -2806,9 +3044,9 @@
}
this.setState({showPermissionEditorFor: undefined});
- },
+ }
- handleLaunchPermissionsEditor: function(which, uid) {
+ handleLaunchPermissionsEditor(which, uid) {
var toEdit, toCompare, toSkip, titleEdit, titleCompare, userTitle, userAvatar
switch (which) {
case 'mode':
@@ -2871,43 +3109,45 @@
immutablePermissionsTitle: titleCompare,
editedPermissionsSkipped: toSkip,
});
- },
+ }
- handleHidePermissionsEditor: function() {
+ handleHidePermissionsEditor() {
this.setState({showPermissionEditorFor: undefined});
- },
+ }
- handleShowAddMembers: function() {
+ handleShowAddMembers() {
this.props.onInitFind();
this.setState({showMemberPanel: true});
- },
+ }
- handleHideAddMembers: function() {
+ handleHideAddMembers() {
this.setState({showMemberPanel: false});
- },
+ }
- handleMemberUpdateRequest: function(members, added, removed) {
+ handleMemberUpdateRequest(members, added, removed) {
this.props.onMemberUpdateRequest(this.props.topic, added, removed);
this.setState({showMemberPanel: false});
- },
+ }
- handleLeave: function() {
+ handleLeave() {
this.props.onLeaveTopic(this.props.topic);
- },
+ }
- handleMemberSelected: function(uid) {
+ handleMemberSelected(uid) {
this.setState({selectedContact: uid});
- },
- handleMoreInfo: function(open) {
+ }
+
+ handleMoreInfo(open) {
this.setState({moreInfoExpanded: open});
- },
- handleTagsUpdated: function(tags) {
+ }
+
+ handleTagsUpdated(tags) {
if (!arrayEqual(this.state.tags.slice(0), tags.slice(0))) {
this.props.onTopicTagsUpdate(this.props.topic, tags);
}
- },
+ }
- handleContextMenu: function(params) {
+ handleContextMenu(params) {
var instance = this;
var topic = Tinode.getTopic(this.props.topic);
if (!topic) {
@@ -2931,9 +3171,9 @@
x: params.x,
y: params.y,
user: params.topicName}, menuItems);
- },
+ }
- render: function() {
+ render() {
return (
@@ -3113,7 +3353,7 @@
);
}
- });
+ };
/* END InfoView */
/* BEGIN Conversation panel */
@@ -3125,8 +3365,15 @@
READ: 2
};
- var Message = React.createClass({
- handlePreviewImage: function(e) {
+ class Message extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handlePreviewImage = this.handlePreviewImage.bind(this);
+ this.handleContextClick = this.handleContextClick.bind(this);
+ }
+
+ handlePreviewImage(e) {
e.preventDefault();
this.props.onImagePreview({
url: e.target.src,
@@ -3136,15 +3383,15 @@
size: e.target.dataset.size,
type: e.target.dataset.mime
});
- },
+ }
- handleContextClick: function(e) {
+ handleContextClick(e) {
e.preventDefault();
e.stopPropagation();
this.props.showContextMenu({ seq: this.props.seq, y: e.pageY, x: e.pageX });
- },
+ }
- render: function() {
+ render() {
var elementKey = 0;
var formatter = function(style, data, values) {
@@ -3178,7 +3425,7 @@
var attachments = [];
if (this.props.mimeType === Drafty.getContentType()) {
Drafty.attachments(content, function(att, i) {
- attachments.push(
);
@@ -3200,7 +3447,7 @@
{content}
{attachments}
-
@@ -3214,15 +3461,15 @@
);
}
- });
+ };
- var MessageUploader = React.createClass({
- render: function() {
+ class MessageUploader extends React.PureComponent {
+ render() {
var sideClass = this.props.sequence + " right";
var bubbleClass = "bubble tip";
var attachments = [
-
@@ -3242,12 +3489,12 @@
);
}
- });
+ };
/* Received/read indicator */
- Message.ReceivedMarker = React.createClass({
- render: function() {
+ class ReceivedMarker extends React.PureComponent {
+ render() {
var timestamp = (this.props.received === Received.SENDING) ?
"sending ..." :
shortDateFormat(this.props.timestamp);
@@ -3269,17 +3516,22 @@
);
}
- });
+ };
- Message.Attachment = React.createClass({
- getInitialState: function() {
- return {
+ class Attachment extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
downloader: null,
progress: 0
};
- },
- downloadFile: function(url, filename, mimetype) {
+ this.downloadFile = this.downloadFile.bind(this);
+ this.handleCancel = this.handleCancel.bind(this);
+ }
+
+ downloadFile(url, filename, mimetype) {
var downloader = Tinode.getLargeFileHelper();
this.setState({downloader: downloader});
var instance = this;
@@ -3293,17 +3545,17 @@
}
instance.setState({downloader: null, progress: 0});
});
- },
+ }
- handleCancel: function() {
+ handleCancel() {
if (this.props.uploader) {
this.props.onCancelUpload();
} else if (this.state.downloader) {
this.state.downloader.cancel();
}
- },
+ }
- render: function() {
+ render() {
var filename = this.props.filename || "file_attachment";
if (filename.length > 36) {
filename = filename.substr(0, 16) + "..." + filename.substr(-16);
@@ -3346,11 +3598,13 @@
);
}
- });
+ };
- var MessagesView = React.createClass({
- getInitialState: function() {
- return {
+ class MessagesView extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
messages: [],
uploadFile: null,
uploadProgress: 0,
@@ -3359,40 +3613,60 @@
title: '',
avatar: null,
scrollPosition: 0,
- readOnly: this.props.acs ? !this.props.acs.isWriter() : true,
- writeOnly: this.props.acs ? !this.props.acs.isReader() : true,
+ readOnly: props.acs ? !props.acs.isWriter() : true,
+ writeOnly: props.acs ? !props.acs.isReader() : true,
typingIndicator: false,
imagePreview: null
};
- },
+
+ this.propsChange = this.propsChange.bind(this);
+ this.leave = this.leave.bind(this);
+ this.handleScrollReference = this.handleScrollReference.bind(this);
+ this.fetchMoreMessages = this.fetchMoreMessages.bind(this);
+ this.handleDescChange = this.handleDescChange.bind(this);
+ this.handleSubsUpdated = this.handleSubsUpdated.bind(this);
+ this.handleNewMessage = this.handleNewMessage.bind(this);
+ this.handleInfoReceipt = this.handleInfoReceipt.bind(this);
+ this.handleImagePreview = this.handleImagePreview.bind(this);
+ this.handleCloseImagePreview = this.handleCloseImagePreview.bind(this);
+ this.handleContextClick = this.handleContextClick.bind(this);
+ this.handleShowContextMenuMessage = this.handleShowContextMenuMessage.bind(this);
+ this.handleBackNavigation = this.handleBackNavigation.bind(this);
+ this.handleStartUpload = this.handleStartUpload.bind(this);
+ this.handleFinishUpload = this.handleFinishUpload.bind(this);
+ this.handleUploadProgress = this.handleUploadProgress.bind(this);
+ this.handleCancelUpload = this.handleCancelUpload.bind(this);
+ }
// Scroll last message into view on component update e.g. on message received.
- componentDidUpdate: function(prevProps, prevState) {
+ componentDidUpdate(prevProps, prevState) {
if (this.messagesScroller &&
(prevState.title != this.state.title ||
prevState.messages.length != this.state.messages.length ||
prevState.uploadFile != this.state.uploadFile)) {
this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight - this.state.scrollPosition;
}
- },
+ }
- componentWillMount: function() {
+ componentWillMount() {
this.propsChange(this.props);
if (this.messagesScroller) {
this.messagesScroller.addEventListener('scroll', this.fetchMoreMessages);
}
- },
+ }
- componentWillUnmount: function() {
+ componentWillUnmount() {
this.leave();
if (this.messagesScroller) {
this.messagesScroller.removeEventListener('scroll', this.fetchMoreMessages);
}
- },
- componentWillReceiveProps: function(nextProps) {
+ }
+
+ componentWillReceiveProps(nextProps) {
this.propsChange(nextProps);
- },
- propsChange: function(props) {
+ }
+
+ propsChange(props) {
if (!props || !props.topic) {
this.setState({messages: [], onlineSubs: [], topic: null});
this.leave();
@@ -3474,9 +3748,9 @@
});
});
}
- },
+ }
- leave: function() {
+ leave() {
if (this.state.topic) {
var oldTopic = Tinode.getTopic(this.state.topic);
if (oldTopic) {
@@ -3493,17 +3767,17 @@
oldTopic.onPres = undefined;
}
}
- },
+ }
- handleScrollReference: function(node) {
+ handleScrollReference(node) {
if (node) {
node.addEventListener('scroll', this.fetchMoreMessages);
this.messagesScroller = node;
}
- },
+ }
// Get older messages
- fetchMoreMessages: function(event) {
+ fetchMoreMessages(event) {
var instance = this;
if (event.target.scrollTop <= 0) {
var started = false;
@@ -3524,9 +3798,9 @@
return newState;
});
}
- },
+ }
- handleDescChange: function(desc) {
+ handleDescChange(desc) {
if (desc.public) {
this.setState({
title: desc.public.fn,
@@ -3539,9 +3813,9 @@
writeOnly: !desc.acs.isReader()
});
}
- },
+ }
- handleSubsUpdated: function() {
+ handleSubsUpdated() {
if (this.state.topic) {
var subs = [];
var myId = this.props.myUserId;
@@ -3553,9 +3827,9 @@
});
this.setState({onlineSubs: subs});
}
- },
+ }
- handleNewMessage: function(msg) {
+ handleNewMessage(msg) {
// Regenerate messages list
var topic = Tinode.getTopic(this.state.topic);
var newState = {messages: []};
@@ -3580,9 +3854,9 @@
}
this.setState(newState);
- },
+ }
- handleInfoReceipt: function(info) {
+ handleInfoReceipt(info) {
switch (info.what) {
case "kp": {
clearTimeout(this.keyPressTimer);
@@ -3603,23 +3877,23 @@
default:
console.log("Other change in topic: " + info.what);
}
- },
+ }
- handleImagePreview: function(content) {
+ handleImagePreview(content) {
this.setState({ imagePreview: content });
- },
+ }
- handleCloseImagePreview: function() {
+ handleCloseImagePreview() {
this.setState({ imagePreview: null });
- },
+ }
- handleContextClick: function(e) {
+ handleContextClick(e) {
e.preventDefault();
e.stopPropagation();
this.props.showContextMenu({ topicName: this.state.topic, y: e.pageY, x: e.pageX });
- },
+ }
- handleShowContextMenuMessage: function(params) {
+ handleShowContextMenuMessage(params) {
params.topicName = this.state.topic;
var menuItems = [ContextMenuItems["message_delete"]];
var topic = Tinode.getTopic(params.topicName);
@@ -3630,13 +3904,13 @@
}
}
this.props.showContextMenu(params, menuItems);
- },
+ }
- handleBackNavigation: function() {
+ handleBackNavigation() {
this.props.onHideMessagesView();
- },
+ }
- handleStartUpload: function(filename, size, uploader) {
+ handleStartUpload(filename, size, uploader) {
this.setState({
uploadFile: {
name: filename,
@@ -3646,21 +3920,24 @@
uploadProgress: 0,
scrollPosition: 0,
});
- },
- handleFinishUpload: function(success) {
+ }
+
+ handleFinishUpload(success) {
this.setState({uploadFile: null});
- },
- handleUploadProgress: function(progress) {
+ }
+
+ handleUploadProgress(progress) {
this.setState({uploadProgress: progress});
- },
- handleCancelUpload: function() {
+ }
+
+ handleCancelUpload() {
if (this.state.uploadFile && this.state.uploadFile.uploader) {
this.state.uploadFile.uploader.cancel();
}
this.setState({uploadFile: null});
- },
+ }
- render: function() {
+ render() {
var component = null;
if (this.state.topic) {
var messageNodes = [];
@@ -3803,7 +4080,7 @@
null
}
-
{this.state.imagePreview ?
-
: null}
);
} else {
component = (
-
);
}
return component;
}
- });
+ };
/* Send message form */
- MessagesView.Send = React.createClass({
- getInitialState:function() {
- return {
+ class SendMessage extends React.PureComponent {
+ constructor(props) {
+ super(props);
+
+ this.state = {
message: '',
// Make initial keypress time as if it happened 5001 milliseconds in the past.
keypressTimestamp: new Date().getTime() - KEYPRESS_DELAY - 1
};
- },
- componentDidMount: function() {
+ this.handlePasteEvent = this.handlePasteEvent.bind(this);
+ this.handleAttachImage = this.handleAttachImage.bind(this);
+ this.handleAttachFile = this.handleAttachFile.bind(this);
+ this.handleSend = this.handleSend.bind(this);
+ this.handleKeyPress = this.handleKeyPress.bind(this);
+ this.handleMessageTyping = this.handleMessageTyping.bind(this);
+ }
+
+ componentDidMount() {
this.pasteFile.addEventListener('paste', this.handlePasteEvent, false);
- },
+ }
- componentWillUnmount: function() {
+ componentWillUnmount() {
this.pasteFile.removeEventListener('paste', this.handlePasteEvent, false)
- },
+ }
- handlePasteEvent: function(e) {
+ handlePasteEvent(e) {
if (this.props.disabled) {
return;
}
@@ -3863,9 +4149,9 @@
// If file was pasted, don't paste base64 daata into input field.
e.preventDefault();
}
- },
+ }
- handleAttachImage: function(e) {
+ handleAttachImage(e) {
if (e.target.files && e.target.files.length > 0) {
var instance = this;
var file = e.target.files[0];
@@ -3899,9 +4185,9 @@
}
// Clear the value so the same file can be uploaded again.
e.target.value = '';
- },
+ }
- handleAttachFile: function(e) {
+ handleAttachFile(e) {
if (e.target.files && e.target.files.length > 0) {
var instance = this;
var file = e.target.files[0];
@@ -3941,17 +4227,18 @@
}
// Clear the value so the same file can be uploaded again.
e.target.value = '';
- },
+ }
- handleSend: function() {
+ handleSend() {
var message = this.state.message.trim();
if (message) {
this.props.sendMessage(this.state.message.trim());
this.setState({message: ''});
}
- },
+ }
+
/* Send on Enter key */
- handleKeyPress: function(e) {
+ handleKeyPress(e) {
// Remove this if you don't want Enter to trigger send
if (e.key === 'Enter') {
// Have Shift-Enter insert a line break instead
@@ -3962,8 +4249,9 @@
this.handleSend();
}
}
- },
- handleMessageTyping: function(e) {
+ }
+
+ handleMessageTyping(e) {
var newState = {message: e.target.value};
var now = new Date().getTime();
if (now - this.state.keypressTimestamp > KEYPRESS_DELAY) {
@@ -3974,8 +4262,9 @@
newState.keypressTimestamp = now;
}
this.setState(newState);
- },
- render: function() {
+ }
+
+ render() {
var prompt = this.props.disabled ? "Messaging disabled" : "New message";
var instance = this;
return (
@@ -4005,11 +4294,11 @@
);
}
- });
+ };
/* This is just a static page to display when no conversation is selected. */
- MessagesView.Dummy = React.createClass({
- render: function() {
+ class LogoView extends React.PureComponent {
+ render() {
var version = Tinode.getVersion() + " build " + document.lastModified;
return (
@@ -4024,21 +4313,23 @@
);
}
- });
+ };
- MessagesView.ImagePreview = React.createClass({
- getInitialState: function() {
- return {};
- },
+ class ImagePreview extends React.PureComponent {
+ constructor(props) {
+ super(props);
- componentDidMount: function() {
+ this.state = {};
+ }
+
+ componentDidMount() {
this.setState({
width: this.container.clientWidth,
height: this.container.clientHeight
});
- },
+ }
- render: function() {
+ render() {
if (!this.props.content) {
return null;
}
@@ -4077,13 +4368,15 @@
);
}
- });
+ };
/* END Conversation panel */
/* The top-level class to hold all fuinctionality together */
- var AppView = React.createClass({
- getInitialState: function() {
- return {
+ class AppView extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
connected: false,
transport: null,
serverAddress: detectServerAddress(),
@@ -4116,8 +4409,54 @@
foundContacts: [],
credMethod: ''
};
- },
- componentDidMount: function() {
+ this.handleResize = this.handleResize.bind(this);
+ this.handleHashRoute = this.handleHashRoute.bind(this);
+ this.handleOnline = this.handleOnline.bind(this);
+ this.handleAppVisibility = this.handleAppVisibility.bind(this);
+ this.handleReadTimer = this.handleReadTimer.bind(this);
+ this.handleVisibilityEvent = this.handleVisibilityEvent.bind(this);
+ this.handleError = this.handleError.bind(this);
+ this.handleLoginRequest = this.handleLoginRequest.bind(this);
+ this.handleConnected = this.handleConnected.bind(this);
+ this.doLogin = this.doLogin.bind(this);
+ this.handleCredentialsRequest = this.handleCredentialsRequest.bind(this);
+ this.handleLoginSuccessful = this.handleLoginSuccessful.bind(this);
+ this.handleDisconnect = this.handleDisconnect.bind(this);
+ this.tnMeMetaDesc = this.tnMeMetaDesc.bind(this);
+ this.tnData = this.tnData.bind(this);
+ this.tnInitFind = this.tnInitFind.bind(this);
+ this.tnFndSubsUpdated = this.tnFndSubsUpdated.bind(this);
+ this.handleSearchContacts = this.handleSearchContacts.bind(this);
+ this.handleTopicSelected = this.handleTopicSelected.bind(this);
+ this.handleTopicSelectedOnline = this.handleTopicSelectedOnline.bind(this);
+ this.handleTopicSelectedAcs = this.handleTopicSelectedAcs.bind(this);
+ this.handleHideMessagesView = this.handleHideMessagesView.bind(this);
+ this.handleSendMessage = this.handleSendMessage.bind(this);
+ this.handleNewAccount = this.handleNewAccount.bind(this);
+ this.handleNewAccountRequest = this.handleNewAccountRequest.bind(this);
+ this.handleUpdateAccountRequest = this.handleUpdateAccountRequest.bind(this);
+ this.handleUpdateAccountTagsRequest = this.handleUpdateAccountTagsRequest.bind(this);
+ this.handleSettings = this.handleSettings.bind(this);
+ this.handleConnectionSettings = this.handleConnectionSettings.bind(this);
+ this.handleSidepanelCancel = this.handleSidepanelCancel.bind(this);
+ this.handleNewTopic = this.handleNewTopic.bind(this);
+ this.handleNewTopicRequest = this.handleNewTopicRequest.bind(this);
+ this.handleTopicUpdateRequest = this.handleTopicUpdateRequest.bind(this);
+ this.handleChangePermissions = this.handleChangePermissions.bind(this);
+ this.handleTagsUpdated = this.handleTagsUpdated.bind(this);
+ this.handleLogout = this.handleLogout.bind(this);
+ this.handleLeaveUnsubRequest = this.handleLeaveUnsubRequest.bind(this);
+ this.handleDialogCancel = this.handleDialogCancel.bind(this);
+ this.handleShowContextMenu = this.handleShowContextMenu.bind(this);
+ this.defaultTopicContextMenu = this.defaultTopicContextMenu.bind(this);
+ this.handleHideContextMenu = this.handleHideContextMenu.bind(this);
+ this.handleShowInfoView = this.handleShowInfoView.bind(this);
+ this.handleHideInfoView = this.handleHideInfoView.bind(this);
+ this.handleMemberUpdateRequest = this.handleMemberUpdateRequest.bind(this);
+ this.handleValidateCredentialsRequest = this.handleValidateCredentialsRequest.bind(this);
+ }
+
+ componentDidMount() {
var instance = this;
window.addEventListener('resize', this.handleResize);
window.addEventListener('online', function(e) { instance.handleOnline(true); });
@@ -4154,21 +4493,24 @@
}
this.readTimer = null;
this.readTimerCallback = null;
- },
+ }
+
// Setup transport (usually websocket) and server address. This will terminate connection with the server.
- tnSetup: function(serverAddress, transport) {
+ // FIXME: make static
+ tnSetup(serverAddress, transport) {
Tinode.setup(APP_NAME, serverAddress, API_KEY, transport);
- },
- handleResize: function() {
+ }
+
+ handleResize() {
var mobile = document.documentElement.clientWidth <= MEDIA_BREAKPOINT;
this.setState({viewportWidth: document.documentElement.clientWidth});
if (this.state.displayMobile != mobile) {
this.setState({displayMobile: mobile});
}
- },
+ }
// Handle for hashchange event: display appropriate panels.
- handleHashRoute: function() {
+ handleHashRoute() {
var hash = parseUrlHash(window.location.hash);
// Left-side panel selector.
if (hash.path && hash.path.length > 0) {
@@ -4201,9 +4543,9 @@
showInfoPanel: hash.params.info,
newTopicTabSelected: hash.params.tab
});
- },
+ }
- handleOnline: function(online) {
+ handleOnline(online) {
var newState = {liveConnection: online};
if (online) {
this.handleError("", null);
@@ -4211,11 +4553,11 @@
this.handleError("No connection", "warn");
}
this.setState({liveConnection: online});
- },
+ }
// Handling read notifications here to be able to pause
// them then the window/tab is not visible.
- handleAppVisibility: function(visible, callback) {
+ handleAppVisibility(visible, callback) {
clearTimeout(this.readTimer);
this.readTimerCallback = callback;
if (visible && callback) {
@@ -4223,20 +4565,22 @@
} else {
this.readTimer = null;
}
- },
- handleReadTimer: function(callback) {
- this.handleAppVisibility(!document.hidden, callback);
- },
- handleVisibilityEvent: function() {
- this.handleAppVisibility(!document.hidden, this.readTimerCallback);
- },
+ }
- handleError: function(err, level) {
+ handleReadTimer(callback) {
+ this.handleAppVisibility(!document.hidden, callback);
+ }
+
+ handleVisibilityEvent() {
+ this.handleAppVisibility(!document.hidden, this.readTimerCallback);
+ }
+
+ handleError(err, level) {
this.setState({errorText: err, errorLevel: level});
- },
+ }
// User clicked Login button in the side panel.
- handleLoginRequest: function(login, password) {
+ handleLoginRequest(login, password) {
var instance = this;
this.setState({loginDisabled: true, login: login, password: password});
this.handleError("", null);
@@ -4250,17 +4594,18 @@
instance.handleError(err.message, "err");
});
}
- },
+ }
+
// Connection succeeded.
- handleConnected: function() {
+ handleConnected() {
var params = Tinode.getServerInfo();
this.setState({
serverVersion: params.ver + " " + (params.build ? params.build : "none") + "; "
});
this.doLogin(this.state.login, this.state.password);
- },
+ }
- doLogin: function(login, password, cred) {
+ doLogin(login, password, cred) {
if (Tinode.isAuthenticated()) {
// Already logged in. Go to default screen.
window.location.hash = "";
@@ -4299,16 +4644,16 @@
window.location.hash = "";
});
}
- },
+ }
- handleCredentialsRequest: function(params) {
+ handleCredentialsRequest(params) {
var parsed = parseUrlHash(window.location.hash);
parsed.path[0] = 'cred';
parsed.params['method'] = params.cred[0];
window.location.hash = composeUrlHash(parsed.path, parsed.params);
- },
+ }
- handleLoginSuccessful: function(instance) {
+ handleLoginSuccessful(instance) {
instance.handleError("", null);
// Refresh authentication token.
@@ -4334,9 +4679,9 @@
window.location.hash = "";
});
window.location.hash = setUrlSidePanel(window.location.hash, 'contacts');
- },
+ }
- handleDisconnect: function(err) {
+ handleDisconnect(err) {
this.setState({
connected: false,
topicSelectedOnline: false,
@@ -4347,26 +4692,28 @@
contextMenuVisible: false,
serverVersion: 'no connection'
});
- },
- tnMeMetaDesc: function(desc) {
+ }
+
+ tnMeMetaDesc(desc) {
if (desc && desc.public) {
this.setState({
sidePanelTitle: desc.public.fn,
sidePanelAvatar: makeImageUrl(desc.public.photo)
});
}
- },
+ }
+
// Sending "received" notifications
- tnData: function(data) {
+ tnData(data) {
clearTimeout(this.receivedTimer);
this.receivedTimer = setTimeout(function() {
this.receivedTimer = undefined;
Tinode.getTopic(data.topic).noteRecv(data.seq);
}, RECEIVED_DELAY);
- },
+ }
/* Fnd topic: find contacts by tokens */
- tnInitFind: function() {
+ tnInitFind() {
var fnd = Tinode.getFndTopic();
if (fnd.isSubscribed()) {
this.setState({contactsSearchQuery: ''});
@@ -4378,20 +4725,20 @@
instance.handleError(err.message, "err");
});
}
- },
+ }
- tnFndSubsUpdated: function() {
+ tnFndSubsUpdated() {
var contacts = [];
// Don't attempt to create P2P topics which already exist. Server will reject the duplicates.
Tinode.getFndTopic().contacts(function(s) {
contacts.push(s);
});
this.setState({foundContacts: contacts});
- },
+ }
/** Called when the user enters a contact into the contact search field in the NewAccount panel
@param query {Array} is an array of contacts to search for
*/
- handleSearchContacts: function(query) {
+ handleSearchContacts(query) {
var fnd = Tinode.getFndTopic();
var instance = this;
fnd.setMeta({desc: {public: query}}).then(function(ctrl) {
@@ -4399,10 +4746,10 @@
}).catch(function(err){
instance.handleError(err.message, "err");
});
- },
+ }
// User clicked on a contact in the side panel or deleted a contact.
- handleTopicSelected: function(topicName, unused_index, online, acs) {
+ handleTopicSelected(topicName, unused_index, online, acs) {
if (topicName) {
// Contact selected
if (this.state.topicSelected != topicName) {
@@ -4430,27 +4777,28 @@
infoPanelShowMembers: false
});
}
- },
+ }
- handleTopicSelectedOnline: function(online) {
+ handleTopicSelectedOnline(online) {
if (typeof online == 'boolean') {
this.setState({topicSelectedOnline: online});
}
- },
+ }
// Permissions of a currently selected topic have been updated by server.
- handleTopicSelectedAcs: function(acs) {
+ handleTopicSelectedAcs(acs) {
this.setState({topicSelectedAcs: acs});
- },
+ }
// In mobile view user requested to show sidepanel
- handleHideMessagesView: function() {
+ handleHideMessagesView() {
this.setState({
mobilePanel: 'sidepanel'
});
window.location.hash = setUrlTopic(window.location.hash, null);
- },
+ }
+
// User clicked on a message send button.
- handleSendMessage: function(msg) {
+ handleSendMessage(msg) {
var topic = Tinode.getTopic(this.state.topicSelected);
var instance = this;
// Check if the message is formatted.
@@ -4476,13 +4824,15 @@
instance.handleError(err.message, "err");
});
}
- },
+ }
+
// User chose a Sign Up menu item.
- handleNewAccount: function() {
+ handleNewAccount() {
window.location.hash = setUrlSidePanel(window.location.hash, 'register');
- },
+ }
+
// Actual registration of a new account.
- handleNewAccountRequest: function(login_, password_, public_, cred_, tags_) {
+ handleNewAccountRequest(login_, password_, public_, cred_, tags_) {
var instance = this;
Tinode.connect(this.state.serverAddress)
.then(function() {
@@ -4497,9 +4847,9 @@
}).catch(function(err) {
instance.handleError(err.message, "err");
});
- },
+ }
- handleUpdateAccountRequest: function(password, pub, priv) {
+ handleUpdateAccountRequest(password, pub, priv) {
var instance = this;
if (pub || priv) {
Tinode.getMeTopic().setMeta({desc: {public: pub, private: priv}}).catch(function(err) {
@@ -4511,29 +4861,31 @@
instance.handleError(err.message, "err");
});
}
- },
+ }
- handleUpdateAccountTagsRequest: function(tags) {
+ handleUpdateAccountTagsRequest(tags) {
var instance = this;
Tinode.getFndTopic().setMeta({tags: tags})
.catch(function(err) {
instance.handleError(err.message, "err");
});
- },
+ }
// User chose Settings menu item.
- handleSettings: function() {
+ handleSettings() {
window.location.hash = setUrlSidePanel(window.location.hash, this.state.myUserId ? 'edit' : 'settings');
- },
+ }
+
// User updated connection parameters - transport and server address
- handleConnectionSettings: function(serverAddress, transport) {
+ handleConnectionSettings(serverAddress, transport) {
transport = transport || this.state.transport;
serverAddress = serverAddress || this.state.serverAddress;
this.setState({serverAddress: serverAddress, transport: transport, sidePanelSelected: 'login'});
this.tnSetup(serverAddress, transport);
- },
+ }
+
// User clicked Cancel button in Setting or Sign Up panel.
- handleSidepanelCancel: function() {
+ handleSidepanelCancel() {
var parsed = parseUrlHash(window.location.hash);
parsed.path[0] = this.state.myUserId ? 'contacts' : '';
if (parsed.params) {
@@ -4543,13 +4895,15 @@
}
window.location.hash = composeUrlHash(parsed.path, parsed.params);
this.setState({errorText: '', errorLevel: null});
- },
+ }
+
// User clicked a (+) menu item.
- handleNewTopic: function() {
+ handleNewTopic() {
window.location.hash = setUrlSidePanel(window.location.hash, 'newtpk');
- },
+ }
+
// Create of a new topic. New P2P topic requires peer's name.
- handleNewTopicRequest: function(peerName, pub, priv, tags) {
+ handleNewTopicRequest(peerName, pub, priv, tags) {
var instance = this;
var topic = peerName ? Tinode.newTopicWith(peerName) : Tinode.newTopic();
var query = topic.startMetaQuery().withDesc().withSub().withData();
@@ -4564,9 +4918,9 @@
}).catch(function(err) {
instance.handleError(err.message, "err");
});
- },
+ }
- handleTopicUpdateRequest: function(topicName, pub, priv, permissions) {
+ handleTopicUpdateRequest(topicName, pub, priv, permissions) {
var instance = this;
var topic = Tinode.getTopic(topicName);
if (topic) {
@@ -4584,9 +4938,9 @@
instance.handleError(err.message, "err");
});
}
- },
+ }
- handleChangePermissions: function(topicName, mode, uid) {
+ handleChangePermissions(topicName, mode, uid) {
var instance = this;
var topic = Tinode.getTopic(topicName);
if (topic) {
@@ -4602,9 +4956,9 @@
instance.handleError(err.message, "err");
});
}
- },
+ }
- handleTagsUpdated: function(topicName, tags) {
+ handleTagsUpdated(topicName, tags) {
var topic = Tinode.getTopic(topicName);
if (topic) {
var instance = this;
@@ -4612,16 +4966,16 @@
instance.handleError(err.message, "err");
});
}
- },
+ }
- handleLogout: function() {
+ handleLogout() {
localStorage.removeItem("auth-token");
this.setState(this.getInitialState());
this.tnSetup(this.state.serverAddress, this.state.transport);
window.location.hash = "";
- },
+ }
- handleLeaveUnsubRequest: function(topicName) {
+ handleLeaveUnsubRequest(topicName) {
var topic = Tinode.getTopic(topicName);
if (!topic) {
return;
@@ -4636,13 +4990,13 @@
}).catch(function(err) {
instance.handleError(err.message, "err");
});
- },
+ }
- handleDialogCancel: function() {
+ handleDialogCancel() {
this.setState({dialogSelected: null});
- },
+ }
- handleShowContextMenu: function(params, menuItems) {
+ handleShowContextMenu(params, menuItems) {
this.setState({
contextMenuVisible: true,
contextMenuClickAt: {x: params.x, y: params.y},
@@ -4650,9 +5004,9 @@
contextMenuItems: menuItems || this.defaultTopicContextMenu(params.topicName),
contextMenuBounds: ReactDOM.findDOMNode(this).getBoundingClientRect()
});
- },
+ }
- defaultTopicContextMenu: function(topicName) {
+ defaultTopicContextMenu(topicName) {
var muted = false, blocked = false, subscribed = false, deleter = false;
var topic = Tinode.getTopic(topicName);
if (topic) {
@@ -4673,26 +5027,28 @@
subscribed ? (blocked ? ContextMenuItems["topic_unblock"] : ContextMenuItems["topic_block"]) : null,
ContextMenuItems["topic_delete"]
];
- },
+ }
- handleHideContextMenu: function() {
+ handleHideContextMenu() {
this.setState({
contextMenuVisible: false,
contextMenuClickAt: null,
contextMenuParams: null,
contextMenuBounds: null
});
- },
+ }
- handleShowInfoView: function() {
+ handleShowInfoView() {
window.location.hash = addUrlParam(window.location.hash, 'info', true);
this.setState({showInfoPanel: true, infoPanelShowMembers: false});
- },
- handleHideInfoView: function() {
+ }
+
+ handleHideInfoView() {
window.location.hash = removeUrlParam(window.location.hash, 'info');
this.setState({showInfoPanel: false, infoPanelShowMembers: false});
- },
- handleMemberUpdateRequest: function(topicName, added, removed) {
+ }
+
+ handleMemberUpdateRequest(topicName, added, removed) {
if (!topicName) {
return;
}
@@ -4719,13 +5075,13 @@
});
});
}
- },
+ }
- handleValidateCredentialsRequest: function(cred, code) {
+ handleValidateCredentialsRequest(cred, code) {
this.doLogin(null, null, {meth: cred, resp: code});
- },
+ }
- render: function() {
+ render() {
return (
);
}
- })
+ };
ReactDOM.render(
,