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

@ -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"
}
}

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>;
}
});

View 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);
}
};

View File

@ -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;
}
});

View File

@ -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];
}

View File

@ -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;
}
});

View File

@ -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;
}
});

View File

@ -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;
}
});

View File

@ -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;
}
});

View File

@ -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) {

View File

@ -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;
}
});

View File

@ -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