/** @jsx React.DOM */ var Timestamp = React.createClass({ render: function() { return <span>[{moment.unix(this.props.children).format("HH:mm")}]</span>; } }); var Author = React.createClass({ render: function() { var name = this.props.children; var color = 'hsl(' + parseInt(md5(name).substr(2, 4), 16) + ', 68%, 35%)'; return <span style={{color: color}}>{name}</span>; } }); var Content = React.createClass({ render: function() { return <span>{this.linkify(this.props.children)}</span> }, linkify: function(text) { var split = text.split(URI.find_uri_expression); var result = []; for (var i = 0; i < split.length; ++i) { if (split[i] !== undefined) { if (i + 1 < split.length && split[i + 1] === undefined) { result.push(<a href={split[i]} target="_blank">{split[i]}</a>); } else { result.push(split[i]); } } } return result; } }); var Message = React.createClass({ shouldComponentUpdate: function() { return false; }, render: function() { return ( <div> <Timestamp>{this.props.message.ts}</Timestamp>{' '} <Author>{this.props.message.who}</Author>{': '} <Content>{this.props.message.msg}</Content> </div> ); } }); var MessageList = React.createClass({ getInitialState: function() { return { messages: [], isLoading: false }; }, componentDidMount: function(elem) { $.ajax(this.props.url) .done(function(messages) { this.setState({messages: messages}); }.bind(this)); window.addEventListener('scroll', function() { if (document.body.scrollTop < 300) { this.loadMore(); } }.bind(this)); }, loadMore: function() { if (this.state.isLoading) { return; } var uri = URI(this.props.url) .addSearch({before: this.state.messages[0].id}) .toString(); $.ajax(uri) .done(function(messages) { this.setState({ isLoading: false, messages: this.state.messages.concat(messages) }); }.bind(this)); this.setState({ isLoading: true }); }, componentDidUpdate: function(props, state, elem) { var count = elem.children.length; if (count !== this.lastCount) { if (elem.offsetHeight < window.innerHeight) { this.loadMore(); } if (!this.lastCount) { window.scrollTo(0, elem.offsetHeight); } else { window.scrollTo( document.body.scrollLeft, document.body.scrollTop + ( elem.children[count - this.lastCount].getBoundingClientRect().top - elem.getBoundingClientRect().top ) ); } } this.lastCount = elem.children.length; }, render: function() { this.state.messages.sort(function(a, b) { return a.ts - b.ts; }); return ( <div> {this.state.messages.map(function(message) { return <Message message={message} key={message.id} ref={message.id} />; })} </div> ); } }); React.renderComponent(<MessageList url="http://vjeux.com:8001/api/utterances/irc.freenode.net/reactjs" />, document.body);
<script src="http://fb.me/react-js-fiddle-integration.js"></script> <script src="http://code.jquery.com/jquery-1.10.2.min.js"></script> <script src="https://rawgithub.com/timrwood/moment/2.1.0/min/moment.min.js"></script> <!-- http://medialize.github.io/URI.js/ --> <script src="https://rawgithub.com/vjeux/6529759/raw/26cc7fc53f9f77deba72018cebadad0eda613326/URI.js"></script> <script src="https://rawgithub.com/kvz/phpjs/master/functions/xml/utf8_encode.js"></script> <script src="https://rawgithub.com/kvz/phpjs/master/functions/strings/md5.js"></script>