const React = require("react"); const ReactDOM = require("react-dom"); const ReactEmoji = require("react-emoji"); const ReactAutolink = require("react-autolink"); const MessageBrowser = React.createClass({ getInitialState() { return { browserState: "", messages: [], page: 0, limit: 250, search: "" } }, handleNextPage(e) { e.preventDefault(); const page = this.state.page; this.setState({ page: page + 1 }); this.loadMessages(); }, handlePreviousPage(e) { e.preventDefault(); const page = this.state.page; if (page < 1) return; this.setState({ page: page - 1 }); this.loadMessages(); }, pageHandlers() { let previous; if (this.state.page > 0) { previous = ( Prev ); } let next; if (this.state.messages.length === this.state.limit) { next = ( Next ); } return (
{previous} {this.state.page} {next}
); }, loadMessages() { const limit = this.state.limit; const page = this.state.page; let data = { limit: limit, page: page }; if (this.state.search.length) { data.query = this.state.search; } this.setState({ browserState: "Retrieving messages"}); $.ajax({ url: "/api/messages", data: data }) .done(data => { this.setState({ messages: data.messages, browserState: "" }); }) .fail(error => { console.error(error); this.setState({ browserState: `Unable to retrieve messages.` }); }) }, componentDidMount() { this.loadMessages(); }, updateLimit(e) { let newLimit = parseInt(e.target.value, 10); if (isNaN(newLimit) || newLimit > 250) newLimit = 250; this.setState({ limit: newLimit }); }, updateSearch(e) { this.setState({ search: e.target.value }); }, render() { let browserState; if (this.state.browserState.length) { browserState = (
{this.state.browserState}
); } const messages = this.state.messages.map(message => { return ( {(new Date(message.createdAt)).toString()} {message.author.username} {message.content} {message._id} ); }); return (

Page Control

{this.pageHandlers()}
{browserState}
{messages}
Date Author Message ID
); } }); const Chatroom = exports.Chatroom = React.createClass({ propTypes: { messages: React.PropTypes.array.isRequired, socket: React.PropTypes.object.isRequired, user: React.PropTypes.object.isRequired }, getInitialState() { return { autoScroll: true }; }, componentDidMount() { let self = this; this.scrollListener = _.debounce((event) => { self.temporarilyDisableAutoScroll(event); }, 300, { leading: false, trailing: true }); let node = ReactDOM.findDOMNode(this.refs.messageContainer); node.addEventListener('scroll', this.scrollListener); this.scrollToBottom(); }, componentWillUnmount() { node.removeEventListener('scroll', this.scrollListener); clearTimeout(this.disableScrollTimer); }, loadMoreMessages() { const earliestMessage = this.props.messages[0]; if (earliestMessage === undefined) return; this.props.socket.emit("message:refresh", { before: earliestMessage.createdAt }); }, sendMessage(message) { this.props.socket.emit("newMessage", {message: message}); }, clearAutoScrollTimeout() { if (this.disableScrollTimer) clearTimeout(this.disableScrollTimer); }, temporarilyDisableAutoScroll(event) { let self = this; let node = event.target; if (node) { if (node.scrollHeight - node.scrollTop === node.clientHeight) { this.setState({ autoScroll: true }); this.clearAutoScrollTimeout(); } if (node.scrollHeight - node.scrollTop - node.clientHeight < 50) return; } this.setState({ autoScroll: false }); this.clearAutoScrollTimeout(); this.disableScrollTimer = setTimeout(() => { self.setState({ autoScroll: true }) }, 10000); }, componentDidUpdate() { this.scrollToBottom(); }, scrollToBottom() { if (!this.state.autoScroll) return; let node = ReactDOM.findDOMNode(this.refs.messageContainer); node.scrollTop = node.scrollHeight; }, render() { const socket = this.props.socket; const messages = this.props.messages.map(message => { if (message) { return } }); return (
Gather Chat
); } }); const imgurRegex = /^(https?:\/\/i\.imgur\.com\/\S*\.(jpg|png))$/i; const ChatMessage = React.createClass({ propTypes: { user: React.PropTypes.object.isRequired, socket: React.PropTypes.object.isRequired, message: React.PropTypes.object.isRequired }, mixins: [ ReactAutolink, ReactEmoji ], getInitialState() { return { createdAt: "" } }, updateCreatedAt() { let self = this; if (this.props.message.createdAt) { self.setState({ createdAt: $.timeago(self.props.message.createdAt) }) } }, componentWillMount() { this.updateCreatedAt(); }, componentDidMount() { this.interval = setInterval(this.updateCreatedAt, 60000); }, componentWillUnmount: function () { clearInterval(this.interval); }, messageContent: function () { let self = this; let message = self.props.message.content if (message.match(imgurRegex)) { return (
); } return self.autolink(message, { target: "_blank", rel: "nofollow" }).map((elem) => { if (_.isString(elem)) { return self.emojify(elem); } else { return elem; } }); }, render() { let deleteButton; let user = this.props.user; if (user && user.admin) { deleteButton = ; } return (
  • User Avatar
    {this.props.message.author.username} {deleteButton} {this.state.createdAt}

    {this.messageContent()}

  • ); } }); const DeleteMessageButton = React.createClass({ propTypes: { socket: React.PropTypes.object.isRequired }, handleClick (e) { e.preventDefault(); this.props.socket.emit("message:delete", { id: this.props.messageId }); }, render() { return ( ); } }) const MessageBar = React.createClass({ propTypes: { socket: React.PropTypes.object.isRequired }, sendMessage(content) { this.props.socket.emit("message:new", { content: content }); }, getInitialState() { return { statusMessage: null }; }, checkInputLength() { const input = ReactDOM.findDOMNode(this.refs.content).value; const currentStatusMessage = this.state.statusMessage; if (input.length > 256) { return this.setState({ statusMessage: "Maximum of 256 characters will be saved" }) } if (currentStatusMessage !== null) { this.setState({ statusMessage: null }); } }, handleInputChange() { // Noop, later assigned as debounced method in componentWillMount }, handleSubmit(e) { e.preventDefault(); let content = ReactDOM.findDOMNode(this.refs.content).value.trim(); if (!content) return; ReactDOM.findDOMNode(this.refs.content).value = ''; this.sendMessage(content); return; }, componentWillMount() { this.handleInputChange = _.debounce(this.checkInputLength, { leading: false, trailing: true }); }, render() { let statusMessage; if (this.state.statusMessage !== null) { statusMessage =
    {this.state.statusMessage}
    ; } return (
    {statusMessage}
    ); } });