// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var UserStore = require('../stores/user_store.jsx');
var PostStore = require('../stores/post_store.jsx');
var SocketStore = require('../stores/socket_store.jsx');
var MsgTyping = require('./msg_typing.jsx');
var MentionList = require('./mention_list.jsx');
var CommandList = require('./command_list.jsx');
var ErrorStore = require('../stores/error_store.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var utils = require('../utils/utils.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
function getStateFromStores() {
var error = ErrorStore.getLastError();
if (error) {
return { message: error.message };
} else {
return { message: null };
}
}
module.exports = React.createClass({
caret: -1,
addedMention: false,
doProcessMentions: false,
mentions: [],
componentDidMount: function() {
PostStore.addAddMentionListener(this._onChange);
ErrorStore.addChangeListener(this._onError);
this.resize();
this.processMentions();
this.updateTextdiv();
},
componentWillUnmount: function() {
PostStore.removeAddMentionListener(this._onChange);
ErrorStore.removeChangeListener(this._onError);
},
_onChange: function(id, username) {
if (id !== this.props.id) return;
this.addMention(username);
},
_onError: function() {
var errorState = getStateFromStores();
if (this.state.timerInterrupt != null) {
window.clearInterval(this.state.timerInterrupt);
this.setState({ timerInterrupt: null });
}
if (errorState.message === "There appears to be a problem with your internet connection") {
this.setState({ connection: "bad-connection" });
var timerInterrupt = window.setInterval(this._onTimerInterrupt, 5000);
this.setState({ timerInterrupt: timerInterrupt });
}
else {
this.setState({ connection: "" });
}
},
_onTimerInterrupt: function() {
//Since these should only happen when you have no connection and slightly briefly after any
//performance hit should not matter
if (this.state.connection === "bad-connection") {
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_ERROR,
err: null
});
AsyncClient.updateLastViewedAt();
}
window.clearInterval(this.state.timerInterrupt);
this.setState({ timerInterrupt: null });
},
componentDidUpdate: function() {
if (this.caret >= 0) {
utils.setCaretPosition(this.refs.message.getDOMNode(), this.caret)
this.caret = -1;
}
if (this.doProcessMentions) {
this.processMentions();
this.doProcessMentions = false;
}
this.updateTextdiv();
this.resize();
},
componentWillReceiveProps: function(nextProps) {
if (!this.addedMention) {
this.checkForNewMention(nextProps.messageText);
}
var text = this.refs.message.getDOMNode().value;
if (nextProps.channelId != this.props.channelId || nextProps.messageText !== text) {
this.doProcessMentions = true;
}
this.addedMention = false;
this.refs.commands.getSuggestedCommands(nextProps.messageText);
this.resize();
},
getInitialState: function() {
return { mentionText: '-1', mentions: [], connection: "", timerInterrupt: null };
},
updateMentionTab: function(mentionText, excludeList) {
var self = this;
// using setTimeout so dispatch isn't called during an in progress dispatch
setTimeout(function() {
AppDispatcher.handleViewAction({
type: ActionTypes.RECIEVED_MENTION_DATA,
id: self.props.id,
mention_text: mentionText,
exclude_list: excludeList
});
}, 1);
},
updateTextdiv: function() {
var html = utils.insertHtmlEntities(this.refs.message.getDOMNode().value);
var re = new RegExp('(^$)(?![.\n])', 'gm');
html = html.replace(re, '
');
$(this.refs.textdiv.getDOMNode()).html(html);
},
handleChange: function() {
this.props.onUserInput(this.refs.message.getDOMNode().value);
this.resize();
},
handleKeyPress: function(e) {
var text = this.refs.message.getDOMNode().value;
if (!this.refs.commands.isEmpty() && text.indexOf("/") == 0 && e.which==13) {
this.refs.commands.addFirstCommand();
e.preventDefault();
return;
}
if ( !this.doProcessMentions) {
var caret = utils.getCaretPosition(this.refs.message.getDOMNode());
var preText = text.substring(0, caret);
var lastSpace = preText.lastIndexOf(' ');
var lastAt = preText.lastIndexOf('@');
if (caret > lastAt && lastSpace < lastAt) {
this.doProcessMentions = true;
}
}
this.props.onKeyPress(e);
},
handleKeyDown: function(e) {
if (utils.getSelectedText(this.refs.message.getDOMNode()) !== '') {
this.doProcessMentions = true;
}
if (e.keyCode === 8) {
this.handleBackspace(e);
}
},
handleBackspace: function(e) {
var text = this.refs.message.getDOMNode().value;
if (text.indexOf("/") == 0) {
this.refs.commands.getSuggestedCommands(text.substring(0, text.length-1));
}
if (this.doProcessMentions) return;
var caret = utils.getCaretPosition(this.refs.message.getDOMNode());
var preText = text.substring(0, caret);
var lastSpace = preText.lastIndexOf(' ');
var lastAt = preText.lastIndexOf('@');
if (caret > lastAt && (lastSpace > lastAt || lastSpace === -1)) {
this.doProcessMentions = true;
}
},
processMentions: function() {
/* First, find all the possible mentions and add
them all to a list of mentions */
var text = utils.insertHtmlEntities(this.refs.message.getDOMNode().value);
var profileMap = UserStore.getProfilesUsernameMap();
var re1 = /@([a-z0-9_]+)( |$|\n)/gi;
var matches = text.match(re1);
if (!matches) {
$(this.refs.textdiv.getDOMNode()).text(text);
this.updateMentionTab(null, []);
return;
}
var mentions = [];
for (var i = 0; i < matches.length; i++) {
var m = matches[i].substring(1,matches[i].length).trim();
if ((m in profileMap && mentions.indexOf(m) === -1) || Constants.SPECIAL_MENTIONS.indexOf(m) !== -1) {
mentions.push(m);
}
}
/* Figure out what the user is currently typing. If it's a mention then we don't
want to add it to the mention list yet, so we remove it if
there is only one occurence of that mention so far. */
var caret = utils.getCaretPosition(this.refs.message.getDOMNode());
var text = this.props.messageText;
var preText = text.substring(0, caret);
var atIndex = preText.lastIndexOf('@');
var spaceIndex = preText.lastIndexOf(' ');
var newLineIndex = preText.lastIndexOf('\n');
var typingMention = "";
if (atIndex > spaceIndex && atIndex > newLineIndex) {
typingMention = text.substring(atIndex+1, caret);
}
var re2 = new RegExp('@' + typingMention + '( |$|\n)', 'g');
if ((text.match(re2) || []).length === 1 && mentions.indexOf(typingMention) !== -1) {
mentions.splice(mentions.indexOf(typingMention), 1);
}
this.updateMentionTab(null, mentions);
},
checkForNewMention: function(text) {
var caret = utils.getCaretPosition(this.refs.message.getDOMNode());
var preText = text.substring(0, caret);
var atIndex = preText.lastIndexOf('@');
// The @ character not typed, so nothing to do.
if (atIndex === -1) {
this.updateMentionTab('-1', null);
return;
}
var lastCharSpace = preText.lastIndexOf(String.fromCharCode(160));
var lastSpace = preText.lastIndexOf(' ');
// If there is a space after the last @, nothing to do.
if (lastSpace > atIndex || lastCharSpace > atIndex) {
this.updateMentionTab('-1', null);
return;
}
// Get the name typed so far.
var name = preText.substring(atIndex+1, preText.length).toLowerCase();
this.updateMentionTab(name, null);
},
addMention: function(name) {
var caret = utils.getCaretPosition(this.refs.message.getDOMNode());
var text = this.props.messageText;
var preText = text.substring(0, caret);
var atIndex = preText.lastIndexOf('@');
// The @ character not typed, so nothing to do.
if (atIndex === -1) {
return;
}
var prefix = text.substring(0, atIndex);
var suffix = text.substring(caret, text.length);
this.caret = prefix.length + name.length + 2;
this.addedMention = true;
this.doProcessMentions = true;
this.props.onUserInput(prefix + "@" + name + " " + suffix);
},
addCommand: function(cmd) {
var elm = this.refs.message.getDOMNode();
elm.value = cmd;
this.handleChange();
},
scroll: function() {
var e = this.refs.message.getDOMNode();
var d = this.refs.textdiv.getDOMNode();
$(d).scrollTop($(e).scrollTop());
},
resize: function() {
var e = this.refs.message.getDOMNode();
var w = this.refs.wrapper.getDOMNode();
var d = this.refs.textdiv.getDOMNode();
var lht = parseInt($(e).css('lineHeight'),10);
var lines = e.scrollHeight / lht;
var mod = lines < 2.5 || this.props.messageText === "" ? 30 : 15;
if (e.scrollHeight - mod < 167) {
$(e).css({'height':'auto','overflow-y':'hidden'}).height(e.scrollHeight - mod);
$(d).css({'height':'auto','overflow-y':'hidden'}).height(e.scrollHeight - mod);
$(w).css({'height':'auto'}).height(e.scrollHeight+2);
} else {
$(e).css({'height':'auto','overflow-y':'scroll'}).height(167);
$(d).css({'height':'auto','overflow-y':'scroll'}).height(167);
$(w).css({'height':'auto'}).height(167);
}
$(d).scrollTop($(e).scrollTop());
},
handleFocus: function() {
var elm = this.refs.message.getDOMNode();
if (elm.title === elm.value) {
elm.value = "";
}
},
handleBlur: function() {
var elm = this.refs.message.getDOMNode();
if (elm.value === '') {
elm.value = elm.title;
}
},
handlePaste: function() {
this.doProcessMentions = true;
},
render: function() {
return (