Use immer
This commit is contained in:
parent
7f755d2a83
commit
4f72e164d7
File diff suppressed because one or more lines are too long
@ -8,7 +8,12 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"@babel/preset-react",
|
"@babel/preset-react",
|
||||||
"@babel/preset-stage-0"
|
[
|
||||||
|
"@babel/preset-stage-0",
|
||||||
|
{
|
||||||
|
"decoratorsLegacy": true
|
||||||
|
}
|
||||||
|
]
|
||||||
],
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"development": {
|
"development": {
|
||||||
|
@ -8,12 +8,12 @@
|
|||||||
"ie 11"
|
"ie 11"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.0.0-beta.44",
|
"@babel/core": "^7.0.0-beta.46",
|
||||||
"@babel/plugin-transform-react-constant-elements": "^7.0.0-beta.44",
|
"@babel/plugin-transform-react-constant-elements": "^7.0.0-beta.46",
|
||||||
"@babel/plugin-transform-react-inline-elements": "^7.0.0-beta.44",
|
"@babel/plugin-transform-react-inline-elements": "^7.0.0-beta.46",
|
||||||
"@babel/preset-env": "^7.0.0-beta.44",
|
"@babel/preset-env": "^7.0.0-beta.46",
|
||||||
"@babel/preset-react": "^7.0.0-beta.44",
|
"@babel/preset-react": "^7.0.0-beta.46",
|
||||||
"@babel/preset-stage-0": "^7.0.0-beta.44",
|
"@babel/preset-stage-0": "^7.0.0-beta.46",
|
||||||
"autoprefixer": "^8.2.0",
|
"autoprefixer": "^8.2.0",
|
||||||
"babel-core": "^7.0.0-0",
|
"babel-core": "^7.0.0-0",
|
||||||
"babel-eslint": "^8.2.2",
|
"babel-eslint": "^8.2.2",
|
||||||
@ -32,18 +32,18 @@
|
|||||||
"eslint-plugin-prettier": "^2.6.0",
|
"eslint-plugin-prettier": "^2.6.0",
|
||||||
"eslint-plugin-react": "^7.7.0",
|
"eslint-plugin-react": "^7.7.0",
|
||||||
"express": "^4.14.1",
|
"express": "^4.14.1",
|
||||||
"express-http-proxy": "^1.0.1",
|
"express-http-proxy": "^1.2.0",
|
||||||
"gulp": "^3.9.1",
|
"gulp": "^3.9.1",
|
||||||
"gulp-util": "^3.0.8",
|
"gulp-util": "^3.0.8",
|
||||||
"jest": "^22.4.3",
|
"jest": "^22.4.3",
|
||||||
"mini-css-extract-plugin": "^0.4.0",
|
"mini-css-extract-plugin": "^0.4.0",
|
||||||
"postcss-loader": "^2.1.3",
|
"postcss-loader": "^2.1.3",
|
||||||
"prettier": "1.12.1",
|
"prettier": "1.12.1",
|
||||||
"style-loader": "^0.20.3",
|
"style-loader": "^0.21.0",
|
||||||
"through2": "^2.0.3",
|
"through2": "^2.0.3",
|
||||||
"webpack": "^4.1.1",
|
"webpack": "^4.1.1",
|
||||||
"webpack-dev-middleware": "^3.0.1",
|
"webpack-dev-middleware": "^3.0.1",
|
||||||
"webpack-hot-middleware": "^2.17.0"
|
"webpack-hot-middleware": "^2.22.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"autolinker": "^1.4.3",
|
"autolinker": "^1.4.3",
|
||||||
@ -51,12 +51,12 @@
|
|||||||
"base64-arraybuffer": "^0.1.5",
|
"base64-arraybuffer": "^0.1.5",
|
||||||
"fontfaceobserver": "^2.0.9",
|
"fontfaceobserver": "^2.0.9",
|
||||||
"history": "4.5.1",
|
"history": "4.5.1",
|
||||||
"immutable": "^3.8.1",
|
"immer": "^1.2.1",
|
||||||
"js-cookie": "^2.1.4",
|
"js-cookie": "^2.1.4",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.10",
|
||||||
"react": "^16.2.0",
|
"react": "^16.2.0",
|
||||||
"react-dom": "^16.2.0",
|
"react-dom": "^16.2.0",
|
||||||
"react-hot-loader": "^4.0.0",
|
"react-hot-loader": "^4.1.2",
|
||||||
"react-redux": "^5.0.2",
|
"react-redux": "^5.0.2",
|
||||||
"react-virtualized": "^9.3.0",
|
"react-virtualized": "^9.3.0",
|
||||||
"redux": "^4.0.0",
|
"redux": "^4.0.0",
|
||||||
@ -65,7 +65,7 @@
|
|||||||
"url-pattern": "^1.0.3"
|
"url-pattern": "^1.0.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prettier": "prettier --write {.*,*.js,src/css/*.css}",
|
"prettier": "prettier --write {.*,*.js,src/css/*.css,src/**/*.test.js}",
|
||||||
"prettier:all": "prettier --write {.*,*.js,src/**/*.js,src/css/*.css}",
|
"prettier:all": "prettier --write {.*,*.js,src/**/*.js,src/css/*.css}",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:verbose": "jest --verbose",
|
"test:verbose": "jest --verbose",
|
||||||
|
@ -79,13 +79,13 @@ export default createCommandMiddleware(COMMAND, {
|
|||||||
topic({ dispatch, getState, server, channel }, ...newTopic) {
|
topic({ dispatch, getState, server, channel }, ...newTopic) {
|
||||||
if (newTopic.length > 0) {
|
if (newTopic.length > 0) {
|
||||||
dispatch(setTopic(newTopic.join(' '), channel, server));
|
dispatch(setTopic(newTopic.join(' '), channel, server));
|
||||||
} else {
|
} else if (channel) {
|
||||||
const topic = getState().channels.getIn([server, channel, 'topic']);
|
const { topic } = getState().channels[server][channel];
|
||||||
if (topic) {
|
if (topic) {
|
||||||
return text(topic);
|
return text(topic);
|
||||||
}
|
}
|
||||||
return 'No topic set';
|
|
||||||
}
|
}
|
||||||
|
return 'No topic set';
|
||||||
},
|
},
|
||||||
|
|
||||||
msg({ dispatch, server }, target, ...message) {
|
msg({ dispatch, server }, target, ...message) {
|
||||||
|
@ -11,20 +11,21 @@ export default class TabList extends PureComponent {
|
|||||||
const className = showTabList ? 'tablist off-canvas' : 'tablist';
|
const className = showTabList ? 'tablist off-canvas' : 'tablist';
|
||||||
const tabs = [];
|
const tabs = [];
|
||||||
|
|
||||||
channels.forEach((server, address) => {
|
channels.forEach(server => {
|
||||||
const srv = servers.get(address);
|
const { address } = server;
|
||||||
|
const srv = servers[address];
|
||||||
tabs.push(
|
tabs.push(
|
||||||
<TabListItem
|
<TabListItem
|
||||||
key={address}
|
key={address}
|
||||||
server={address}
|
server={address}
|
||||||
content={srv.name}
|
content={srv.name}
|
||||||
selected={tab.server === address && tab.name === null}
|
selected={tab.server === address && !tab.name}
|
||||||
connected={srv.status.connected}
|
connected={srv.status.connected}
|
||||||
onClick={this.handleTabClick}
|
onClick={this.handleTabClick}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
server.forEach((channel, name) =>
|
server.channels.forEach(name =>
|
||||||
tabs.push(
|
tabs.push(
|
||||||
<TabListItem
|
<TabListItem
|
||||||
key={address + name}
|
key={address + name}
|
||||||
@ -37,27 +38,25 @@ export default class TabList extends PureComponent {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (privateChats.has(address) && privateChats.get(address).size > 0) {
|
if (privateChats[address] && privateChats[address].length > 0) {
|
||||||
tabs.push(
|
tabs.push(
|
||||||
<div key={`${address}-pm}`} className="tab-label">
|
<div key={`${address}-pm}`} className="tab-label">
|
||||||
Private messages
|
Private messages
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
privateChats
|
privateChats[address].forEach(nick =>
|
||||||
.get(address)
|
tabs.push(
|
||||||
.forEach(nick =>
|
<TabListItem
|
||||||
tabs.push(
|
key={address + nick}
|
||||||
<TabListItem
|
server={address}
|
||||||
key={address + nick}
|
target={nick}
|
||||||
server={address}
|
content={nick}
|
||||||
target={nick}
|
selected={tab.server === address && tab.name === nick}
|
||||||
content={nick}
|
onClick={this.handleTabClick}
|
||||||
selected={tab.server === address && tab.name === nick}
|
/>
|
||||||
onClick={this.handleTabClick}
|
)
|
||||||
/>
|
);
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { isChannel } from 'utils';
|
||||||
import ChatTitle from './ChatTitle';
|
import ChatTitle from './ChatTitle';
|
||||||
import Search from './Search';
|
import Search from './Search';
|
||||||
import MessageBox from './MessageBox';
|
import MessageBox from './MessageBox';
|
||||||
@ -9,7 +10,7 @@ export default class Chat extends Component {
|
|||||||
handleCloseClick = () => {
|
handleCloseClick = () => {
|
||||||
const { tab, part, closePrivateChat, disconnect } = this.props;
|
const { tab, part, closePrivateChat, disconnect } = this.props;
|
||||||
|
|
||||||
if (tab.isChannel()) {
|
if (isChannel(tab)) {
|
||||||
part([tab.name], tab.server);
|
part([tab.name], tab.server);
|
||||||
} else if (tab.name) {
|
} else if (tab.name) {
|
||||||
closePrivateChat(tab.server, tab.name);
|
closePrivateChat(tab.server, tab.name);
|
||||||
@ -20,7 +21,7 @@ export default class Chat extends Component {
|
|||||||
|
|
||||||
handleSearch = phrase => {
|
handleSearch = phrase => {
|
||||||
const { tab, searchMessages } = this.props;
|
const { tab, searchMessages } = this.props;
|
||||||
if (tab.isChannel()) {
|
if (isChannel(tab)) {
|
||||||
searchMessages(tab.server, tab.name, phrase);
|
searchMessages(tab.server, tab.name, phrase);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -70,7 +71,7 @@ export default class Chat extends Component {
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
let chatClass;
|
let chatClass;
|
||||||
if (tab.isChannel()) {
|
if (isChannel(tab)) {
|
||||||
chatClass = 'chat-channel';
|
chatClass = 'chat-channel';
|
||||||
} else if (tab.name) {
|
} else if (tab.name) {
|
||||||
chatClass = 'chat-private';
|
chatClass = 'chat-private';
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { List } from 'immutable';
|
|
||||||
import Navicon from 'containers/Navicon';
|
import Navicon from 'containers/Navicon';
|
||||||
import Editable from 'components/ui/Editable';
|
import Editable from 'components/ui/Editable';
|
||||||
import { isValidServerName } from 'state/servers';
|
import { isValidServerName } from 'state/servers';
|
||||||
import { linkify } from 'utils';
|
import { isChannel, linkify } from 'utils';
|
||||||
|
|
||||||
export default class ChatTitle extends PureComponent {
|
export default class ChatTitle extends PureComponent {
|
||||||
render() {
|
render() {
|
||||||
@ -19,7 +18,7 @@ export default class ChatTitle extends PureComponent {
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
let closeTitle;
|
let closeTitle;
|
||||||
if (tab.isChannel()) {
|
if (isChannel(tab)) {
|
||||||
closeTitle = 'Leave';
|
closeTitle = 'Leave';
|
||||||
} else if (tab.name) {
|
} else if (tab.name) {
|
||||||
closeTitle = 'Close';
|
closeTitle = 'Close';
|
||||||
@ -49,7 +48,7 @@ export default class ChatTitle extends PureComponent {
|
|||||||
</Editable>
|
</Editable>
|
||||||
<div className="chat-topic-wrap">
|
<div className="chat-topic-wrap">
|
||||||
<span className="chat-topic">
|
<span className="chat-topic">
|
||||||
{linkify(channel.get('topic')) || null}
|
{channel && linkify(channel.topic)}
|
||||||
</span>
|
</span>
|
||||||
{serverError}
|
{serverError}
|
||||||
</div>
|
</div>
|
||||||
@ -64,7 +63,7 @@ export default class ChatTitle extends PureComponent {
|
|||||||
<div className="userlist-bar">
|
<div className="userlist-bar">
|
||||||
<i className="icon-user" />
|
<i className="icon-user" />
|
||||||
<span className="chat-usercount">
|
<span className="chat-usercount">
|
||||||
{channel.get('users', List()).size}
|
{channel && channel.users.length}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,13 +31,13 @@ export default class MessageBox extends PureComponent {
|
|||||||
this.bottom = false;
|
this.bottom = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextProps.messages.get(0) !== this.props.messages.get(0)) {
|
if (nextProps.messages[0] !== this.props.messages[0]) {
|
||||||
if (nextProps.tab === this.props.tab) {
|
if (nextProps.tab === this.props.tab) {
|
||||||
const addedMessages =
|
const addedMessages =
|
||||||
nextProps.messages.size - this.props.messages.size;
|
nextProps.messages.length - this.props.messages.length;
|
||||||
let addedHeight = 0;
|
let addedHeight = 0;
|
||||||
for (let i = 0; i < addedMessages; i++) {
|
for (let i = 0; i < addedMessages; i++) {
|
||||||
addedHeight += nextProps.messages.get(i).height;
|
addedHeight += nextProps.messages[i].height;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.nextScrollTop = addedHeight + this.container.scrollTop;
|
this.nextScrollTop = addedHeight + this.container.scrollTop;
|
||||||
@ -57,7 +57,7 @@ export default class MessageBox extends PureComponent {
|
|||||||
this.container.scrollTop = this.nextScrollTop;
|
this.container.scrollTop = this.nextScrollTop;
|
||||||
this.nextScrollTop = 0;
|
this.nextScrollTop = 0;
|
||||||
} else if (this.bottom) {
|
} else if (this.bottom) {
|
||||||
this.list.scrollToRow(this.props.messages.size);
|
this.list.scrollToRow(this.props.messages.length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ export default class MessageBox extends PureComponent {
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return this.props.messages.get(index - 1).height;
|
return this.props.messages[index - 1].height;
|
||||||
};
|
};
|
||||||
|
|
||||||
listRef = el => {
|
listRef = el => {
|
||||||
@ -101,7 +101,7 @@ export default class MessageBox extends PureComponent {
|
|||||||
} else {
|
} else {
|
||||||
this.bottom = true;
|
this.bottom = true;
|
||||||
if (scroll) {
|
if (scroll) {
|
||||||
this.list.scrollToRow(this.props.messages.size);
|
this.list.scrollToRow(this.props.messages.length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -177,7 +177,7 @@ export default class MessageBox extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { messages, onNickClick } = this.props;
|
const { messages, onNickClick } = this.props;
|
||||||
const message = messages.get(index - 1);
|
const message = messages[index - 1];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Message
|
<Message
|
||||||
@ -192,7 +192,7 @@ export default class MessageBox extends PureComponent {
|
|||||||
render() {
|
render() {
|
||||||
const props = {};
|
const props = {};
|
||||||
if (this.bottom) {
|
if (this.bottom) {
|
||||||
props.scrollToIndex = this.props.messages.size;
|
props.scrollToIndex = this.props.messages.length;
|
||||||
} else if (this.scrollTop >= 0) {
|
} else if (this.scrollTop >= 0) {
|
||||||
props.scrollTop = this.scrollTop;
|
props.scrollTop = this.scrollTop;
|
||||||
}
|
}
|
||||||
@ -209,7 +209,7 @@ export default class MessageBox extends PureComponent {
|
|||||||
ref={this.listRef}
|
ref={this.listRef}
|
||||||
width={width}
|
width={width}
|
||||||
height={height - 14}
|
height={height - 14}
|
||||||
rowCount={this.props.messages.size + 1}
|
rowCount={this.props.messages.length + 1}
|
||||||
rowHeight={this.getRowHeight}
|
rowHeight={this.getRowHeight}
|
||||||
rowRenderer={this.renderMessage}
|
rowRenderer={this.renderMessage}
|
||||||
onScroll={this.handleScroll}
|
onScroll={this.handleScroll}
|
||||||
|
@ -20,7 +20,7 @@ export default class UserList extends PureComponent {
|
|||||||
return (
|
return (
|
||||||
<UserListItem
|
<UserListItem
|
||||||
key={key}
|
key={key}
|
||||||
user={users.get(index)}
|
user={users[index]}
|
||||||
style={style}
|
style={style}
|
||||||
onClick={onNickClick}
|
onClick={onNickClick}
|
||||||
/>
|
/>
|
||||||
@ -39,7 +39,7 @@ export default class UserList extends PureComponent {
|
|||||||
ref={this.listRef}
|
ref={this.listRef}
|
||||||
width={200}
|
width={200}
|
||||||
height={height - 20}
|
height={height - 20}
|
||||||
rowCount={users.size}
|
rowCount={users.length}
|
||||||
rowHeight={24}
|
rowHeight={24}
|
||||||
rowRenderer={this.renderUser}
|
rowRenderer={this.renderUser}
|
||||||
className="rvlist-users"
|
className="rvlist-users"
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
import Navicon from 'containers/Navicon';
|
import Navicon from 'containers/Navicon';
|
||||||
|
|
||||||
|
const getSortedDefaultChannels = createSelector(
|
||||||
|
defaults => defaults.channels,
|
||||||
|
channels => channels.concat().sort()
|
||||||
|
);
|
||||||
|
|
||||||
export default class Connect extends Component {
|
export default class Connect extends Component {
|
||||||
state = {
|
state = {
|
||||||
showOptionals: false,
|
showOptionals: false,
|
||||||
@ -90,7 +96,9 @@ export default class Connect extends Component {
|
|||||||
{defaults.showDetails && (
|
{defaults.showDetails && (
|
||||||
<div className="connect-details">
|
<div className="connect-details">
|
||||||
<h2>{defaults.address}</h2>
|
<h2>{defaults.address}</h2>
|
||||||
{defaults.channels.sort().map(channel => <p>{channel}</p>)}
|
{getSortedDefaultChannels(defaults).map(channel => (
|
||||||
|
<p>{channel}</p>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<input name="nick" type="text" placeholder="Nick" />
|
<input name="nick" type="text" placeholder="Nick" />
|
||||||
|
@ -3,8 +3,8 @@ import Navicon from 'containers/Navicon';
|
|||||||
import FileInput from 'components/ui/FileInput';
|
import FileInput from 'components/ui/FileInput';
|
||||||
|
|
||||||
const Settings = ({ settings, onCertChange, onKeyChange, uploadCert }) => {
|
const Settings = ({ settings, onCertChange, onKeyChange, uploadCert }) => {
|
||||||
const status = settings.get('uploadingCert') ? 'Uploading...' : 'Upload';
|
const status = settings.uploadingCert ? 'Uploading...' : 'Upload';
|
||||||
const error = settings.get('certError');
|
const error = settings.certError;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="settings">
|
<div className="settings">
|
||||||
@ -14,14 +14,14 @@ const Settings = ({ settings, onCertChange, onKeyChange, uploadCert }) => {
|
|||||||
<div>
|
<div>
|
||||||
<p>Certificate</p>
|
<p>Certificate</p>
|
||||||
<FileInput
|
<FileInput
|
||||||
name={settings.get('certFile') || 'Select Certificate'}
|
name={settings.certFile || 'Select Certificate'}
|
||||||
onChange={onCertChange}
|
onChange={onCertChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p>Private Key</p>
|
<p>Private Key</p>
|
||||||
<FileInput
|
<FileInput
|
||||||
name={settings.get('keyFile') || 'Select Key'}
|
name={settings.keyFile || 'Select Key'}
|
||||||
onChange={onKeyChange}
|
onChange={onKeyChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@ import { createStructuredSelector } from 'reselect';
|
|||||||
import App from 'components/App';
|
import App from 'components/App';
|
||||||
import { getConnected } from 'state/app';
|
import { getConnected } from 'state/app';
|
||||||
import { getSortedChannels } from 'state/channels';
|
import { getSortedChannels } from 'state/channels';
|
||||||
import { getSortedPrivateChats } from 'state/privateChats';
|
import { getPrivateChats } from 'state/privateChats';
|
||||||
import { getServers } from 'state/servers';
|
import { getServers } from 'state/servers';
|
||||||
import { getSelectedTab, select } from 'state/tab';
|
import { getSelectedTab, select } from 'state/tab';
|
||||||
import { getShowTabList, hideMenu } from 'state/ui';
|
import { getShowTabList, hideMenu } from 'state/ui';
|
||||||
@ -12,7 +12,7 @@ import { push } from 'utils/router';
|
|||||||
const mapState = createStructuredSelector({
|
const mapState = createStructuredSelector({
|
||||||
channels: getSortedChannels,
|
channels: getSortedChannels,
|
||||||
connected: getConnected,
|
connected: getConnected,
|
||||||
privateChats: getSortedPrivateChats,
|
privateChats: getPrivateChats,
|
||||||
servers: getServers,
|
servers: getServers,
|
||||||
showTabList: getShowTabList,
|
showTabList: getShowTabList,
|
||||||
tab: getSelectedTab
|
tab: getSelectedTab
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
} from 'state/messages';
|
} from 'state/messages';
|
||||||
import { reconnect } from 'state/servers';
|
import { reconnect } from 'state/servers';
|
||||||
import { select } from 'state/tab';
|
import { select } from 'state/tab';
|
||||||
import { normalizeChannel } from 'utils';
|
import { find, normalizeChannel } from 'utils';
|
||||||
|
|
||||||
function withReason(message, reason) {
|
function withReason(message, reason) {
|
||||||
return message + (reason ? ` (${reason})` : '');
|
return message + (reason ? ` (${reason})` : '');
|
||||||
@ -18,9 +18,9 @@ function withReason(message, reason) {
|
|||||||
function findChannels(state, server, user) {
|
function findChannels(state, server, user) {
|
||||||
const channels = [];
|
const channels = [];
|
||||||
|
|
||||||
state.channels.get(server).forEach((channel, channelName) => {
|
Object.keys(state.channels[server]).forEach(channel => {
|
||||||
if (channel.get('users').find(u => u.nick === user)) {
|
if (find(state.channels[server][channel].users, u => u.nick === user)) {
|
||||||
channels.push(channelName);
|
channels.push(channel);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ export default function handleSocket({
|
|||||||
const tab = state.tab.selected;
|
const tab = state.tab.selected;
|
||||||
const [joinedChannel] = channels;
|
const [joinedChannel] = channels;
|
||||||
if (tab.server && tab.name) {
|
if (tab.server && tab.name) {
|
||||||
const { nick } = state.servers.get(tab.server);
|
const { nick } = state.servers[tab.server];
|
||||||
if (
|
if (
|
||||||
tab.server === server &&
|
tab.server === server &&
|
||||||
nick === user &&
|
nick === user &&
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import Cookie from 'js-cookie';
|
import Cookie from 'js-cookie';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { getSelectedTab } from 'state/tab';
|
import { getSelectedTab } from 'state/tab';
|
||||||
|
import { isChannel, stringifyTab } from 'utils';
|
||||||
import { observe } from 'utils/observe';
|
import { observe } from 'utils/observe';
|
||||||
|
|
||||||
const saveTab = debounce(
|
const saveTab = debounce(
|
||||||
tab => Cookie.set('tab', tab.toString(), { expires: 30 }),
|
tab => Cookie.set('tab', stringifyTab(tab), { expires: 30 }),
|
||||||
1000
|
1000
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function storage({ store }) {
|
export default function storage({ store }) {
|
||||||
observe(store, getSelectedTab, tab => {
|
observe(store, getSelectedTab, tab => {
|
||||||
if (tab.isChannel() || (tab.server && !tab.name)) {
|
if (isChannel(tab) || (tab.server && !tab.name)) {
|
||||||
saveTab(tab);
|
saveTab(tab);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import Immutable from 'immutable';
|
import reducer, { compareUsers, getSortedChannels } from '../channels';
|
||||||
import reducer, { compareUsers } from '../channels';
|
|
||||||
import { connect } from '../servers';
|
import { connect } from '../servers';
|
||||||
import * as actions from '../actions';
|
import * as actions from '../actions';
|
||||||
|
|
||||||
describe('channel reducer', () => {
|
describe('channel reducer', () => {
|
||||||
it('removes channels on PART', () => {
|
it('removes channels on PART', () => {
|
||||||
let state = Immutable.fromJS({
|
let state = {
|
||||||
srv1: {
|
srv1: {
|
||||||
chan1: {},
|
chan1: {},
|
||||||
chan2: {},
|
chan2: {},
|
||||||
@ -14,7 +13,7 @@ describe('channel reducer', () => {
|
|||||||
srv2: {
|
srv2: {
|
||||||
chan1: {}
|
chan1: {}
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
state = reducer(state, {
|
state = reducer(state, {
|
||||||
type: actions.PART,
|
type: actions.PART,
|
||||||
@ -22,7 +21,7 @@ describe('channel reducer', () => {
|
|||||||
channels: ['chan1', 'chan3']
|
channels: ['chan1', 'chan3']
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
srv1: {
|
srv1: {
|
||||||
chan2: {}
|
chan2: {}
|
||||||
},
|
},
|
||||||
@ -44,7 +43,7 @@ describe('channel reducer', () => {
|
|||||||
user: 'nick2'
|
user: 'nick2'
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
srv: {
|
srv: {
|
||||||
chan1: {
|
chan1: {
|
||||||
users: [{ mode: '', nick: 'nick1', renderName: 'nick1' }]
|
users: [{ mode: '', nick: 'nick1', renderName: 'nick1' }]
|
||||||
@ -59,7 +58,7 @@ describe('channel reducer', () => {
|
|||||||
it('handles SOCKET_JOIN', () => {
|
it('handles SOCKET_JOIN', () => {
|
||||||
const state = reducer(undefined, socket_join('srv', 'chan1', 'nick1'));
|
const state = reducer(undefined, socket_join('srv', 'chan1', 'nick1'));
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
srv: {
|
srv: {
|
||||||
chan1: {
|
chan1: {
|
||||||
users: [{ mode: '', nick: 'nick1', renderName: 'nick1' }]
|
users: [{ mode: '', nick: 'nick1', renderName: 'nick1' }]
|
||||||
@ -79,7 +78,7 @@ describe('channel reducer', () => {
|
|||||||
user: 'nick2'
|
user: 'nick2'
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
srv: {
|
srv: {
|
||||||
chan1: {
|
chan1: {
|
||||||
users: [{ mode: '', nick: 'nick1', renderName: 'nick1' }]
|
users: [{ mode: '', nick: 'nick1', renderName: 'nick1' }]
|
||||||
@ -103,7 +102,7 @@ describe('channel reducer', () => {
|
|||||||
newNick: 'nick3'
|
newNick: 'nick3'
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
srv: {
|
srv: {
|
||||||
chan1: {
|
chan1: {
|
||||||
users: [
|
users: [
|
||||||
@ -126,7 +125,7 @@ describe('channel reducer', () => {
|
|||||||
users: ['user3', 'user2', '@user4', 'user1', '+user5']
|
users: ['user3', 'user2', '@user4', 'user1', '+user5']
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
srv: {
|
srv: {
|
||||||
chan1: {
|
chan1: {
|
||||||
users: [
|
users: [
|
||||||
@ -149,10 +148,11 @@ describe('channel reducer', () => {
|
|||||||
topic: 'the topic'
|
topic: 'the topic'
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
srv: {
|
srv: {
|
||||||
chan1: {
|
chan1: {
|
||||||
topic: 'the topic'
|
topic: 'the topic',
|
||||||
|
users: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -165,7 +165,7 @@ describe('channel reducer', () => {
|
|||||||
|
|
||||||
state = reducer(state, socket_mode('srv', 'chan1', 'nick1', 'o', ''));
|
state = reducer(state, socket_mode('srv', 'chan1', 'nick1', 'o', ''));
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
srv: {
|
srv: {
|
||||||
chan1: {
|
chan1: {
|
||||||
users: [
|
users: [
|
||||||
@ -183,7 +183,7 @@ describe('channel reducer', () => {
|
|||||||
state = reducer(state, socket_mode('srv', 'chan1', 'nick2', 'o', ''));
|
state = reducer(state, socket_mode('srv', 'chan1', 'nick2', 'o', ''));
|
||||||
state = reducer(state, socket_mode('srv', 'chan2', 'not_there', 'x', ''));
|
state = reducer(state, socket_mode('srv', 'chan2', 'not_there', 'x', ''));
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
srv: {
|
srv: {
|
||||||
chan1: {
|
chan1: {
|
||||||
users: [
|
users: [
|
||||||
@ -208,7 +208,7 @@ describe('channel reducer', () => {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
srv: {
|
srv: {
|
||||||
chan1: { topic: 'the topic', users: [] },
|
chan1: { topic: 'the topic', users: [] },
|
||||||
chan2: { users: [] }
|
chan2: { users: [] }
|
||||||
@ -225,7 +225,7 @@ describe('channel reducer', () => {
|
|||||||
data: [{ host: '127.0.0.1' }, { host: 'thehost' }]
|
data: [{ host: '127.0.0.1' }, { host: 'thehost' }]
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
'127.0.0.1': {},
|
'127.0.0.1': {},
|
||||||
thehost: {}
|
thehost: {}
|
||||||
});
|
});
|
||||||
@ -234,23 +234,23 @@ describe('channel reducer', () => {
|
|||||||
it('optimistically adds the server on CONNECT', () => {
|
it('optimistically adds the server on CONNECT', () => {
|
||||||
const state = reducer(undefined, connect('127.0.0.1:1337', 'nick', {}));
|
const state = reducer(undefined, connect('127.0.0.1:1337', 'nick', {}));
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
'127.0.0.1': {}
|
'127.0.0.1': {}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('removes the server on DISCONNECT', () => {
|
it('removes the server on DISCONNECT', () => {
|
||||||
let state = Immutable.fromJS({
|
let state = {
|
||||||
srv: {},
|
srv: {},
|
||||||
srv2: {}
|
srv2: {}
|
||||||
});
|
};
|
||||||
|
|
||||||
state = reducer(state, {
|
state = reducer(state, {
|
||||||
type: actions.DISCONNECT,
|
type: actions.DISCONNECT,
|
||||||
server: 'srv2'
|
server: 'srv2'
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
srv: {}
|
srv: {}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -301,3 +301,32 @@ describe('compareUsers()', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getSortedChannels', () => {
|
||||||
|
it('sorts servers and channels', () => {
|
||||||
|
expect(
|
||||||
|
getSortedChannels({
|
||||||
|
channels: {
|
||||||
|
'bob.com': {},
|
||||||
|
'127.0.0.1': {
|
||||||
|
'#chan1': {
|
||||||
|
users: [],
|
||||||
|
topic: 'cake'
|
||||||
|
},
|
||||||
|
'#pie': {},
|
||||||
|
'##apples': {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
|
address: '127.0.0.1',
|
||||||
|
channels: ['##apples', '#chan1', '#pie']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
address: 'bob.com',
|
||||||
|
channels: []
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { Map, fromJS } from 'immutable';
|
|
||||||
import reducer, { broadcast, getMessageTab } from '../messages';
|
import reducer, { broadcast, getMessageTab } from '../messages';
|
||||||
import * as actions from '../actions';
|
import * as actions from '../actions';
|
||||||
import appReducer from '../app';
|
import appReducer from '../app';
|
||||||
@ -15,7 +14,7 @@ describe('message reducer', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toMatchObject({
|
expect(state).toMatchObject({
|
||||||
srv: {
|
srv: {
|
||||||
'#chan1': [
|
'#chan1': [
|
||||||
{
|
{
|
||||||
@ -49,7 +48,7 @@ describe('message reducer', () => {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toMatchObject({
|
expect(state).toMatchObject({
|
||||||
srv: {
|
srv: {
|
||||||
'#chan1': [
|
'#chan1': [
|
||||||
{
|
{
|
||||||
@ -72,11 +71,11 @@ describe('message reducer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('handles prepending of messages on ADD_MESSAGES', () => {
|
it('handles prepending of messages on ADD_MESSAGES', () => {
|
||||||
let state = fromJS({
|
let state = {
|
||||||
srv: {
|
srv: {
|
||||||
'#chan1': [{ id: 0 }]
|
'#chan1': [{ id: 0 }]
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
state = reducer(state, {
|
state = reducer(state, {
|
||||||
type: actions.ADD_MESSAGES,
|
type: actions.ADD_MESSAGES,
|
||||||
@ -86,7 +85,7 @@ describe('message reducer', () => {
|
|||||||
messages: [{ id: 1 }, { id: 2 }]
|
messages: [{ id: 1 }, { id: 2 }]
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toMatchObject({
|
expect(state).toMatchObject({
|
||||||
srv: {
|
srv: {
|
||||||
'#chan1': [{ id: 1 }, { id: 2 }, { id: 0 }]
|
'#chan1': [{ id: 1 }, { id: 2 }, { id: 0 }]
|
||||||
}
|
}
|
||||||
@ -103,7 +102,7 @@ describe('message reducer', () => {
|
|||||||
state.messages = reducer(undefined, action);
|
state.messages = reducer(undefined, action);
|
||||||
}, () => state);
|
}, () => state);
|
||||||
|
|
||||||
const messages = state.messages.toJS();
|
const messages = state.messages;
|
||||||
|
|
||||||
expect(messages.srv).not.toHaveProperty('srv');
|
expect(messages.srv).not.toHaveProperty('srv');
|
||||||
expect(messages.srv['#chan1']).toHaveLength(1);
|
expect(messages.srv['#chan1']).toHaveLength(1);
|
||||||
@ -113,7 +112,7 @@ describe('message reducer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('deletes all messages related to server when disconnecting', () => {
|
it('deletes all messages related to server when disconnecting', () => {
|
||||||
let state = fromJS({
|
let state = {
|
||||||
srv: {
|
srv: {
|
||||||
'#chan1': [{ content: 'msg1' }, { content: 'msg2' }],
|
'#chan1': [{ content: 'msg1' }, { content: 'msg2' }],
|
||||||
'#chan2': [{ content: 'msg' }]
|
'#chan2': [{ content: 'msg' }]
|
||||||
@ -121,14 +120,14 @@ describe('message reducer', () => {
|
|||||||
srv2: {
|
srv2: {
|
||||||
'#chan1': [{ content: 'msg' }]
|
'#chan1': [{ content: 'msg' }]
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
state = reducer(state, {
|
state = reducer(state, {
|
||||||
type: actions.DISCONNECT,
|
type: actions.DISCONNECT,
|
||||||
server: 'srv'
|
server: 'srv'
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
srv2: {
|
srv2: {
|
||||||
'#chan1': [{ content: 'msg' }]
|
'#chan1': [{ content: 'msg' }]
|
||||||
}
|
}
|
||||||
@ -136,7 +135,7 @@ describe('message reducer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('deletes all messages related to channel when parting', () => {
|
it('deletes all messages related to channel when parting', () => {
|
||||||
let state = fromJS({
|
let state = {
|
||||||
srv: {
|
srv: {
|
||||||
'#chan1': [{ content: 'msg1' }, { content: 'msg2' }],
|
'#chan1': [{ content: 'msg1' }, { content: 'msg2' }],
|
||||||
'#chan2': [{ content: 'msg' }]
|
'#chan2': [{ content: 'msg' }]
|
||||||
@ -144,7 +143,7 @@ describe('message reducer', () => {
|
|||||||
srv2: {
|
srv2: {
|
||||||
'#chan1': [{ content: 'msg' }]
|
'#chan1': [{ content: 'msg' }]
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
state = reducer(state, {
|
state = reducer(state, {
|
||||||
type: actions.PART,
|
type: actions.PART,
|
||||||
@ -152,7 +151,7 @@ describe('message reducer', () => {
|
|||||||
channels: ['#chan1']
|
channels: ['#chan1']
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
srv: {
|
srv: {
|
||||||
'#chan2': [{ content: 'msg' }]
|
'#chan2': [{ content: 'msg' }]
|
||||||
},
|
},
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import Immutable from 'immutable';
|
|
||||||
import reducer, { connect, setServerName } from '../servers';
|
import reducer, { connect, setServerName } from '../servers';
|
||||||
import * as actions from '../actions';
|
import * as actions from '../actions';
|
||||||
|
|
||||||
@ -6,7 +5,7 @@ describe('server reducer', () => {
|
|||||||
it('adds the server on CONNECT', () => {
|
it('adds the server on CONNECT', () => {
|
||||||
let state = reducer(undefined, connect('127.0.0.1:1337', 'nick', {}));
|
let state = reducer(undefined, connect('127.0.0.1:1337', 'nick', {}));
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
'127.0.0.1': {
|
'127.0.0.1': {
|
||||||
name: '127.0.0.1',
|
name: '127.0.0.1',
|
||||||
nick: 'nick',
|
nick: 'nick',
|
||||||
@ -20,7 +19,7 @@ describe('server reducer', () => {
|
|||||||
|
|
||||||
state = reducer(state, connect('127.0.0.1:1337', 'nick', {}));
|
state = reducer(state, connect('127.0.0.1:1337', 'nick', {}));
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
'127.0.0.1': {
|
'127.0.0.1': {
|
||||||
name: '127.0.0.1',
|
name: '127.0.0.1',
|
||||||
nick: 'nick',
|
nick: 'nick',
|
||||||
@ -39,7 +38,7 @@ describe('server reducer', () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
'127.0.0.1': {
|
'127.0.0.1': {
|
||||||
name: '127.0.0.1',
|
name: '127.0.0.1',
|
||||||
nick: 'nick',
|
nick: 'nick',
|
||||||
@ -62,31 +61,31 @@ describe('server reducer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('removes the server on DISCONNECT', () => {
|
it('removes the server on DISCONNECT', () => {
|
||||||
let state = Immutable.fromJS({
|
let state = {
|
||||||
srv: {},
|
srv: {},
|
||||||
srv2: {}
|
srv2: {}
|
||||||
});
|
};
|
||||||
|
|
||||||
state = reducer(state, {
|
state = reducer(state, {
|
||||||
type: actions.DISCONNECT,
|
type: actions.DISCONNECT,
|
||||||
server: 'srv2'
|
server: 'srv2'
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
srv: {}
|
srv: {}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles SET_SERVER_NAME', () => {
|
it('handles SET_SERVER_NAME', () => {
|
||||||
let state = Immutable.fromJS({
|
let state = {
|
||||||
srv: {
|
srv: {
|
||||||
name: 'cake'
|
name: 'cake'
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
state = reducer(state, setServerName('pie', 'srv'));
|
state = reducer(state, setServerName('pie', 'srv'));
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
srv: {
|
srv: {
|
||||||
name: 'pie'
|
name: 'pie'
|
||||||
}
|
}
|
||||||
@ -102,7 +101,7 @@ describe('server reducer', () => {
|
|||||||
editing: true
|
editing: true
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toMatchObject({
|
expect(state).toMatchObject({
|
||||||
'127.0.0.1': {
|
'127.0.0.1': {
|
||||||
name: '127.0.0.1',
|
name: '127.0.0.1',
|
||||||
nick: 'nick',
|
nick: 'nick',
|
||||||
@ -125,7 +124,7 @@ describe('server reducer', () => {
|
|||||||
nick: ''
|
nick: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toMatchObject({
|
expect(state).toMatchObject({
|
||||||
'127.0.0.1': {
|
'127.0.0.1': {
|
||||||
name: '127.0.0.1',
|
name: '127.0.0.1',
|
||||||
nick: 'nick',
|
nick: 'nick',
|
||||||
@ -143,7 +142,7 @@ describe('server reducer', () => {
|
|||||||
newNick: 'nick2'
|
newNick: 'nick2'
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toMatchObject({
|
expect(state).toMatchObject({
|
||||||
'127.0.0.1': {
|
'127.0.0.1': {
|
||||||
name: '127.0.0.1',
|
name: '127.0.0.1',
|
||||||
nick: 'nick2',
|
nick: 'nick2',
|
||||||
@ -165,7 +164,7 @@ describe('server reducer', () => {
|
|||||||
server: '127.0.0.1'
|
server: '127.0.0.1'
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toMatchObject({
|
expect(state).toMatchObject({
|
||||||
'127.0.0.1': {
|
'127.0.0.1': {
|
||||||
name: '127.0.0.1',
|
name: '127.0.0.1',
|
||||||
nick: 'nick',
|
nick: 'nick',
|
||||||
@ -197,23 +196,19 @@ describe('server reducer', () => {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
'127.0.0.1': {
|
'127.0.0.1': {
|
||||||
name: 'stuff',
|
name: 'stuff',
|
||||||
nick: 'nick',
|
nick: 'nick',
|
||||||
editedNick: null,
|
|
||||||
status: {
|
status: {
|
||||||
connected: true,
|
connected: true
|
||||||
error: null
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'127.0.0.2': {
|
'127.0.0.2': {
|
||||||
name: 'stuffz',
|
name: 'stuffz',
|
||||||
nick: 'nick2',
|
nick: 'nick2',
|
||||||
editedNick: null,
|
|
||||||
status: {
|
status: {
|
||||||
connected: false,
|
connected: false
|
||||||
error: null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -227,14 +222,13 @@ describe('server reducer', () => {
|
|||||||
connected: true
|
connected: true
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
'127.0.0.1': {
|
'127.0.0.1': {
|
||||||
name: '127.0.0.1',
|
name: '127.0.0.1',
|
||||||
nick: 'nick',
|
nick: 'nick',
|
||||||
editedNick: null,
|
editedNick: null,
|
||||||
status: {
|
status: {
|
||||||
connected: true,
|
connected: true
|
||||||
error: null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -246,7 +240,7 @@ describe('server reducer', () => {
|
|||||||
error: 'Bad stuff happened'
|
error: 'Bad stuff happened'
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
'127.0.0.1': {
|
'127.0.0.1': {
|
||||||
name: '127.0.0.1',
|
name: '127.0.0.1',
|
||||||
nick: 'nick',
|
nick: 'nick',
|
||||||
|
@ -6,14 +6,14 @@ describe('tab reducer', () => {
|
|||||||
it('selects the tab and adds it to history', () => {
|
it('selects the tab and adds it to history', () => {
|
||||||
let state = reducer(undefined, setSelectedTab('srv', '#chan'));
|
let state = reducer(undefined, setSelectedTab('srv', '#chan'));
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
selected: { server: 'srv', name: '#chan' },
|
selected: { server: 'srv', name: '#chan' },
|
||||||
history: [{ server: 'srv', name: '#chan' }]
|
history: [{ server: 'srv', name: '#chan' }]
|
||||||
});
|
});
|
||||||
|
|
||||||
state = reducer(state, setSelectedTab('srv', 'user1'));
|
state = reducer(state, setSelectedTab('srv', 'user1'));
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
selected: { server: 'srv', name: 'user1' },
|
selected: { server: 'srv', name: 'user1' },
|
||||||
history: [
|
history: [
|
||||||
{ server: 'srv', name: '#chan' },
|
{ server: 'srv', name: '#chan' },
|
||||||
@ -34,7 +34,7 @@ describe('tab reducer', () => {
|
|||||||
channels: ['#chan']
|
channels: ['#chan']
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
selected: { server: 'srv', name: '#chan3' },
|
selected: { server: 'srv', name: '#chan3' },
|
||||||
history: [
|
history: [
|
||||||
{ server: 'srv1', name: 'bob' },
|
{ server: 'srv1', name: 'bob' },
|
||||||
@ -55,7 +55,7 @@ describe('tab reducer', () => {
|
|||||||
nick: 'bob'
|
nick: 'bob'
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
selected: { server: 'srv', name: '#chan3' },
|
selected: { server: 'srv', name: '#chan3' },
|
||||||
history: [
|
history: [
|
||||||
{ server: 'srv', name: '#chan' },
|
{ server: 'srv', name: '#chan' },
|
||||||
@ -76,7 +76,7 @@ describe('tab reducer', () => {
|
|||||||
server: 'srv'
|
server: 'srv'
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
selected: { server: 'srv', name: '#chan3' },
|
selected: { server: 'srv', name: '#chan3' },
|
||||||
history: [{ server: 'srv1', name: 'bob' }]
|
history: [{ server: 'srv1', name: 'bob' }]
|
||||||
});
|
});
|
||||||
@ -87,8 +87,8 @@ describe('tab reducer', () => {
|
|||||||
|
|
||||||
state = reducer(state, locationChanged('settings'));
|
state = reducer(state, locationChanged('settings'));
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
selected: { server: null, name: null },
|
selected: {},
|
||||||
history: [{ server: 'srv', name: '#chan' }]
|
history: [{ server: 'srv', name: '#chan' }]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -102,7 +102,7 @@ describe('tab reducer', () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state).toEqual({
|
||||||
selected: { server: 'srv', name: '#chan' },
|
selected: { server: 'srv', name: '#chan' },
|
||||||
history: [{ server: 'srv', name: '#chan' }]
|
history: [{ server: 'srv', name: '#chan' }]
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { Record } from 'immutable';
|
|
||||||
import createReducer from 'utils/createReducer';
|
import createReducer from 'utils/createReducer';
|
||||||
import * as actions from './actions';
|
import * as actions from './actions';
|
||||||
|
|
||||||
@ -9,34 +8,31 @@ export const getCharWidth = state => state.app.charWidth;
|
|||||||
export const getWindowWidth = state => state.app.windowWidth;
|
export const getWindowWidth = state => state.app.windowWidth;
|
||||||
export const getConnectDefaults = state => state.app.connectDefaults;
|
export const getConnectDefaults = state => state.app.connectDefaults;
|
||||||
|
|
||||||
const ConnectDefaults = Record({
|
const initialState = {
|
||||||
name: '',
|
|
||||||
address: '',
|
|
||||||
channels: [],
|
|
||||||
ssl: false,
|
|
||||||
password: false,
|
|
||||||
readonly: false,
|
|
||||||
showDetails: false
|
|
||||||
});
|
|
||||||
|
|
||||||
const App = Record({
|
|
||||||
connected: true,
|
connected: true,
|
||||||
wrapWidth: 0,
|
wrapWidth: 0,
|
||||||
charWidth: 0,
|
charWidth: 0,
|
||||||
windowWidth: 0,
|
windowWidth: 0,
|
||||||
connectDefaults: new ConnectDefaults()
|
connectDefaults: {
|
||||||
});
|
name: '',
|
||||||
|
address: '',
|
||||||
|
channels: [],
|
||||||
|
ssl: false,
|
||||||
|
password: false,
|
||||||
|
readonly: false,
|
||||||
|
showDetails: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default createReducer(new App(), {
|
export default createReducer(initialState, {
|
||||||
[actions.APP_SET](state, action) {
|
[actions.APP_SET](state, { key, value }) {
|
||||||
return state.set(action.key, action.value);
|
state[key] = value;
|
||||||
},
|
},
|
||||||
|
|
||||||
[actions.UPDATE_MESSAGE_HEIGHT](state, action) {
|
[actions.UPDATE_MESSAGE_HEIGHT](state, action) {
|
||||||
return state
|
state.wrapWidth = action.wrapWidth;
|
||||||
.set('wrapWidth', action.wrapWidth)
|
state.charWidth = action.charWidth;
|
||||||
.set('charWidth', action.charWidth)
|
state.windowWidth = action.windowWidth;
|
||||||
.set('windowWidth', action.windowWidth);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -57,5 +53,5 @@ export function setCharWidth(width) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function setConnectDefaults(defaults) {
|
export function setConnectDefaults(defaults) {
|
||||||
return appSet('connectDefaults', new ConnectDefaults(defaults));
|
return appSet('connectDefaults', defaults);
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
import { Map, List, Record } from 'immutable';
|
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import sortBy from 'lodash/sortBy';
|
||||||
import createReducer from 'utils/createReducer';
|
import createReducer from 'utils/createReducer';
|
||||||
|
import { find, findIndex } from 'utils';
|
||||||
import { getSelectedTab, updateSelection } from './tab';
|
import { getSelectedTab, updateSelection } from './tab';
|
||||||
import * as actions from './actions';
|
import * as actions from './actions';
|
||||||
|
|
||||||
const User = Record({
|
|
||||||
nick: null,
|
|
||||||
renderName: null,
|
|
||||||
mode: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
const modePrefixes = [
|
const modePrefixes = [
|
||||||
{ mode: 'q', prefix: '~' }, // Owner
|
{ mode: 'q', prefix: '~' }, // Owner
|
||||||
{ mode: 'a', prefix: '&' }, // Admin
|
{ mode: 'a', prefix: '&' }, // Admin
|
||||||
@ -18,24 +14,23 @@ const modePrefixes = [
|
|||||||
{ mode: 'v', prefix: '+' } // Voice
|
{ mode: 'v', prefix: '+' } // Voice
|
||||||
];
|
];
|
||||||
|
|
||||||
function updateRenderName(user) {
|
function getRenderName(user) {
|
||||||
for (let i = 0; i < modePrefixes.length; i++) {
|
for (let i = 0; i < modePrefixes.length; i++) {
|
||||||
if (user.mode.indexOf(modePrefixes[i].mode) !== -1) {
|
if (user.mode.indexOf(modePrefixes[i].mode) !== -1) {
|
||||||
return user.set('renderName', `${modePrefixes[i].prefix}${user.nick}`);
|
return `${modePrefixes[i].prefix}${user.nick}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return user.set('renderName', user.nick);
|
return user.nick;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createUser(nick, mode) {
|
function createUser(nick, mode) {
|
||||||
return updateRenderName(
|
const user = {
|
||||||
new User({
|
nick,
|
||||||
nick,
|
mode: mode || ''
|
||||||
renderName: nick,
|
};
|
||||||
mode: mode || ''
|
user.renderName = getRenderName(user);
|
||||||
})
|
return user;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadUser(nick) {
|
function loadUser(nick) {
|
||||||
@ -54,6 +49,22 @@ function loadUser(nick) {
|
|||||||
return createUser(nick);
|
return createUser(nick);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeUser(users, nick) {
|
||||||
|
const i = findIndex(users, u => u.nick === nick);
|
||||||
|
if (i !== -1) {
|
||||||
|
users.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(state, server, channel) {
|
||||||
|
if (!state[server]) {
|
||||||
|
state[server] = {};
|
||||||
|
}
|
||||||
|
if (channel && !state[server][channel]) {
|
||||||
|
state[server][channel] = { users: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function compareUsers(a, b) {
|
export function compareUsers(a, b) {
|
||||||
a = a.renderName.toLowerCase();
|
a = a.renderName.toLowerCase();
|
||||||
b = b.renderName.toLowerCase();
|
b = b.renderName.toLowerCase();
|
||||||
@ -80,152 +91,119 @@ export function compareUsers(a, b) {
|
|||||||
|
|
||||||
export const getChannels = state => state.channels;
|
export const getChannels = state => state.channels;
|
||||||
|
|
||||||
const key = (v, k) => k.toLowerCase();
|
|
||||||
|
|
||||||
export const getSortedChannels = createSelector(getChannels, channels =>
|
export const getSortedChannels = createSelector(getChannels, channels =>
|
||||||
channels
|
sortBy(
|
||||||
.withMutations(c =>
|
Object.keys(channels).map(server => ({
|
||||||
c.forEach((server, address) =>
|
address: server,
|
||||||
c.update(address, chans => chans.sortBy(key))
|
channels: sortBy(Object.keys(channels[server]), channel =>
|
||||||
|
channel.toLowerCase()
|
||||||
)
|
)
|
||||||
)
|
})),
|
||||||
.sortBy(key)
|
server => server.address.toLowerCase()
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getSelectedChannel = createSelector(
|
export const getSelectedChannel = createSelector(
|
||||||
getSelectedTab,
|
getSelectedTab,
|
||||||
getChannels,
|
getChannels,
|
||||||
(tab, channels) => channels.getIn([tab.server, tab.name], Map())
|
(tab, channels) => get(channels, [tab.server, tab.name])
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getSelectedChannelUsers = createSelector(
|
export const getSelectedChannelUsers = createSelector(
|
||||||
getSelectedChannel,
|
getSelectedChannel,
|
||||||
channel => channel.get('users', List()).sort(compareUsers)
|
channel => {
|
||||||
|
if (channel) {
|
||||||
|
return channel.users.concat().sort(compareUsers);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export default createReducer(Map(), {
|
export default createReducer(
|
||||||
[actions.PART](state, { server, channels }) {
|
{},
|
||||||
return state.withMutations(s => {
|
{
|
||||||
channels.forEach(channel => s.deleteIn([server, channel]));
|
[actions.PART](state, { server, channels }) {
|
||||||
});
|
channels.forEach(channel => delete state[server][channel]);
|
||||||
},
|
},
|
||||||
|
|
||||||
[actions.socket.JOIN](state, { server, channels, user }) {
|
[actions.socket.JOIN](state, { server, channels, user }) {
|
||||||
return state.updateIn([server, channels[0], 'users'], List(), users =>
|
const channel = channels[0];
|
||||||
users.push(createUser(user))
|
init(state, server, channel);
|
||||||
);
|
state[server][channel].users.push(createUser(user));
|
||||||
},
|
},
|
||||||
|
|
||||||
[actions.socket.PART](state, { server, channel, user }) {
|
[actions.socket.PART](state, { server, channel, user }) {
|
||||||
if (state.hasIn([server, channel])) {
|
if (state[server][channel]) {
|
||||||
return state.updateIn([server, channel, 'users'], users =>
|
removeUser(state[server][channel].users, user);
|
||||||
users.filter(u => u.nick !== user)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.socket.QUIT](state, { server, user }) {
|
|
||||||
return state.withMutations(s => {
|
|
||||||
s.get(server).forEach((v, channel) => {
|
|
||||||
s.updateIn([server, channel, 'users'], users =>
|
|
||||||
users.filter(u => u.nick !== user)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.socket.NICK](state, { server, oldNick, newNick }) {
|
|
||||||
return state.withMutations(s => {
|
|
||||||
s.get(server).forEach((v, channel) => {
|
|
||||||
s.updateIn([server, channel, 'users'], users => {
|
|
||||||
const i = users.findIndex(user => user.nick === oldNick);
|
|
||||||
if (i < 0) {
|
|
||||||
return users;
|
|
||||||
}
|
|
||||||
|
|
||||||
return users.update(i, user =>
|
|
||||||
updateRenderName(user.set('nick', newNick))
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.socket.USERS](state, { server, channel, users }) {
|
|
||||||
return state.setIn(
|
|
||||||
[server, channel, 'users'],
|
|
||||||
List(users.map(user => loadUser(user)))
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.socket.TOPIC](state, { server, channel, topic }) {
|
|
||||||
return state.setIn([server, channel, 'topic'], topic);
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.socket.MODE](state, { server, channel, user, remove, add }) {
|
|
||||||
return state.updateIn([server, channel, 'users'], users => {
|
|
||||||
const i = users.findIndex(u => u.nick === user);
|
|
||||||
if (i < 0) {
|
|
||||||
return users;
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
return users.update(i, u => {
|
[actions.socket.QUIT](state, { server, user }) {
|
||||||
let { mode } = u;
|
Object.keys(state[server]).forEach(channel => {
|
||||||
|
removeUser(state[server][channel].users, user);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
[actions.socket.NICK](state, { server, oldNick, newNick }) {
|
||||||
|
Object.keys(state[server]).forEach(channel => {
|
||||||
|
const user = find(
|
||||||
|
state[server][channel].users,
|
||||||
|
u => u.nick === oldNick
|
||||||
|
);
|
||||||
|
if (user) {
|
||||||
|
user.nick = newNick;
|
||||||
|
user.renderName = getRenderName(user);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
[actions.socket.USERS](state, { server, channel, users }) {
|
||||||
|
init(state, server, channel);
|
||||||
|
state[server][channel].users = users.map(nick => loadUser(nick));
|
||||||
|
},
|
||||||
|
|
||||||
|
[actions.socket.TOPIC](state, { server, channel, topic }) {
|
||||||
|
init(state, server, channel);
|
||||||
|
state[server][channel].topic = topic;
|
||||||
|
},
|
||||||
|
|
||||||
|
[actions.socket.MODE](state, { server, channel, user, remove, add }) {
|
||||||
|
const u = find(state[server][channel].users, v => v.nick === user);
|
||||||
|
if (u) {
|
||||||
let j = remove.length;
|
let j = remove.length;
|
||||||
while (j--) {
|
while (j--) {
|
||||||
mode = mode.replace(remove[j], '');
|
u.mode = u.mode.replace(remove[j], '');
|
||||||
}
|
}
|
||||||
|
|
||||||
return updateRenderName(u.set('mode', mode + add));
|
u.mode += add;
|
||||||
});
|
u.renderName = getRenderName(u);
|
||||||
});
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
[actions.socket.CHANNELS](state, { data }) {
|
[actions.socket.CHANNELS](state, { data }) {
|
||||||
if (!data) {
|
if (data) {
|
||||||
return state;
|
data.forEach(({ server, name, topic }) => {
|
||||||
|
init(state, server, name);
|
||||||
|
state[server][name].topic = topic;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
[actions.socket.SERVERS](state, { data }) {
|
||||||
|
if (data) {
|
||||||
|
data.forEach(({ host }) => init(state, host));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
[actions.CONNECT](state, { host }) {
|
||||||
|
init(state, host);
|
||||||
|
},
|
||||||
|
|
||||||
|
[actions.DISCONNECT](state, { server }) {
|
||||||
|
delete state[server];
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.withMutations(s => {
|
|
||||||
data.forEach(channel => {
|
|
||||||
s.setIn(
|
|
||||||
[channel.server, channel.name],
|
|
||||||
Map({
|
|
||||||
users: List(),
|
|
||||||
topic: channel.topic
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.socket.SERVERS](state, { data }) {
|
|
||||||
if (!data) {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
return state.withMutations(s => {
|
|
||||||
data.forEach(server => {
|
|
||||||
if (!s.has(server.host)) {
|
|
||||||
s.set(server.host, Map());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.CONNECT](state, { host }) {
|
|
||||||
if (!state.has(host)) {
|
|
||||||
return state.set(host, Map());
|
|
||||||
}
|
|
||||||
|
|
||||||
return state;
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.DISCONNECT](state, { server }) {
|
|
||||||
return state.delete(server);
|
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
export function join(channels, server) {
|
export function join(channels, server) {
|
||||||
return {
|
return {
|
||||||
|
@ -1,54 +1,45 @@
|
|||||||
import { List, Record } from 'immutable';
|
|
||||||
import createReducer from 'utils/createReducer';
|
import createReducer from 'utils/createReducer';
|
||||||
import * as actions from './actions';
|
import * as actions from './actions';
|
||||||
|
|
||||||
const HISTORY_MAX_LENGTH = 128;
|
const HISTORY_MAX_LENGTH = 128;
|
||||||
|
|
||||||
const State = Record({
|
const initialState = {
|
||||||
history: List(),
|
history: [],
|
||||||
index: 0
|
index: 0
|
||||||
});
|
};
|
||||||
|
|
||||||
export const getCurrentInputHistoryEntry = state => {
|
export const getCurrentInputHistoryEntry = state => {
|
||||||
if (state.input.index === -1) {
|
if (state.input.index === -1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.input.history.get(state.input.index);
|
return state.input.history[state.input.index];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default createReducer(new State(), {
|
export default createReducer(initialState, {
|
||||||
[actions.INPUT_HISTORY_ADD](state, action) {
|
[actions.INPUT_HISTORY_ADD](state, { line }) {
|
||||||
const { line } = action;
|
if (line.trim() && line !== state.history[0]) {
|
||||||
if (line.trim() && line !== state.history.get(0)) {
|
|
||||||
if (history.length === HISTORY_MAX_LENGTH) {
|
if (history.length === HISTORY_MAX_LENGTH) {
|
||||||
return state.set('history', state.history.unshift(line).pop());
|
state.history.pop();
|
||||||
}
|
}
|
||||||
|
state.history.unshift(line);
|
||||||
return state.set('history', state.history.unshift(line));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return state;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
[actions.INPUT_HISTORY_RESET](state) {
|
[actions.INPUT_HISTORY_RESET](state) {
|
||||||
return state.set('index', -1);
|
state.index = -1;
|
||||||
},
|
},
|
||||||
|
|
||||||
[actions.INPUT_HISTORY_INCREMENT](state) {
|
[actions.INPUT_HISTORY_INCREMENT](state) {
|
||||||
if (state.index < state.history.size - 1) {
|
if (state.index < state.history.length - 1) {
|
||||||
return state.set('index', state.index + 1);
|
state.index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return state;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
[actions.INPUT_HISTORY_DECREMENT](state) {
|
[actions.INPUT_HISTORY_DECREMENT](state) {
|
||||||
if (state.index >= 0) {
|
if (state.index >= 0) {
|
||||||
return state.set('index', state.index - 1);
|
state.index--;
|
||||||
}
|
}
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { List, Map, Record } from 'immutable';
|
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
import has from 'lodash/has';
|
||||||
import {
|
import {
|
||||||
findBreakpoints,
|
findBreakpoints,
|
||||||
messageHeight,
|
messageHeight,
|
||||||
@ -12,95 +12,85 @@ import { getApp } from './app';
|
|||||||
import { getSelectedTab } from './tab';
|
import { getSelectedTab } from './tab';
|
||||||
import * as actions from './actions';
|
import * as actions from './actions';
|
||||||
|
|
||||||
const Message = Record({
|
|
||||||
id: null,
|
|
||||||
from: null,
|
|
||||||
content: '',
|
|
||||||
time: null,
|
|
||||||
type: null,
|
|
||||||
channel: false,
|
|
||||||
next: false,
|
|
||||||
height: 0,
|
|
||||||
length: 0,
|
|
||||||
breakpoints: null
|
|
||||||
});
|
|
||||||
|
|
||||||
export const getMessages = state => state.messages;
|
export const getMessages = state => state.messages;
|
||||||
|
|
||||||
export const getSelectedMessages = createSelector(
|
export const getSelectedMessages = createSelector(
|
||||||
getSelectedTab,
|
getSelectedTab,
|
||||||
getMessages,
|
getMessages,
|
||||||
(tab, messages) =>
|
(tab, messages) => {
|
||||||
messages.getIn([tab.server, tab.name || tab.server], List())
|
const target = tab.name || tab.server;
|
||||||
|
if (has(messages, [tab.server, target])) {
|
||||||
|
return messages[tab.server][target];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getHasMoreMessages = createSelector(
|
export const getHasMoreMessages = createSelector(
|
||||||
getSelectedMessages,
|
getSelectedMessages,
|
||||||
messages => {
|
messages => {
|
||||||
const first = messages.get(0);
|
const first = messages[0];
|
||||||
return first && first.next;
|
return first && first.next;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export default createReducer(Map(), {
|
function init(state, server, tab) {
|
||||||
[actions.ADD_MESSAGE](state, { server, tab, message }) {
|
if (!state[server]) {
|
||||||
return state.updateIn([server, tab], List(), list =>
|
state[server] = {};
|
||||||
list.push(new Message(message))
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.ADD_MESSAGES](state, { server, tab, messages, prepend }) {
|
|
||||||
return state.withMutations(s => {
|
|
||||||
if (prepend) {
|
|
||||||
for (let i = messages.length - 1; i >= 0; i--) {
|
|
||||||
s.updateIn([server, tab], List(), list =>
|
|
||||||
list.unshift(new Message(messages[i]))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
messages.forEach(message =>
|
|
||||||
s.updateIn([server, message.tab || tab], List(), list =>
|
|
||||||
list.push(new Message(message))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.DISCONNECT](state, { server }) {
|
|
||||||
return state.delete(server);
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.PART](state, { server, channels }) {
|
|
||||||
return state.withMutations(s =>
|
|
||||||
channels.forEach(channel => s.deleteIn([server, channel]))
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.UPDATE_MESSAGE_HEIGHT](
|
|
||||||
state,
|
|
||||||
{ wrapWidth, charWidth, windowWidth }
|
|
||||||
) {
|
|
||||||
return state.withMutations(s =>
|
|
||||||
s.forEach((server, serverKey) =>
|
|
||||||
server.forEach((target, targetKey) =>
|
|
||||||
target.forEach((message, index) =>
|
|
||||||
s.setIn(
|
|
||||||
[serverKey, targetKey, index, 'height'],
|
|
||||||
messageHeight(
|
|
||||||
message,
|
|
||||||
wrapWidth,
|
|
||||||
charWidth,
|
|
||||||
6 * charWidth,
|
|
||||||
windowWidth
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
if (!state[server][tab]) {
|
||||||
|
state[server][tab] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createReducer(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
[actions.ADD_MESSAGE](state, { server, tab, message }) {
|
||||||
|
init(state, server, tab);
|
||||||
|
state[server][tab].push(message);
|
||||||
|
},
|
||||||
|
|
||||||
|
[actions.ADD_MESSAGES](state, { server, tab, messages, prepend }) {
|
||||||
|
if (prepend) {
|
||||||
|
init(state, server, tab);
|
||||||
|
state[server][tab].unshift(...messages);
|
||||||
|
} else {
|
||||||
|
messages.forEach(message => {
|
||||||
|
init(state, server, message.tab || tab);
|
||||||
|
state[server][message.tab || tab].push(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
[actions.DISCONNECT](state, { server }) {
|
||||||
|
delete state[server];
|
||||||
|
},
|
||||||
|
|
||||||
|
[actions.PART](state, { server, channels }) {
|
||||||
|
channels.forEach(channel => delete state[server][channel]);
|
||||||
|
},
|
||||||
|
|
||||||
|
[actions.UPDATE_MESSAGE_HEIGHT](
|
||||||
|
state,
|
||||||
|
{ wrapWidth, charWidth, windowWidth }
|
||||||
|
) {
|
||||||
|
Object.keys(state).forEach(server =>
|
||||||
|
Object.keys(state[server]).forEach(target =>
|
||||||
|
state[server][target].forEach(message => {
|
||||||
|
message.height = messageHeight(
|
||||||
|
message,
|
||||||
|
wrapWidth,
|
||||||
|
charWidth,
|
||||||
|
6 * charWidth,
|
||||||
|
windowWidth
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
let nextID = 0;
|
let nextID = 0;
|
||||||
|
|
||||||
@ -156,14 +146,14 @@ export function getMessageTab(server, to) {
|
|||||||
export function fetchMessages() {
|
export function fetchMessages() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const first = getSelectedMessages(state).get(0);
|
const first = getSelectedMessages(state)[0];
|
||||||
|
|
||||||
if (!first) {
|
if (!first) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tab = state.tab.selected;
|
const tab = state.tab.selected;
|
||||||
if (tab.isChannel()) {
|
if (isChannel(tab)) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: actions.FETCH_MESSAGES,
|
type: actions.FETCH_MESSAGES,
|
||||||
socket: {
|
socket: {
|
||||||
@ -206,7 +196,7 @@ export function sendMessage(content, to, server) {
|
|||||||
tab: to,
|
tab: to,
|
||||||
message: initMessage(
|
message: initMessage(
|
||||||
{
|
{
|
||||||
from: state.servers.getIn([server, 'nick']),
|
from: state.servers[server].nick,
|
||||||
content
|
content
|
||||||
},
|
},
|
||||||
to,
|
to,
|
||||||
|
@ -1,48 +1,46 @@
|
|||||||
import { Set, Map } from 'immutable';
|
import sortBy from 'lodash/sortBy';
|
||||||
import { createSelector } from 'reselect';
|
import { findIndex } from 'utils';
|
||||||
import createReducer from 'utils/createReducer';
|
import createReducer from 'utils/createReducer';
|
||||||
import { updateSelection } from './tab';
|
import { updateSelection } from './tab';
|
||||||
import * as actions from './actions';
|
import * as actions from './actions';
|
||||||
|
|
||||||
export const getPrivateChats = state => state.privateChats;
|
export const getPrivateChats = state => state.privateChats;
|
||||||
|
|
||||||
const lowerCaseValue = v => v.toLowerCase();
|
|
||||||
|
|
||||||
export const getSortedPrivateChats = createSelector(
|
|
||||||
getPrivateChats,
|
|
||||||
privateChats =>
|
|
||||||
privateChats.withMutations(p =>
|
|
||||||
p.forEach((server, address) =>
|
|
||||||
p.update(address, chats => chats.sortBy(lowerCaseValue))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
function open(state, server, nick) {
|
function open(state, server, nick) {
|
||||||
return state.update(server, Set(), chats => chats.add(nick));
|
if (!state[server]) {
|
||||||
|
state[server] = [];
|
||||||
|
}
|
||||||
|
if (findIndex(state[server], n => n === nick) === -1) {
|
||||||
|
state[server].push(nick);
|
||||||
|
state[server] = sortBy(state[server], v => v.toLowerCase());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default createReducer(Map(), {
|
export default createReducer(
|
||||||
[actions.OPEN_PRIVATE_CHAT](state, action) {
|
{},
|
||||||
return open(state, action.server, action.nick);
|
{
|
||||||
},
|
[actions.OPEN_PRIVATE_CHAT](state, action) {
|
||||||
|
open(state, action.server, action.nick);
|
||||||
|
},
|
||||||
|
|
||||||
[actions.CLOSE_PRIVATE_CHAT](state, action) {
|
[actions.CLOSE_PRIVATE_CHAT](state, { server, nick }) {
|
||||||
return state.update(action.server, chats => chats.delete(action.nick));
|
const i = findIndex(state[server], n => n === nick);
|
||||||
},
|
if (i !== -1) {
|
||||||
|
state[server].splice(i, 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
[actions.socket.PM](state, action) {
|
[actions.socket.PM](state, action) {
|
||||||
if (action.from.indexOf('.') === -1) {
|
if (action.from.indexOf('.') === -1) {
|
||||||
return open(state, action.server, action.from);
|
open(state, action.server, action.from);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
[actions.DISCONNECT](state, { server }) {
|
||||||
|
delete state[server];
|
||||||
}
|
}
|
||||||
|
|
||||||
return state;
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.DISCONNECT](state, action) {
|
|
||||||
return state.delete(action.server);
|
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
export function openPrivateChat(server, nick) {
|
export function openPrivateChat(server, nick) {
|
||||||
return {
|
return {
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
import { List, Record } from 'immutable';
|
|
||||||
import createReducer from 'utils/createReducer';
|
import createReducer from 'utils/createReducer';
|
||||||
import * as actions from './actions';
|
import * as actions from './actions';
|
||||||
|
|
||||||
const State = Record({
|
const initialState = {
|
||||||
show: false,
|
show: false,
|
||||||
results: List()
|
results: []
|
||||||
});
|
};
|
||||||
|
|
||||||
export const getSearch = state => state.search;
|
export const getSearch = state => state.search;
|
||||||
|
|
||||||
export default createReducer(new State(), {
|
export default createReducer(initialState, {
|
||||||
[actions.socket.SEARCH](state, action) {
|
[actions.socket.SEARCH](state, { results }) {
|
||||||
return state.set('results', List(action.results));
|
state.results = results;
|
||||||
},
|
},
|
||||||
|
|
||||||
[actions.TOGGLE_SEARCH](state) {
|
[actions.TOGGLE_SEARCH](state) {
|
||||||
return state.set('show', !state.show);
|
state.show = !state.show;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
import get from 'lodash/get';
|
||||||
import { getServers } from './servers';
|
import { getServers } from './servers';
|
||||||
import { getSelectedTab } from './tab';
|
import { getSelectedTab } from './tab';
|
||||||
|
|
||||||
@ -6,5 +7,5 @@ import { getSelectedTab } from './tab';
|
|||||||
export const getSelectedTabTitle = createSelector(
|
export const getSelectedTabTitle = createSelector(
|
||||||
getSelectedTab,
|
getSelectedTab,
|
||||||
getServers,
|
getServers,
|
||||||
(tab, servers) => tab.name || servers.getIn([tab.server, 'name'])
|
(tab, servers) => tab.name || get(servers, [tab.server, 'name'])
|
||||||
);
|
);
|
||||||
|
@ -1,30 +1,21 @@
|
|||||||
import { Map, Record } from 'immutable';
|
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
import get from 'lodash/get';
|
||||||
import createReducer from 'utils/createReducer';
|
import createReducer from 'utils/createReducer';
|
||||||
import { getSelectedTab, updateSelection } from './tab';
|
import { getSelectedTab, updateSelection } from './tab';
|
||||||
import * as actions from './actions';
|
import * as actions from './actions';
|
||||||
|
|
||||||
const Status = Record({
|
|
||||||
connected: false,
|
|
||||||
error: null
|
|
||||||
});
|
|
||||||
|
|
||||||
const Server = Record({
|
|
||||||
nick: '',
|
|
||||||
editedNick: null,
|
|
||||||
name: '',
|
|
||||||
status: new Status()
|
|
||||||
});
|
|
||||||
|
|
||||||
export const getServers = state => state.servers;
|
export const getServers = state => state.servers;
|
||||||
|
|
||||||
export const getCurrentNick = createSelector(
|
export const getCurrentNick = createSelector(
|
||||||
getServers,
|
getServers,
|
||||||
getSelectedTab,
|
getSelectedTab,
|
||||||
(servers, tab) => {
|
(servers, tab) => {
|
||||||
const editedNick = servers.getIn([tab.server, 'editedNick']);
|
if (!servers[tab.server]) {
|
||||||
if (editedNick === null) {
|
return;
|
||||||
return servers.getIn([tab.server, 'nick']);
|
}
|
||||||
|
const { editedNick } = servers[tab.server];
|
||||||
|
if (!editedNick) {
|
||||||
|
return servers[tab.server].nick;
|
||||||
}
|
}
|
||||||
return editedNick;
|
return editedNick;
|
||||||
}
|
}
|
||||||
@ -33,80 +24,77 @@ export const getCurrentNick = createSelector(
|
|||||||
export const getCurrentServerName = createSelector(
|
export const getCurrentServerName = createSelector(
|
||||||
getServers,
|
getServers,
|
||||||
getSelectedTab,
|
getSelectedTab,
|
||||||
(servers, tab) => servers.getIn([tab.server, 'name'])
|
(servers, tab) => get(servers, [tab.server, 'name'])
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getCurrentServerStatus = createSelector(
|
export const getCurrentServerStatus = createSelector(
|
||||||
getServers,
|
getServers,
|
||||||
getSelectedTab,
|
getSelectedTab,
|
||||||
(servers, tab) => servers.getIn([tab.server, 'status'])
|
(servers, tab) => get(servers, [tab.server, 'status'], {})
|
||||||
);
|
);
|
||||||
|
|
||||||
export default createReducer(Map(), {
|
export default createReducer(
|
||||||
[actions.CONNECT](state, { host, nick, options }) {
|
{},
|
||||||
if (!state.has(host)) {
|
{
|
||||||
return state.set(
|
[actions.CONNECT](state, { host, nick, options }) {
|
||||||
host,
|
if (!state[host]) {
|
||||||
new Server({
|
state[host] = {
|
||||||
nick,
|
nick,
|
||||||
name: options.name || host
|
editedNick: null,
|
||||||
})
|
name: options.name || host,
|
||||||
);
|
status: {
|
||||||
}
|
connected: false,
|
||||||
|
error: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return state;
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.DISCONNECT](state, { server }) {
|
|
||||||
return state.delete(server);
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.SET_SERVER_NAME](state, { server, name }) {
|
|
||||||
return state.setIn([server, 'name'], name);
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.SET_NICK](state, { server, nick, editing }) {
|
|
||||||
if (editing) {
|
|
||||||
return state.setIn([server, 'editedNick'], nick);
|
|
||||||
} else if (nick === '') {
|
|
||||||
return state.setIn([server, 'editedNick'], null);
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.socket.NICK](state, { server, oldNick, newNick }) {
|
|
||||||
if (!oldNick || oldNick === state.get(server).nick) {
|
|
||||||
return state.update(server, s =>
|
|
||||||
s.set('nick', newNick).set('editedNick', null)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.socket.NICK_FAIL](state, { server }) {
|
|
||||||
return state.setIn([server, 'editedNick'], null);
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.socket.SERVERS](state, { data }) {
|
|
||||||
if (!data) {
|
|
||||||
return state;
|
return state;
|
||||||
}
|
},
|
||||||
|
|
||||||
return state.withMutations(s => {
|
[actions.DISCONNECT](state, { server }) {
|
||||||
data.forEach(server => {
|
delete state[server];
|
||||||
server.status = new Status(server.status);
|
},
|
||||||
s.set(server.host, new Server(server));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.socket.CONNECTION_UPDATE](state, action) {
|
[actions.SET_SERVER_NAME](state, { server, name }) {
|
||||||
if (state.has(action.server)) {
|
state[server].name = name;
|
||||||
return state.setIn([action.server, 'status'], new Status(action));
|
},
|
||||||
|
|
||||||
|
[actions.SET_NICK](state, { server, nick, editing }) {
|
||||||
|
if (editing) {
|
||||||
|
state[server].editedNick = nick;
|
||||||
|
} else if (nick === '') {
|
||||||
|
state[server].editedNick = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
[actions.socket.NICK](state, { server, oldNick, newNick }) {
|
||||||
|
if (!oldNick || oldNick === state[server].nick) {
|
||||||
|
state[server].nick = newNick;
|
||||||
|
state[server].editedNick = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
[actions.socket.NICK_FAIL](state, { server }) {
|
||||||
|
state[server].editedNick = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
[actions.socket.SERVERS](state, { data }) {
|
||||||
|
if (data) {
|
||||||
|
data.forEach(({ host, name, nick, status }) => {
|
||||||
|
state[host] = { name, nick, status };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
[actions.socket.CONNECTION_UPDATE](state, { server, connected, error }) {
|
||||||
|
if (state[server]) {
|
||||||
|
state[server].status.connected = connected;
|
||||||
|
state[server].status.error = error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return state;
|
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
export function connect(server, nick, options) {
|
export function connect(server, nick, options) {
|
||||||
let host = server;
|
let host = server;
|
||||||
|
@ -1,47 +1,45 @@
|
|||||||
import { Map } from 'immutable';
|
|
||||||
import base64 from 'base64-arraybuffer';
|
import base64 from 'base64-arraybuffer';
|
||||||
import createReducer from 'utils/createReducer';
|
import createReducer from 'utils/createReducer';
|
||||||
import * as actions from './actions';
|
import * as actions from './actions';
|
||||||
|
|
||||||
export const getSettings = state => state.settings;
|
export const getSettings = state => state.settings;
|
||||||
|
|
||||||
export default createReducer(Map(), {
|
export default createReducer(
|
||||||
[actions.UPLOAD_CERT](state) {
|
{},
|
||||||
return state.set('uploadingCert', true);
|
{
|
||||||
},
|
[actions.UPLOAD_CERT](state) {
|
||||||
|
state.uploadingCert = true;
|
||||||
|
},
|
||||||
|
|
||||||
[actions.socket.CERT_SUCCESS]() {
|
[actions.socket.CERT_SUCCESS](state) {
|
||||||
return Map({ uploadingCert: false });
|
state.uploadingCert = false;
|
||||||
},
|
delete state.certFile;
|
||||||
|
delete state.cert;
|
||||||
|
delete state.keyFile;
|
||||||
|
delete state.key;
|
||||||
|
},
|
||||||
|
|
||||||
[actions.socket.CERT_FAIL](state, action) {
|
[actions.socket.CERT_FAIL](state, action) {
|
||||||
return state.merge({
|
state.uploadingCert = false;
|
||||||
uploadingCert: false,
|
state.certError = action.message;
|
||||||
certError: action.message
|
},
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.SET_CERT_ERROR](state, action) {
|
[actions.SET_CERT_ERROR](state, action) {
|
||||||
return state.merge({
|
state.uploadingCert = false;
|
||||||
uploadingCert: false,
|
state.certError = action.message;
|
||||||
certError: action.message
|
},
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.SET_CERT](state, action) {
|
[actions.SET_CERT](state, action) {
|
||||||
return state.merge({
|
state.certFile = action.fileName;
|
||||||
certFile: action.fileName,
|
state.cert = action.cert;
|
||||||
cert: action.cert
|
},
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
[actions.SET_KEY](state, action) {
|
[actions.SET_KEY](state, action) {
|
||||||
return state.merge({
|
state.keyFile = action.fileName;
|
||||||
keyFile: action.fileName,
|
state.key = action.key;
|
||||||
key: action.key
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
export function setCertError(message) {
|
export function setCertError(message) {
|
||||||
return {
|
return {
|
||||||
@ -53,14 +51,14 @@ export function setCertError(message) {
|
|||||||
export function uploadCert() {
|
export function uploadCert() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const { settings } = getState();
|
const { settings } = getState();
|
||||||
if (settings.has('cert') && settings.has('key')) {
|
if (settings.cert && settings.key) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: actions.UPLOAD_CERT,
|
type: actions.UPLOAD_CERT,
|
||||||
socket: {
|
socket: {
|
||||||
type: 'cert',
|
type: 'cert',
|
||||||
data: {
|
data: {
|
||||||
cert: settings.get('cert'),
|
cert: settings.cert,
|
||||||
key: settings.get('key')
|
key: settings.key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,77 +1,48 @@
|
|||||||
import { Record, List } from 'immutable';
|
|
||||||
import createReducer from 'utils/createReducer';
|
import createReducer from 'utils/createReducer';
|
||||||
import { push, replace, LOCATION_CHANGED } from 'utils/router';
|
import { push, replace, LOCATION_CHANGED } from 'utils/router';
|
||||||
import * as actions from './actions';
|
import * as actions from './actions';
|
||||||
|
|
||||||
const TabRecord = Record({
|
const initialState = {
|
||||||
server: null,
|
selected: {},
|
||||||
name: null
|
history: []
|
||||||
});
|
};
|
||||||
|
|
||||||
class Tab extends TabRecord {
|
|
||||||
isChannel() {
|
|
||||||
return this.name && this.name.charAt(0) === '#';
|
|
||||||
}
|
|
||||||
|
|
||||||
toString() {
|
|
||||||
let str = this.server;
|
|
||||||
if (this.name) {
|
|
||||||
str += `;${this.name}`;
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const State = Record({
|
|
||||||
selected: new Tab(),
|
|
||||||
history: List()
|
|
||||||
});
|
|
||||||
|
|
||||||
function selectTab(state, action) {
|
function selectTab(state, action) {
|
||||||
const tab = new Tab(action);
|
state.selected = {
|
||||||
return state
|
server: action.server,
|
||||||
.set('selected', tab)
|
name: action.name
|
||||||
.update('history', history => history.push(tab));
|
};
|
||||||
|
state.history.push(state.selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getSelectedTab = state => state.tab.selected;
|
export const getSelectedTab = state => state.tab.selected;
|
||||||
|
|
||||||
export default createReducer(new State(), {
|
export default createReducer(initialState, {
|
||||||
[actions.SELECT_TAB]: selectTab,
|
[actions.SELECT_TAB]: selectTab,
|
||||||
|
|
||||||
[actions.PART](state, action) {
|
[actions.PART](state, action) {
|
||||||
return state.set(
|
state.history = state.history.filter(
|
||||||
'history',
|
tab => !(tab.server === action.server && tab.name === action.channels[0])
|
||||||
state.history.filter(
|
|
||||||
tab =>
|
|
||||||
!(tab.server === action.server && tab.name === action.channels[0])
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
[actions.CLOSE_PRIVATE_CHAT](state, action) {
|
[actions.CLOSE_PRIVATE_CHAT](state, action) {
|
||||||
return state.set(
|
state.history = state.history.filter(
|
||||||
'history',
|
tab => !(tab.server === action.server && tab.name === action.nick)
|
||||||
state.history.filter(
|
|
||||||
tab => !(tab.server === action.server && tab.name === action.nick)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
[actions.DISCONNECT](state, action) {
|
[actions.DISCONNECT](state, action) {
|
||||||
return state.set(
|
state.history = state.history.filter(tab => tab.server !== action.server);
|
||||||
'history',
|
|
||||||
state.history.filter(tab => tab.server !== action.server)
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
[LOCATION_CHANGED](state, action) {
|
[LOCATION_CHANGED](state, action) {
|
||||||
const { route, params } = action;
|
const { route, params } = action;
|
||||||
if (route === 'chat') {
|
if (route === 'chat') {
|
||||||
return selectTab(state, params);
|
selectTab(state, params);
|
||||||
|
} else {
|
||||||
|
state.selected = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.set('selected', new Tab());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -89,16 +60,17 @@ export function updateSelection() {
|
|||||||
const { history } = state.tab;
|
const { history } = state.tab;
|
||||||
const { servers } = state;
|
const { servers } = state;
|
||||||
const { server } = state.tab.selected;
|
const { server } = state.tab.selected;
|
||||||
|
const serverAddrs = Object.keys(servers);
|
||||||
|
|
||||||
if (servers.size === 0) {
|
if (serverAddrs.length === 0) {
|
||||||
dispatch(replace('/connect'));
|
dispatch(replace('/connect'));
|
||||||
} else if (history.size > 0) {
|
} else if (history.length > 0) {
|
||||||
const tab = history.last();
|
const tab = history[history.length - 1];
|
||||||
dispatch(select(tab.server, tab.name, true));
|
dispatch(select(tab.server, tab.name, true));
|
||||||
} else if (servers.has(server)) {
|
} else if (servers[server]) {
|
||||||
dispatch(select(server, null, true));
|
dispatch(select(server, null, true));
|
||||||
} else {
|
} else {
|
||||||
dispatch(select(servers.keySeq().first(), null, true));
|
dispatch(select(serverAddrs.sort()[0], null, true));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,29 @@
|
|||||||
import { Record } from 'immutable';
|
|
||||||
import createReducer from 'utils/createReducer';
|
import createReducer from 'utils/createReducer';
|
||||||
import { LOCATION_CHANGED } from 'utils/router';
|
import { LOCATION_CHANGED } from 'utils/router';
|
||||||
import * as actions from './actions';
|
import * as actions from './actions';
|
||||||
|
|
||||||
const State = Record({
|
const initialState = {
|
||||||
showTabList: false,
|
showTabList: false,
|
||||||
showUserList: false
|
showUserList: false
|
||||||
});
|
};
|
||||||
|
|
||||||
export const getShowTabList = state => state.ui.showTabList;
|
export const getShowTabList = state => state.ui.showTabList;
|
||||||
export const getShowUserList = state => state.ui.showUserList;
|
export const getShowUserList = state => state.ui.showUserList;
|
||||||
|
|
||||||
function setMenuHidden(state) {
|
function setMenuHidden(state) {
|
||||||
return state.set('showTabList', false);
|
state.showTabList = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default createReducer(new State(), {
|
export default createReducer(initialState, {
|
||||||
[actions.TOGGLE_MENU](state) {
|
[actions.TOGGLE_MENU](state) {
|
||||||
return state.update('showTabList', show => !show);
|
state.showTabList = !state.showTabList;
|
||||||
},
|
},
|
||||||
|
|
||||||
[actions.HIDE_MENU]: setMenuHidden,
|
[actions.HIDE_MENU]: setMenuHidden,
|
||||||
[LOCATION_CHANGED]: setMenuHidden,
|
[LOCATION_CHANGED]: setMenuHidden,
|
||||||
|
|
||||||
[actions.TOGGLE_USERLIST](state) {
|
[actions.TOGGLE_USERLIST](state) {
|
||||||
return state.update('showUserList', show => !show);
|
state.showUserList = !state.showUserList;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,6 +1,19 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { isChannel } from '..';
|
||||||
import linkify from '../linkify';
|
import linkify from '../linkify';
|
||||||
|
|
||||||
|
describe('isChannel()', () => {
|
||||||
|
it('it handles strings', () => {
|
||||||
|
expect(isChannel('#cake')).toBe(true);
|
||||||
|
expect(isChannel('cake')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles tab objects', () => {
|
||||||
|
expect(isChannel({ name: '#cake' })).toBe(true);
|
||||||
|
expect(isChannel({ name: 'cake' })).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('linkify()', () => {
|
describe('linkify()', () => {
|
||||||
const proto = href => (href.indexOf('http') !== 0 ? `http://${href}` : href);
|
const proto = href => (href.indexOf('http') !== 0 ? `http://${href}` : href);
|
||||||
const linkTo = href => (
|
const linkTo = href => (
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
|
import produce from 'immer';
|
||||||
|
import has from 'lodash/has';
|
||||||
|
|
||||||
export default function createReducer(initialState, handlers) {
|
export default function createReducer(initialState, handlers) {
|
||||||
return function reducer(state = initialState, action) {
|
return function reducer(state = initialState, action) {
|
||||||
if (Object.prototype.hasOwnProperty.call(handlers, action.type)) {
|
if (has(handlers, action.type)) {
|
||||||
return handlers[action.type](state, action);
|
return produce(state, draft => handlers[action.type](draft, action));
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
@ -16,9 +16,25 @@ export function normalizeChannel(channel) {
|
|||||||
|
|
||||||
export function isChannel(name) {
|
export function isChannel(name) {
|
||||||
// TODO: Handle other channel types
|
// TODO: Handle other channel types
|
||||||
|
if (typeof name === 'object') {
|
||||||
|
({ name } = name);
|
||||||
|
}
|
||||||
return typeof name === 'string' && name[0] === '#';
|
return typeof name === 'string' && name[0] === '#';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function stringifyTab(server, name) {
|
||||||
|
if (typeof server === 'object') {
|
||||||
|
if (server.name) {
|
||||||
|
return `${server.server};${server.name}`;
|
||||||
|
}
|
||||||
|
return server.server;
|
||||||
|
}
|
||||||
|
if (name) {
|
||||||
|
return `${server};${name}`;
|
||||||
|
}
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
export function timestamp(date = new Date()) {
|
export function timestamp(date = new Date()) {
|
||||||
const h = padStart(date.getHours(), 2, '0');
|
const h = padStart(date.getHours(), 2, '0');
|
||||||
const m = padStart(date.getMinutes(), 2, '0');
|
const m = padStart(date.getMinutes(), 2, '0');
|
||||||
@ -54,14 +70,24 @@ export function measureScrollBarWidth() {
|
|||||||
return widthNoScroll - widthWithScroll;
|
return widthNoScroll - widthWithScroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function find(arr, pred) {
|
export function findIndex(arr, pred) {
|
||||||
if (!arr) {
|
if (!arr) {
|
||||||
return null;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < arr.length; i++) {
|
for (let i = 0; i < arr.length; i++) {
|
||||||
if (pred(arr[i])) {
|
if (pred(arr[i])) {
|
||||||
return arr[i];
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function find(arr, pred) {
|
||||||
|
const i = findIndex(arr, pred);
|
||||||
|
if (i !== -1) {
|
||||||
|
return arr[i];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
1140
client/yarn.lock
1140
client/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user