Immutable messages, search and selectedTab

This commit is contained in:
khlieng 2015-05-19 01:17:23 +02:00
parent 77c723344c
commit 11ea241b60
26 changed files with 332 additions and 260 deletions

View file

@ -13,7 +13,7 @@ var App = React.createClass({
Reflux.listenTo(routeActions.navigate, 'navigate')
],
navigate: function(path, replace) {
navigate(path, replace) {
if (!replace) {
this.transitionTo(path);
} else {
@ -21,7 +21,7 @@ var App = React.createClass({
}
},
render: function() {
render() {
return (
<div>
<TabList />

View file

@ -9,20 +9,22 @@ var MessageInput = require('./MessageInput.jsx');
var UserList = require('./UserList.jsx');
var selectedTabStore = require('../stores/selectedTab');
var tabActions = require('../actions/tab');
var PureMixin = require('../mixins/pure');
var Chat = React.createClass({
mixins: [
PureMixin,
Router.State,
Reflux.connect(selectedTabStore, 'selectedTab')
],
getInitialState: function() {
getInitialState() {
return {
selectedTab: selectedTabStore.getState()
};
},
componentWillMount: function() {
componentWillMount() {
if (!window.loaded) {
var p = this.getParams();
@ -34,7 +36,7 @@ var Chat = React.createClass({
}
},
render: function() {
render() {
var chatClass;
var tab = this.state.selectedTab;

View file

@ -14,7 +14,7 @@ var ChatTitle = React.createClass({
Reflux.listenTo(selectedTabStore, 'selectedTabChanged')
],
getInitialState: function() {
getInitialState() {
var tab = selectedTabStore.getState();
return {
@ -23,20 +23,20 @@ var ChatTitle = React.createClass({
};
},
channelsChanged: function() {
channelsChanged() {
var tab = this.state.selectedTab;
this.setState({ usercount: channelStore.getUsers(tab.server, tab.channel).length });
},
selectedTabChanged: function(tab) {
selectedTabChanged(tab) {
this.setState({
selectedTab: tab,
usercount: channelStore.getUsers(tab.server, tab.channel).length
});
},
handleLeaveClick: function() {
handleLeaveClick() {
var tab = this.state.selectedTab;
if (!tab.channel) {
@ -48,7 +48,7 @@ var ChatTitle = React.createClass({
}
},
render: function() {
render() {
var tab = this.state.selectedTab;
var leaveTitle;

View file

@ -5,13 +5,13 @@ var serverActions = require('../actions/server');
var channelActions = require('../actions/channel');
var Connect = React.createClass({
getInitialState: function() {
getInitialState() {
return {
showOptionals: false
};
},
handleSubmit: function(e) {
handleSubmit(e) {
e.preventDefault();
var address = e.target.address.value.trim();

View file

@ -5,17 +5,20 @@ var Infinite = require('react-infinite');
var Autolinker = require('autolinker');
var MessageHeader = require('./MessageHeader.jsx');
var MessageLine = require('./MessageLine.jsx');
var messageLineStore = require('../stores/messageLine');
var selectedTabStore = require('../stores/selectedTab');
var messageActions = require('../actions/message');
var PureMixin = require('../mixins/pure');
var MessageBox = React.createClass({
mixins: [
PureMixin,
Reflux.connect(messageLineStore, 'messages'),
Reflux.connect(selectedTabStore, 'selectedTab')
],
getInitialState: function() {
getInitialState() {
return {
messages: messageLineStore.getState(),
selectedTab: selectedTabStore.getState(),
@ -23,20 +26,20 @@ var MessageBox = React.createClass({
};
},
componentDidMount: function() {
componentDidMount() {
window.addEventListener('resize', this.handleResize);
},
componentWillUnmount: function() {
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
},
componentWillUpdate: function() {
componentWillUpdate() {
var el = this.refs.list.getDOMNode();
this.autoScroll = el.scrollTop + el.offsetHeight === el.scrollHeight;
},
componentDidUpdate: function() {
componentDidUpdate() {
this.updateWidth();
if (this.autoScroll) {
@ -45,12 +48,12 @@ var MessageBox = React.createClass({
}
},
handleResize: function() {
handleResize() {
this.updateWidth();
this.setState({ height: window.innerHeight - 100 });
},
updateWidth: function() {
updateWidth() {
var width = this.refs.list.getDOMNode().firstChild.offsetWidth;
if (this.width !== width) {
@ -59,7 +62,7 @@ var MessageBox = React.createClass({
}
},
render: function() {
render() {
var tab = this.state.selectedTab;
var dest = tab.channel || tab.server;
var lines = [];
@ -67,27 +70,17 @@ var MessageBox = React.createClass({
paddingLeft: this.props.indent + 'px'
};
for (var j = 0; j < this.state.messages.length; j++) {
var message = this.state.messages[j];
var messageClass = 'message';
var key = message.server + dest + j;
if (message.type) {
messageClass += ' message-' + message.type;
}
this.state.messages.forEach((message, j) => {
var key = message.server + dest + j;
lines.push(<MessageHeader key={key} message={message} />);
for (var i = 1; i < message.lines.length; i++) {
var line = Autolinker.link(message.lines[i], { keepOriginalText: true });
lines.push(
<p key={key + '-' + i} className={messageClass} style={innerStyle}>
<span dangerouslySetInnerHTML={{ __html: line }}></span>
</p>
<MessageLine key={key + '-' + i} type={message.type} line={message.lines[i]} />
);
}
}
});
if (lines.length !== 1) {
return (

View file

@ -7,14 +7,18 @@ var privateChatActions = require('../actions/privateChat');
var tabActions = require('../actions/tab');
var MessageHeader = React.createClass({
handleSenderClick: function() {
shouldComponentUpdate(nextProps) {
return nextProps.message.lines[0] !== this.props.message.lines[0];
},
handleSenderClick() {
var message = this.props.message;
privateChatActions.open(message.server, message.from);
tabActions.select(message.server, message.from);
},
render: function() {
render() {
var message = this.props.message;
var sender = null;
var messageClass = 'message';

View file

@ -6,33 +6,33 @@ var selectedTabStore = require('../stores/selectedTab');
var messageActions = require('../actions/message');
var inputHistoryActions = require('../actions/inputHistory');
var tabActions = require('../actions/tab');
var PureMixin = require('../mixins/pure');
var MessageInput = React.createClass({
mixins: [
Reflux.connect(selectedTabStore, 'selectedTab'),
PureMixin,
Reflux.connect(inputHistoryStore, 'history'),
Reflux.listenTo(tabActions.select, 'tabSelected')
],
getInitialState: function() {
getInitialState() {
return {
selectedTab: selectedTabStore.getState(),
history: inputHistoryStore.getState(),
value: ''
};
},
componentDidMount: function() {
componentDidMount() {
this.refs.input.getDOMNode().focus();
},
tabSelected: function() {
tabSelected() {
this.refs.input.getDOMNode().focus();
},
handleKey: function(e) {
handleKey(e) {
if (e.which === 13 && e.target.value) {
var tab = this.state.selectedTab;
var tab = selectedTabStore.getState();
if (e.target.value[0] === '/') {
messageActions.command(e.target.value, tab.channel, tab.server);
@ -55,11 +55,11 @@ var MessageInput = React.createClass({
}
},
handleChange: function(e) {
handleChange(e) {
this.setState({ value: e.target.value });
},
render: function() {
render() {
return (
<div className="message-input-wrap">
<input

View file

@ -0,0 +1,28 @@
var React = require('react');
var Autolinker = require('autolinker');
var PureMixin = require('../mixins/pure');
var MessageLine = React.createClass({
mixins: [PureMixin],
render() {
var line = Autolinker.link(this.props.line, { keepOriginalText: true });
var messageClass = 'message';
var style = {
paddingLeft: window.messageIndent + 'px'
};
if (this.props.type) {
messageClass += ' message-' + this.props.type;
}
return (
<p className={messageClass} style={style}>
<span dangerouslySetInnerHTML={{ __html: line }}></span>
</p>
);
}
});
module.exports = MessageLine;

View file

@ -6,27 +6,29 @@ var util = require('../util');
var searchStore = require('../stores/search');
var selectedTabStore = require('../stores/selectedTab');
var searchActions = require('../actions/search');
var PureMixin = require('../mixins/pure');
var Search = React.createClass({
mixins: [
Reflux.connect(searchStore),
PureMixin,
Reflux.connect(searchStore, 'search'),
Reflux.connect(selectedTabStore, 'selectedTab')
],
getInitialState: function() {
var state = _.extend({}, searchStore.getState());
state.selectedTab = selectedTabStore.getState();
return state;
getInitialState() {
return {
search: searchStore.getState(),
selectedTab: selectedTabStore.getState()
};
},
componentDidUpdate: function(prevProps, prevState) {
if (!prevState.show && this.state.show) {
componentDidUpdate(prevProps, prevState) {
if (!prevState.search.get('show') && this.state.search.get('show')) {
this.refs.input.getDOMNode().focus();
}
},
handleChange: function(e) {
handleChange(e) {
var tab = this.state.selectedTab;
if (tab.channel) {
@ -34,12 +36,12 @@ var Search = React.createClass({
}
},
render: function() {
render() {
var style = {
display: this.state.show ? 'block' : 'none'
display: this.state.search.get('show') ? 'block' : 'none'
};
var results = _.map(this.state.results, (result) => {
var results = this.state.search.get('results').map(result => {
return (
<p key={result.id}>{util.timestamp(new Date(result.time * 1000))} {result.from} {result.content}</p>
);

View file

@ -1,7 +1,7 @@
var React = require('react');
var Settings = React.createClass({
render: function() {
render() {
return (
<div>
<h1>Settings</h1>

View file

@ -15,7 +15,7 @@ var TabList = React.createClass({
Reflux.connect(privateChatStore, 'privateChats')
],
getInitialState: function() {
getInitialState() {
return {
servers: serverStore.getState(),
channels: channelStore.getState(),
@ -23,15 +23,15 @@ var TabList = React.createClass({
};
},
handleConnectClick: function() {
handleConnectClick() {
routeActions.navigate('connect');
},
handleSettingsClick: function() {
handleSettingsClick() {
routeActions.navigate('settings');
},
render: function() {
render() {
var tabs = _.map(this.state.channels, (server, address) => {
var serverTabs = _.map(server, (channel, name) => {
return (

View file

@ -3,27 +3,33 @@ var Reflux = require('reflux');
var selectedTabStore = require('../stores/selectedTab');
var tabActions = require('../actions/tab');
var PureMixin = require('../mixins/pure');
var TabListItem = React.createClass({
mixins: [Reflux.connect(selectedTabStore)],
mixins: [
PureMixin,
Reflux.connect(selectedTabStore, 'tab')
],
getInitialState: function() {
return selectedTabStore.getState();
getInitialState() {
return {
tab: selectedTabStore.getState()
};
},
handleClick: function() {
handleClick() {
tabActions.select(this.props.server, this.props.channel);
},
render: function() {
render() {
var classes = [];
if (!this.props.channel) {
classes.push('tab-server');
}
if (this.props.server === this.state.server &&
this.props.channel === this.state.channel) {
if (this.props.server === this.state.tab.server &&
this.props.channel === this.state.tab.channel) {
classes.push('selected');
}

View file

@ -13,7 +13,7 @@ var UserList = React.createClass({
Reflux.listenTo(selectedTabStore, 'selectedTabChanged')
],
getInitialState: function() {
getInitialState() {
var tab = selectedTabStore.getState();
return {
@ -23,32 +23,32 @@ var UserList = React.createClass({
};
},
componentDidMount: function() {
componentDidMount() {
window.addEventListener('resize', this.handleResize);
},
componentWillUnmount: function() {
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
},
channelsChanged: function() {
channelsChanged() {
var tab = this.state.selectedTab;
this.setState({ users: channelStore.getUsers(tab.server, tab.channel) });
},
selectedTabChanged: function(tab) {
selectedTabChanged(tab) {
this.setState({
selectedTab: tab,
users: channelStore.getUsers(tab.server, tab.channel)
});
},
handleResize: function() {
handleResize() {
this.setState({ height: window.innerHeight - 100 });
},
render: function() {
render() {
var tab = this.state.selectedTab;
var users = [];
var style = {};

View file

@ -5,14 +5,14 @@ var privateChatActions = require('../actions/privateChat');
var tabActions = require('../actions/tab');
var UserListItem = React.createClass({
handleClick: function() {
handleClick() {
var server = selectedTabStore.getServer();
privateChatActions.open(server, this.props.user.nick);
tabActions.select(server, this.props.user.nick);
},
render: function() {
render() {
return <p onClick={this.handleClick}>{this.props.user.renderName}</p>;
}
});