const React = require("react"); const moment = require("moment"); const ReactDOM = require("react-dom"); const Ps = require("perfect-scrollbar"); 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); $(window).on("load", 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(prevProps) { if (prevProps.messages.length < this.props.messages.length) { 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 (
); } }); 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: "" } }, 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; } }) }

); }, messageTime() { let self = this; let ts = new Date(self.props.message.createdAt); let t = ts.toLocaleTimeString(undefined,{hour:"2-digit", minute:"2-digit"}); let d = ts.toLocaleDateString(undefined,{month: "2-digit", day:"2-digit"}); let r = ""; return r.concat(d," ", t); }, render() { let deleteButton; let user = this.props.user; if (user && user.admin) { deleteButton = ; } return (
  • User Avatar
    {this.props.message.author.username} {this.messageTime()} {deleteButton}
    {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}
    ); } });