From cf7a05f80f68b5b1c8bcc0089679dd497cec2506 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Sun, 14 Jun 2015 23:53:32 -0800 Subject: first commit --- web/react/components/textbox.jsx | 290 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 web/react/components/textbox.jsx (limited to 'web/react/components/textbox.jsx') diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx new file mode 100644 index 000000000..45798809f --- /dev/null +++ b/web/react/components/textbox.jsx @@ -0,0 +1,290 @@ +// 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 utils = require('../utils/utils.jsx'); +var Constants = require('../utils/constants.jsx'); +var ActionTypes = Constants.ActionTypes; + +module.exports = React.createClass({ + caret: -1, + addedMention: false, + doProcessMentions: false, + mentions: [], + componentDidMount: function() { + PostStore.addAddMentionListener(this._onChange); + + this.resize(); + this.processMentions(); + this.updateTextdiv(); + }, + componentWillUnmount: function() { + PostStore.removeAddMentionListener(this._onChange); + }, + _onChange: function(id, username) { + if (id !== this.props.id) return; + this.addMention(username); + }, + 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: [] }; + }, + 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); + for (var k in this.mentions) { + var m = this.mentions[k]; + var re = new RegExp('( |^)@' + m + '( |$|\n)', 'm'); + html = html.replace(re, '$1@'+m+'$2'); + } + $(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, highlight them in the HTML 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, []); + this.mentions = []; + 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) { + mentions.push(m); + } + } + + /* Figure out what the user is currently typing. If it's a mention then we don't + want to highlight it and 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 re3 = new RegExp('@' + typingMention + '( |$|\n)', 'g'); + + if ((text.match(re3) || []).length === 1 && mentions.indexOf(typingMention) !== -1) { + mentions.splice(mentions.indexOf(typingMention), 1); + } + + this.updateMentionTab(null, mentions); + this.mentions = 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.setState({ mentionText: '-1' }); + 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); + } + }, + 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 ( +
+ +
+