Immutable messages, search and selectedTab
This commit is contained in:
parent
77c723344c
commit
11ea241b60
@ -27,6 +27,8 @@
|
||||
"react-router": "0.13.3",
|
||||
"react": "0.13.3",
|
||||
"react-infinite": "0.3.4",
|
||||
"autolinker": "khlieng/Autolinker.js"
|
||||
"autolinker": "khlieng/Autolinker.js",
|
||||
"immutable": "~3.7.2",
|
||||
"react-pure-render": "~1.0.1"
|
||||
}
|
||||
}
|
||||
|
@ -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 />
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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 (
|
||||
|
@ -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';
|
||||
|
@ -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
|
||||
|
28
client/src/js/components/MessageLine.jsx
Normal file
28
client/src/js/components/MessageLine.jsx
Normal 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;
|
@ -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>
|
||||
);
|
||||
|
@ -1,7 +1,7 @@
|
||||
var React = require('react');
|
||||
|
||||
var Settings = React.createClass({
|
||||
render: function() {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Settings</h1>
|
||||
|
@ -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 (
|
||||
|
@ -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');
|
||||
}
|
||||
|
||||
|
@ -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 = {};
|
||||
|
@ -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>;
|
||||
}
|
||||
});
|
||||
|
17
client/src/js/mixins/pure.js
Normal file
17
client/src/js/mixins/pure.js
Normal file
@ -0,0 +1,17 @@
|
||||
var shallowEqual = require('react-pure-render/shallowEqual');
|
||||
|
||||
module.exports = {
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
if (this.context.router) {
|
||||
var changed = this.pureComponentLastPath !== this.context.router.getCurrentPath();
|
||||
this.pureComponentLastPath = this.context.router.getCurrentPath();
|
||||
|
||||
if (changed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return !shallowEqual(this.props, nextProps) ||
|
||||
!shallowEqual(this.state, nextState);
|
||||
}
|
||||
};
|
@ -79,42 +79,42 @@ function sortUsers(server, channel) {
|
||||
}
|
||||
|
||||
var channelStore = Reflux.createStore({
|
||||
init: function() {
|
||||
init() {
|
||||
this.listenToMany(actions);
|
||||
this.listenTo(serverActions.connect, 'addServer');
|
||||
this.listenTo(serverActions.disconnect, 'removeServer');
|
||||
this.listenTo(serverActions.load, 'loadServers');
|
||||
},
|
||||
|
||||
part: function(partChannels, server) {
|
||||
part(partChannels, server) {
|
||||
_.each(partChannels, function(channel) {
|
||||
delete channels[server][channel];
|
||||
});
|
||||
this.trigger(channels);
|
||||
},
|
||||
|
||||
addUser: function(user, server, channel) {
|
||||
addUser(user, server, channel) {
|
||||
initChannel(server, channel);
|
||||
channels[server][channel].users.push(createUser(user));
|
||||
sortUsers(server, channel);
|
||||
this.trigger(channels);
|
||||
},
|
||||
|
||||
removeUser: function(user, server, channel) {
|
||||
removeUser(user, server, channel) {
|
||||
if (channels[server][channel]) {
|
||||
_.remove(channels[server][channel].users, { nick: user });
|
||||
this.trigger(channels);
|
||||
}
|
||||
},
|
||||
|
||||
removeUserAll: function(user, server) {
|
||||
removeUserAll(user, server) {
|
||||
_.each(channels[server], function(channel) {
|
||||
_.remove(channel.users, { nick: user });
|
||||
});
|
||||
this.trigger(channels);
|
||||
},
|
||||
|
||||
renameUser: function(oldNick, newNick, server) {
|
||||
renameUser(oldNick, newNick, server) {
|
||||
_.each(channels[server], function(channel, channelName) {
|
||||
var user = _.find(channel.users, { nick: oldNick });
|
||||
if (user) {
|
||||
@ -126,7 +126,7 @@ var channelStore = Reflux.createStore({
|
||||
this.trigger(channels);
|
||||
},
|
||||
|
||||
setUsers: function(users, server, channel) {
|
||||
setUsers(users, server, channel) {
|
||||
initChannel(server, channel);
|
||||
var chan = channels[server][channel];
|
||||
|
||||
@ -140,12 +140,12 @@ var channelStore = Reflux.createStore({
|
||||
this.trigger(channels);
|
||||
},
|
||||
|
||||
setTopic: function(topic, server, channel) {
|
||||
setTopic(topic, server, channel) {
|
||||
channels[server][channel].topic = topic;
|
||||
this.trigger(channels);
|
||||
},
|
||||
|
||||
setMode: function(mode) {
|
||||
setMode(mode) {
|
||||
var user = _.find(channels[mode.server][mode.channel].users, { nick: mode.user });
|
||||
if (user) {
|
||||
_.each(mode.remove, function(mode) {
|
||||
@ -161,7 +161,7 @@ var channelStore = Reflux.createStore({
|
||||
}
|
||||
},
|
||||
|
||||
load: function(storedChannels) {
|
||||
load(storedChannels) {
|
||||
_.each(storedChannels, function(channel) {
|
||||
initChannel(channel.server, channel.name);
|
||||
var chan = channels[channel.server][channel.name];
|
||||
@ -179,19 +179,19 @@ var channelStore = Reflux.createStore({
|
||||
this.trigger(channels);
|
||||
},
|
||||
|
||||
addServer: function(server) {
|
||||
addServer(server) {
|
||||
if (!(server in channels)) {
|
||||
channels[server] = {};
|
||||
this.trigger(channels);
|
||||
}
|
||||
},
|
||||
|
||||
removeServer: function(server) {
|
||||
removeServer(server) {
|
||||
delete channels[server];
|
||||
this.trigger(channels);
|
||||
},
|
||||
|
||||
loadServers: function(storedServers) {
|
||||
loadServers(storedServers) {
|
||||
_.each(storedServers, function(server) {
|
||||
if (!(server.address in channels)) {
|
||||
channels[server.address] = {};
|
||||
@ -200,25 +200,25 @@ var channelStore = Reflux.createStore({
|
||||
this.trigger(channels);
|
||||
},
|
||||
|
||||
getChannels: function(server) {
|
||||
getChannels(server) {
|
||||
return channels[server];
|
||||
},
|
||||
|
||||
getUsers: function(server, channel) {
|
||||
getUsers(server, channel) {
|
||||
if (channels[server] && channels[server][channel]) {
|
||||
return channels[server][channel].users;
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
getTopic: function(server, channel) {
|
||||
getTopic(server, channel) {
|
||||
if (channels[server] && channels[server][channel]) {
|
||||
return channels[server][channel].topic || null;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
getState: function() {
|
||||
getState() {
|
||||
return channels;
|
||||
}
|
||||
});
|
||||
|
@ -14,11 +14,11 @@ if (stored) {
|
||||
}
|
||||
|
||||
var inputHistoryStore = Reflux.createStore({
|
||||
init: function() {
|
||||
init() {
|
||||
this.listenToMany(actions);
|
||||
},
|
||||
|
||||
add: function(line) {
|
||||
add(line) {
|
||||
if (line.trim() && line !== history[0]) {
|
||||
history.unshift(line);
|
||||
|
||||
@ -30,28 +30,28 @@ var inputHistoryStore = Reflux.createStore({
|
||||
}
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
reset() {
|
||||
if (index !== -1) {
|
||||
index = -1;
|
||||
this.trigger(history[index]);
|
||||
}
|
||||
},
|
||||
|
||||
increment: function() {
|
||||
increment() {
|
||||
if (index !== history.length - 1) {
|
||||
index++;
|
||||
this.trigger(history[index]);
|
||||
}
|
||||
},
|
||||
|
||||
decrement: function() {
|
||||
decrement() {
|
||||
if (index !== -1) {
|
||||
index--;
|
||||
this.trigger(history[index]);
|
||||
}
|
||||
},
|
||||
|
||||
getState: function() {
|
||||
getState() {
|
||||
if (index !== -1) {
|
||||
return history[index];
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
var Reflux = require('reflux');
|
||||
var Immutable = require('immutable');
|
||||
var _ = require('lodash');
|
||||
|
||||
var serverStore = require('./server');
|
||||
@ -7,7 +8,18 @@ var actions = require('../actions/message');
|
||||
var serverActions = require('../actions/server');
|
||||
var channelActions = require('../actions/channel');
|
||||
|
||||
var messages = {};
|
||||
var messages = Immutable.Map();
|
||||
var empty = Immutable.List();
|
||||
|
||||
var Message = Immutable.Record({
|
||||
server: null,
|
||||
from: null,
|
||||
to: null,
|
||||
message: '',
|
||||
time: null,
|
||||
type: null,
|
||||
lines: []
|
||||
});
|
||||
|
||||
function addMessage(message, dest) {
|
||||
message.time = new Date();
|
||||
@ -19,24 +31,17 @@ function addMessage(message, dest) {
|
||||
message.message = from + message.message.slice(7);
|
||||
}
|
||||
|
||||
if (!(message.server in messages)) {
|
||||
messages[message.server] = {};
|
||||
messages[message.server][dest] = [message];
|
||||
} else if (!(dest in messages[message.server])) {
|
||||
messages[message.server][dest] = [message];
|
||||
} else {
|
||||
messages[message.server][dest].push(message);
|
||||
}
|
||||
messages = messages.updateIn([message.server, dest], empty, list => list.push(new Message(message)));
|
||||
}
|
||||
|
||||
var messageStore = Reflux.createStore({
|
||||
init: function() {
|
||||
init() {
|
||||
this.listenToMany(actions);
|
||||
this.listenTo(serverActions.disconnect, 'disconnect');
|
||||
this.listenTo(channelActions.part, 'part');
|
||||
},
|
||||
|
||||
send: function(message, to, server) {
|
||||
send(message, to, server) {
|
||||
addMessage({
|
||||
server: server,
|
||||
from: serverStore.getNick(server),
|
||||
@ -47,7 +52,7 @@ var messageStore = Reflux.createStore({
|
||||
this.trigger(messages);
|
||||
},
|
||||
|
||||
add: function(message) {
|
||||
add(message) {
|
||||
var dest = message.to || message.from;
|
||||
if (message.from && message.from.indexOf('.') !== -1) {
|
||||
dest = message.server;
|
||||
@ -57,7 +62,7 @@ var messageStore = Reflux.createStore({
|
||||
this.trigger(messages);
|
||||
},
|
||||
|
||||
broadcast: function(message, server, user) {
|
||||
broadcast(message, server, user) {
|
||||
_.each(channelStore.getChannels(server), function(channel, channelName) {
|
||||
if (!user || (user && _.find(channel.users, { nick: user }))) {
|
||||
addMessage({
|
||||
@ -71,7 +76,7 @@ var messageStore = Reflux.createStore({
|
||||
this.trigger(messages);
|
||||
},
|
||||
|
||||
inform: function(message, server, channel) {
|
||||
inform(message, server, channel) {
|
||||
if (_.isArray(message)) {
|
||||
_.each(message, (msg) => {
|
||||
addMessage({
|
||||
@ -93,26 +98,23 @@ var messageStore = Reflux.createStore({
|
||||
this.trigger(messages);
|
||||
},
|
||||
|
||||
disconnect: function(server) {
|
||||
delete messages[server];
|
||||
disconnect(server) {
|
||||
messages = messages.delete(server);
|
||||
this.trigger(messages);
|
||||
},
|
||||
|
||||
part: function(channels, server) {
|
||||
part(channels, server) {
|
||||
_.each(channels, function(channel) {
|
||||
delete messages[server][channel];
|
||||
messages = messages.deleteIn([server, channel]);
|
||||
});
|
||||
this.trigger(messages);
|
||||
},
|
||||
|
||||
getMessages: function(server, dest) {
|
||||
if (messages[server] && messages[server][dest]) {
|
||||
return messages[server][dest];
|
||||
}
|
||||
return [];
|
||||
getMessages(server, dest) {
|
||||
return messages.getIn([server, dest]) || empty;
|
||||
},
|
||||
|
||||
getState: function() {
|
||||
getState() {
|
||||
return messages;
|
||||
}
|
||||
});
|
||||
|
@ -10,43 +10,53 @@ var width = window.innerWidth;
|
||||
window.charWidth = util.stringWidth(' ', '16px Droid Sans Mono');
|
||||
window.messageIndent = 6 * charWidth;
|
||||
|
||||
// Temporary hack incase this runs before the font has loaded
|
||||
setTimeout(() => window.charWidth = util.stringWidth(' ', '16px Droid Sans Mono'), 1000);
|
||||
|
||||
var tab = selectedTabStore.getState();
|
||||
var messages;
|
||||
var prev;
|
||||
|
||||
function wrap() {
|
||||
messages = messageStore.getMessages(tab.server, tab.channel || tab.server);
|
||||
util.wrapMessages(messages, width, charWidth, messageIndent);
|
||||
var next = messageStore.getMessages(tab.server, tab.channel || tab.server);
|
||||
if (next !== prev) {
|
||||
prev = next;
|
||||
messages = util.wrapMessages(next, width, charWidth, messageIndent);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
wrap();
|
||||
|
||||
var messageLineStore = Reflux.createStore({
|
||||
init: function() {
|
||||
init() {
|
||||
this.listenTo(messageActions.setWrapWidth, 'setWrapWidth');
|
||||
this.listenTo(messageStore, 'messagesChanged');
|
||||
this.listenTo(selectedTabStore, 'selectedTabChanged');
|
||||
},
|
||||
|
||||
setWrapWidth: function(w) {
|
||||
setWrapWidth(w) {
|
||||
width = w;
|
||||
|
||||
util.wrapMessages(messages, width, charWidth, messageIndent);
|
||||
messages = util.wrapMessages(messages, width, charWidth, messageIndent);
|
||||
this.trigger(messages);
|
||||
},
|
||||
|
||||
messagesChanged: function() {
|
||||
wrap();
|
||||
this.trigger(messages);
|
||||
messagesChanged() {
|
||||
if (wrap()) {
|
||||
this.trigger(messages);
|
||||
}
|
||||
},
|
||||
|
||||
selectedTabChanged: function(selectedTab) {
|
||||
selectedTabChanged(selectedTab) {
|
||||
tab = selectedTab;
|
||||
|
||||
wrap();
|
||||
this.trigger(messages);
|
||||
if (wrap()) {
|
||||
this.trigger(messages);
|
||||
}
|
||||
},
|
||||
|
||||
getState: function() {
|
||||
getState() {
|
||||
return messages;
|
||||
}
|
||||
});
|
||||
|
@ -21,35 +21,35 @@ function initChat(server, nick) {
|
||||
}
|
||||
|
||||
var privateChatStore = Reflux.createStore({
|
||||
init: function() {
|
||||
init() {
|
||||
this.listenToMany(actions);
|
||||
this.listenTo(messageActions.add, 'messageAdded');
|
||||
this.listenTo(serverActions.disconnect, 'disconnect');
|
||||
},
|
||||
|
||||
open: function(server, nick) {
|
||||
open(server, nick) {
|
||||
if (initChat(server, nick)) {
|
||||
this.trigger(privateChats);
|
||||
}
|
||||
},
|
||||
|
||||
close: function(server, nick) {
|
||||
close(server, nick) {
|
||||
delete privateChats[server][nick];
|
||||
this.trigger(privateChats);
|
||||
},
|
||||
|
||||
messageAdded: function(message) {
|
||||
messageAdded(message) {
|
||||
if (!message.to && message.from.indexOf('.') === -1) {
|
||||
this.open(message.server, message.from);
|
||||
}
|
||||
},
|
||||
|
||||
disconnect: function(server) {
|
||||
disconnect(server) {
|
||||
delete privateChats[server];
|
||||
this.trigger(privateChats);
|
||||
},
|
||||
|
||||
getState: function() {
|
||||
getState() {
|
||||
return privateChats;
|
||||
}
|
||||
});
|
||||
|
@ -1,28 +1,29 @@
|
||||
var Reflux = require('reflux');
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var actions = require('../actions/search');
|
||||
|
||||
var state = {
|
||||
var state = Immutable.Map({
|
||||
show: false,
|
||||
results: []
|
||||
};
|
||||
results: Immutable.List()
|
||||
});
|
||||
|
||||
var searchStore = Reflux.createStore({
|
||||
init: function() {
|
||||
init() {
|
||||
this.listenToMany(actions);
|
||||
},
|
||||
|
||||
searchDone: function(results) {
|
||||
state.results = results;
|
||||
searchDone(results) {
|
||||
state = state.set('results', Immutable.List(results));
|
||||
this.trigger(state);
|
||||
},
|
||||
|
||||
toggle: function() {
|
||||
state.show = !state.show;
|
||||
toggle() {
|
||||
state = state.update('show', show => !show);
|
||||
this.trigger(state);
|
||||
},
|
||||
|
||||
getState: function() {
|
||||
getState() {
|
||||
return state;
|
||||
}
|
||||
});
|
||||
|
@ -1,4 +1,5 @@
|
||||
var Reflux = require('reflux');
|
||||
var Immutable = require('immutable');
|
||||
var _ = require('lodash');
|
||||
|
||||
var serverStore = require('./server');
|
||||
@ -8,14 +9,20 @@ var serverActions = require('../actions/server');
|
||||
var routeActions = require('../actions/route');
|
||||
var privateChatActions = require('../actions/privateChat');
|
||||
|
||||
var selectedTab = {};
|
||||
var Tab = Immutable.Record({
|
||||
server: null,
|
||||
channel: null,
|
||||
name: null
|
||||
});
|
||||
|
||||
var selectedTab = new Tab();
|
||||
var history = [];
|
||||
|
||||
function selectPrevTab() {
|
||||
history.pop();
|
||||
|
||||
if (history.length > 0) {
|
||||
selectedTab = _.extend({}, history[history.length - 1]);
|
||||
selectedTab = history[history.length - 1];
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -23,14 +30,12 @@ function selectPrevTab() {
|
||||
}
|
||||
|
||||
function updateChannelName(name) {
|
||||
selectedTab.channel = name;
|
||||
selectedTab.name = name;
|
||||
history[history.length - 1].channel = name;
|
||||
history[history.length - 1].name = name;
|
||||
selectedTab = selectedTab.set('channel', name).set('name', name);
|
||||
history[history.length - 1] = selectedTab;
|
||||
}
|
||||
|
||||
var selectedTabStore = Reflux.createStore({
|
||||
init: function() {
|
||||
init() {
|
||||
this.listenToMany(actions);
|
||||
this.listenTo(channelActions.part, 'part');
|
||||
this.listenTo(privateChatActions.close, 'close');
|
||||
@ -41,58 +46,57 @@ var selectedTabStore = Reflux.createStore({
|
||||
this.listenTo(routeActions.navigate, 'navigate');
|
||||
},
|
||||
|
||||
select: function(server, channel = null) {
|
||||
selectedTab.server = server;
|
||||
selectedTab.channel = channel;
|
||||
select(server, channel = null) {
|
||||
selectedTab = new Tab({
|
||||
server,
|
||||
channel,
|
||||
name: channel || serverStore.getName(server)
|
||||
});
|
||||
|
||||
if (channel) {
|
||||
selectedTab.name = channel;
|
||||
} else {
|
||||
selectedTab.name = serverStore.getName(server);
|
||||
}
|
||||
|
||||
history.push(_.extend({}, selectedTab));
|
||||
history.push(selectedTab);
|
||||
|
||||
this.trigger(selectedTab);
|
||||
},
|
||||
|
||||
part: function(channels, server) {
|
||||
part(channels, server) {
|
||||
if (server === selectedTab.server &&
|
||||
channels.indexOf(selectedTab.channel) !== -1) {
|
||||
if (!selectPrevTab()) {
|
||||
selectedTab.channel = null;
|
||||
selectedTab.name = serverStore.getName(server);
|
||||
selectedTab = selectedTab
|
||||
.set('channel', null)
|
||||
.set('name', serverStore.getName(server));
|
||||
}
|
||||
|
||||
this.trigger(selectedTab);
|
||||
}
|
||||
},
|
||||
|
||||
close: function(server, nick) {
|
||||
close(server, nick) {
|
||||
if (server === selectedTab.server &&
|
||||
nick === selectedTab.channel) {
|
||||
if (!selectPrevTab()) {
|
||||
selectedTab.channel = null;
|
||||
selectedTab.name = serverStore.getName(server);
|
||||
selectedTab = selectedTab
|
||||
.set('channel', null)
|
||||
.set('name', serverStore.getName(server));
|
||||
}
|
||||
|
||||
this.trigger(selectedTab);
|
||||
}
|
||||
},
|
||||
|
||||
disconnect: function(server) {
|
||||
disconnect(server) {
|
||||
if (server === selectedTab.server) {
|
||||
_.remove(history, { server: server });
|
||||
|
||||
if (!selectPrevTab()) {
|
||||
selectedTab = {};
|
||||
selectedTab = new Tab();
|
||||
}
|
||||
|
||||
this.trigger(selectedTab);
|
||||
}
|
||||
},
|
||||
|
||||
userAdded: function(user, server, channel) {
|
||||
userAdded(user, server, channel) {
|
||||
if (selectedTab.channel &&
|
||||
server === selectedTab.server &&
|
||||
user === serverStore.getNick(server) &&
|
||||
@ -103,7 +107,7 @@ var selectedTabStore = Reflux.createStore({
|
||||
}
|
||||
},
|
||||
|
||||
loadChannels: function(channels) {
|
||||
loadChannels(channels) {
|
||||
_.each(channels, (channel) => {
|
||||
if (channel.server === selectedTab.server &&
|
||||
channel.name !== selectedTab.channel &&
|
||||
@ -118,39 +122,38 @@ var selectedTabStore = Reflux.createStore({
|
||||
});
|
||||
},
|
||||
|
||||
loadServers: function(servers) {
|
||||
loadServers(servers) {
|
||||
var server = _.find(servers, { address: selectedTab.server });
|
||||
|
||||
if (server && !selectedTab.channel) {
|
||||
selectedTab.name = server.name;
|
||||
history[history.length - 1].name = server.name;
|
||||
selectedTab = selectedTab.set('name', server.name);
|
||||
history[history.length - 1] = selectedTab;
|
||||
|
||||
this.trigger(selectedTab);
|
||||
}
|
||||
},
|
||||
|
||||
navigate: function(route) {
|
||||
navigate(route) {
|
||||
if (route.indexOf('.') === -1 && selectedTab.server) {
|
||||
selectedTab.server = null;
|
||||
selectedTab.channel = null;
|
||||
selectedTab = new Tab();
|
||||
this.trigger(selectedTab);
|
||||
}
|
||||
},
|
||||
|
||||
getServer: function() {
|
||||
getServer() {
|
||||
return selectedTab.server;
|
||||
},
|
||||
|
||||
getChannel: function() {
|
||||
getChannel() {
|
||||
return selectedTab.channel;
|
||||
},
|
||||
|
||||
getState: function() {
|
||||
getState() {
|
||||
return selectedTab;
|
||||
}
|
||||
});
|
||||
|
||||
selectedTabStore.listen(function(selectedTab) {
|
||||
selectedTabStore.listen(selectedTab => {
|
||||
var channel = selectedTab.channel;
|
||||
|
||||
if (selectedTab.server) {
|
||||
|
@ -7,11 +7,11 @@ var tabActions = require('../actions/tab');
|
||||
var servers = {};
|
||||
|
||||
var serverStore = Reflux.createStore({
|
||||
init: function() {
|
||||
init() {
|
||||
this.listenToMany(actions);
|
||||
},
|
||||
|
||||
connect: function(server, nick, opts) {
|
||||
connect(server, nick, opts) {
|
||||
var i = server.indexOf(':');
|
||||
if (i > 0) {
|
||||
server = server.slice(0, i);
|
||||
@ -27,38 +27,38 @@ var serverStore = Reflux.createStore({
|
||||
tabActions.select(server);
|
||||
},
|
||||
|
||||
disconnect: function(server) {
|
||||
disconnect(server) {
|
||||
delete servers[server];
|
||||
this.trigger(servers);
|
||||
},
|
||||
|
||||
setNick: function(nick, server) {
|
||||
setNick(nick, server) {
|
||||
servers[server].nick = nick;
|
||||
this.trigger(servers);
|
||||
},
|
||||
|
||||
load: function(storedServers) {
|
||||
load(storedServers) {
|
||||
_.each(storedServers, function(server) {
|
||||
servers[server.address] = server;
|
||||
});
|
||||
this.trigger(servers);
|
||||
},
|
||||
|
||||
getNick: function(server) {
|
||||
getNick(server) {
|
||||
if (servers[server]) {
|
||||
return servers[server].nick;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
getName: function(server) {
|
||||
getName(server) {
|
||||
if (servers[server]) {
|
||||
return servers[server].name;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
getState: function() {
|
||||
getState() {
|
||||
return servers;
|
||||
}
|
||||
});
|
||||
|
@ -17,67 +17,69 @@ exports.timestamp = function(date) {
|
||||
};
|
||||
|
||||
exports.wrapMessages = function(messages, width, charWidth, indent = 0) {
|
||||
for (var j = 0, llen = messages.length; j < llen; j++) {
|
||||
var message = messages[j];
|
||||
var lineWidth = (6 + (message.from ? message.from.length + 1 : 0)) * charWidth;
|
||||
return messages.withMutations(m => {
|
||||
for (var j = 0, llen = messages.size; j < llen; j++) {
|
||||
var message = messages.get(j);
|
||||
var lineWidth = (6 + (message.from ? message.from.length + 1 : 0)) * charWidth;
|
||||
|
||||
if (lineWidth + message.message.length * charWidth < width) {
|
||||
message.lines = [message.message];
|
||||
continue;
|
||||
}
|
||||
|
||||
var words = message.message.split(' ');
|
||||
var line = '';
|
||||
var wrapped = [];
|
||||
var wordCount = 0;
|
||||
var hasWrapped = false;
|
||||
|
||||
// Add empty line if first word after timestamp + sender wraps
|
||||
if (words.length > 0 && message.from && lineWidth + words[0].length * charWidth >= width) {
|
||||
wrapped.push(line);
|
||||
lineWidth = 0;
|
||||
}
|
||||
|
||||
for (var i = 0, wlen = words.length; i < wlen; i++) {
|
||||
var word = words[i];
|
||||
|
||||
if (hasWrapped) {
|
||||
hasWrapped = false;
|
||||
lineWidth += indent;
|
||||
if (lineWidth + message.message.length * charWidth < width) {
|
||||
m.setIn([j, 'lines'], [message.message]);
|
||||
continue;
|
||||
}
|
||||
|
||||
lineWidth += word.length * charWidth;
|
||||
wordCount++;
|
||||
var words = message.message.split(' ');
|
||||
var line = '';
|
||||
var wrapped = [];
|
||||
var wordCount = 0;
|
||||
var hasWrapped = false;
|
||||
|
||||
if (lineWidth >= width) {
|
||||
if (wordCount !== 1) {
|
||||
wrapped.push(line);
|
||||
// Add empty line if first word after timestamp + sender wraps
|
||||
if (words.length > 0 && message.from && lineWidth + words[0].length * charWidth >= width) {
|
||||
wrapped.push(line);
|
||||
lineWidth = 0;
|
||||
}
|
||||
|
||||
if (i !== wlen - 1) {
|
||||
line = word + ' ';
|
||||
lineWidth = (word.length + 1) * charWidth;
|
||||
wordCount = 1;
|
||||
} else {
|
||||
wrapped.push(word);
|
||||
}
|
||||
} else {
|
||||
wrapped.push(word);
|
||||
lineWidth = 0;
|
||||
wordCount = 0;
|
||||
for (var i = 0, wlen = words.length; i < wlen; i++) {
|
||||
var word = words[i];
|
||||
|
||||
if (hasWrapped) {
|
||||
hasWrapped = false;
|
||||
lineWidth += indent;
|
||||
}
|
||||
|
||||
hasWrapped = true;
|
||||
} else if (i !== wlen - 1) {
|
||||
line += word + ' ';
|
||||
lineWidth += charWidth;
|
||||
} else {
|
||||
line += word;
|
||||
wrapped.push(line);
|
||||
}
|
||||
}
|
||||
lineWidth += word.length * charWidth;
|
||||
wordCount++;
|
||||
|
||||
message.lines = wrapped;
|
||||
}
|
||||
if (lineWidth >= width) {
|
||||
if (wordCount !== 1) {
|
||||
wrapped.push(line);
|
||||
|
||||
if (i !== wlen - 1) {
|
||||
line = word + ' ';
|
||||
lineWidth = (word.length + 1) * charWidth;
|
||||
wordCount = 1;
|
||||
} else {
|
||||
wrapped.push(word);
|
||||
}
|
||||
} else {
|
||||
wrapped.push(word);
|
||||
lineWidth = 0;
|
||||
wordCount = 0;
|
||||
}
|
||||
|
||||
hasWrapped = true;
|
||||
} else if (i !== wlen - 1) {
|
||||
line += word + ' ';
|
||||
lineWidth += charWidth;
|
||||
} else {
|
||||
line += word;
|
||||
wrapped.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
m.setIn([j, 'lines'], wrapped);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var canvas = document.createElement('canvas');
|
||||
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user