Handle kick, rename server to network
This commit is contained in:
parent
a33157ff84
commit
6985dd16da
65 changed files with 2650 additions and 2179 deletions
|
@ -1,8 +1,8 @@
|
|||
import { COMMAND } from 'state/actions';
|
||||
import { join, part, invite, kick, setTopic } from 'state/channels';
|
||||
import { sendMessage, raw } from 'state/messages';
|
||||
import { setNick, disconnect, whois, away } from 'state/networks';
|
||||
import { openPrivateChat } from 'state/privateChats';
|
||||
import { setNick, disconnect, whois, away } from 'state/servers';
|
||||
import { select } from 'state/tab';
|
||||
import { find, isChannel } from 'utils';
|
||||
import createCommandMiddleware, {
|
||||
|
@ -14,7 +14,7 @@ const help = [
|
|||
'/join <channel> - Join a channel',
|
||||
'/part [channel] - Leave the current or specified channel',
|
||||
'/nick <nick> - Change nick',
|
||||
'/quit - Disconnect from the current server',
|
||||
'/quit - Disconnect from the current network',
|
||||
'/me <message> - Send action message',
|
||||
'/topic [topic] - Show or set topic in the current channel',
|
||||
'/msg <target> <message> - Send message to the specified channel or user',
|
||||
|
@ -23,7 +23,7 @@ const help = [
|
|||
'/kick <nick> - Kick user from the current channel',
|
||||
'/whois <nick> - Get information about user',
|
||||
'/away [message] - Set or clear away message',
|
||||
'/raw [message] - Send raw IRC message to the current server',
|
||||
'/raw [message] - Send raw IRC message to the current network',
|
||||
'/help [command]... - Print help for all or the specified command(s)'
|
||||
];
|
||||
|
||||
|
@ -34,56 +34,56 @@ const findHelp = cmd =>
|
|||
find(help, line => line.slice(1, line.indexOf(' ')) === cmd);
|
||||
|
||||
export default createCommandMiddleware(COMMAND, {
|
||||
join({ dispatch, server }, channel) {
|
||||
join({ dispatch, network }, channel) {
|
||||
if (channel) {
|
||||
if (channel[0] !== '#') {
|
||||
return error('Bad channel name');
|
||||
}
|
||||
dispatch(join([channel], server));
|
||||
dispatch(select(server, channel));
|
||||
dispatch(join([channel], network));
|
||||
dispatch(select(network, channel));
|
||||
} else {
|
||||
return error('Missing channel');
|
||||
}
|
||||
},
|
||||
|
||||
part({ dispatch, server, channel, inChannel }, partChannel) {
|
||||
part({ dispatch, network, channel, inChannel }, partChannel) {
|
||||
if (partChannel) {
|
||||
dispatch(part([partChannel], server));
|
||||
dispatch(part([partChannel], network));
|
||||
} else if (inChannel) {
|
||||
dispatch(part([channel], server));
|
||||
dispatch(part([channel], network));
|
||||
} else {
|
||||
return error('This is not a channel');
|
||||
}
|
||||
},
|
||||
|
||||
nick({ dispatch, server }, nick) {
|
||||
nick({ dispatch, network }, nick) {
|
||||
if (nick) {
|
||||
dispatch(setNick(nick, server));
|
||||
dispatch(setNick(nick, network));
|
||||
} else {
|
||||
return error('Missing nick');
|
||||
}
|
||||
},
|
||||
|
||||
quit({ dispatch, server }) {
|
||||
dispatch(disconnect(server));
|
||||
quit({ dispatch, network }) {
|
||||
dispatch(disconnect(network));
|
||||
},
|
||||
|
||||
me({ dispatch, server, channel }, ...message) {
|
||||
me({ dispatch, network, channel }, ...message) {
|
||||
const msg = message.join(' ');
|
||||
if (msg !== '') {
|
||||
dispatch(sendMessage(`\x01ACTION ${msg}\x01`, channel, server));
|
||||
dispatch(sendMessage(`\x01ACTION ${msg}\x01`, channel, network));
|
||||
} else {
|
||||
return error('Messages can not be empty');
|
||||
}
|
||||
},
|
||||
|
||||
topic({ dispatch, getState, server, channel }, ...newTopic) {
|
||||
topic({ dispatch, getState, network, channel }, ...newTopic) {
|
||||
if (newTopic.length > 0) {
|
||||
dispatch(setTopic(newTopic.join(' '), channel, server));
|
||||
dispatch(setTopic(newTopic.join(' '), channel, network));
|
||||
return;
|
||||
}
|
||||
if (channel) {
|
||||
const { topic } = getState().channels[server][channel];
|
||||
const { topic } = getState().channels[network][channel];
|
||||
if (topic) {
|
||||
return text(topic);
|
||||
}
|
||||
|
@ -91,83 +91,83 @@ export default createCommandMiddleware(COMMAND, {
|
|||
return 'No topic set';
|
||||
},
|
||||
|
||||
msg({ dispatch, server }, target, ...message) {
|
||||
msg({ dispatch, network }, target, ...message) {
|
||||
if (!target) {
|
||||
return error('Missing nick/channel');
|
||||
}
|
||||
|
||||
const msg = message.join(' ');
|
||||
if (msg !== '') {
|
||||
dispatch(sendMessage(message.join(' '), target, server));
|
||||
dispatch(sendMessage(message.join(' '), target, network));
|
||||
if (!isChannel(target)) {
|
||||
dispatch(openPrivateChat(server, target));
|
||||
dispatch(openPrivateChat(network, target));
|
||||
}
|
||||
dispatch(select(server, target));
|
||||
dispatch(select(network, target));
|
||||
} else {
|
||||
return error('Messages can not be empty');
|
||||
}
|
||||
},
|
||||
|
||||
say({ dispatch, server, channel }, ...message) {
|
||||
say({ dispatch, network, channel }, ...message) {
|
||||
if (!channel) {
|
||||
return error('Messages can only be sent to channels or users');
|
||||
}
|
||||
|
||||
const msg = message.join(' ');
|
||||
if (msg !== '') {
|
||||
dispatch(sendMessage(message.join(' '), channel, server));
|
||||
dispatch(sendMessage(message.join(' '), channel, network));
|
||||
} else {
|
||||
return error('Messages can not be empty');
|
||||
}
|
||||
},
|
||||
|
||||
invite({ dispatch, server, channel, inChannel }, user, inviteChannel) {
|
||||
invite({ dispatch, network, channel, inChannel }, user, inviteChannel) {
|
||||
if (!inviteChannel && !inChannel) {
|
||||
return error('This is not a channel');
|
||||
}
|
||||
|
||||
if (user && inviteChannel) {
|
||||
dispatch(invite(user, inviteChannel, server));
|
||||
dispatch(invite(user, inviteChannel, network));
|
||||
} else if (user && channel) {
|
||||
dispatch(invite(user, channel, server));
|
||||
dispatch(invite(user, channel, network));
|
||||
} else {
|
||||
return error('Missing nick');
|
||||
}
|
||||
},
|
||||
|
||||
kick({ dispatch, server, channel, inChannel }, user) {
|
||||
kick({ dispatch, network, channel, inChannel }, user) {
|
||||
if (!inChannel) {
|
||||
return error('This is not a channel');
|
||||
}
|
||||
|
||||
if (user) {
|
||||
dispatch(kick(user, channel, server));
|
||||
dispatch(kick(user, channel, network));
|
||||
} else {
|
||||
return error('Missing nick');
|
||||
}
|
||||
},
|
||||
|
||||
whois({ dispatch, server }, user) {
|
||||
whois({ dispatch, network }, user) {
|
||||
if (user) {
|
||||
dispatch(whois(user, server));
|
||||
dispatch(whois(user, network));
|
||||
} else {
|
||||
return error('Missing nick');
|
||||
}
|
||||
},
|
||||
|
||||
away({ dispatch, server }, ...message) {
|
||||
away({ dispatch, network }, ...message) {
|
||||
const msg = message.join(' ');
|
||||
dispatch(away(msg, server));
|
||||
dispatch(away(msg, network));
|
||||
if (msg !== '') {
|
||||
return 'Away message set';
|
||||
}
|
||||
return 'Away message cleared';
|
||||
},
|
||||
|
||||
raw({ dispatch, server }, ...message) {
|
||||
raw({ dispatch, network }, ...message) {
|
||||
if (message.length > 0 && message[0] !== '') {
|
||||
const cmd = `${message[0].toUpperCase()} ${message.slice(1).join(' ')}`;
|
||||
dispatch(raw(cmd, server));
|
||||
dispatch(raw(cmd, network));
|
||||
return prompt(`=> ${cmd}`);
|
||||
}
|
||||
return [prompt('=> /raw'), error('Missing message')];
|
||||
|
|
|
@ -15,7 +15,7 @@ const App = ({
|
|||
connected,
|
||||
tab,
|
||||
channels,
|
||||
servers,
|
||||
networks,
|
||||
privateChats,
|
||||
showTabList,
|
||||
select,
|
||||
|
@ -62,7 +62,7 @@ const App = ({
|
|||
<TabList
|
||||
tab={tab}
|
||||
channels={channels}
|
||||
servers={servers}
|
||||
networks={networks}
|
||||
privateChats={privateChats}
|
||||
showTabList={showTabList}
|
||||
select={select}
|
||||
|
|
|
@ -7,7 +7,7 @@ import TabListItem from 'containers/TabListItem';
|
|||
import { count } from 'utils';
|
||||
|
||||
export default class TabList extends PureComponent {
|
||||
handleTabClick = (server, target) => this.props.select(server, target);
|
||||
handleTabClick = (network, target) => this.props.select(network, target);
|
||||
|
||||
handleConnectClick = () => this.props.push('/connect');
|
||||
|
||||
|
@ -17,7 +17,7 @@ export default class TabList extends PureComponent {
|
|||
const {
|
||||
tab,
|
||||
channels,
|
||||
servers,
|
||||
networks,
|
||||
privateChats,
|
||||
showTabList,
|
||||
openModal
|
||||
|
@ -28,21 +28,21 @@ export default class TabList extends PureComponent {
|
|||
'off-canvas': showTabList
|
||||
});
|
||||
|
||||
channels.forEach(server => {
|
||||
const { address } = server;
|
||||
const srv = servers[address];
|
||||
channels.forEach(network => {
|
||||
const { address } = network;
|
||||
const srv = networks[address];
|
||||
tabs.push(
|
||||
<TabListItem
|
||||
key={address}
|
||||
server={address}
|
||||
network={address}
|
||||
content={srv.name}
|
||||
selected={tab.server === address && !tab.name}
|
||||
connected={srv.status.connected}
|
||||
selected={tab.network === address && !tab.name}
|
||||
connected={srv.connected}
|
||||
onClick={this.handleTabClick}
|
||||
/>
|
||||
);
|
||||
|
||||
const chanCount = count(server.channels, c => c.joined);
|
||||
const chanCount = count(network.channels, c => c.joined);
|
||||
const chanLimit =
|
||||
get(srv.features, ['CHANLIMIT', '#'], 0) || srv.features.MAXCHANNELS;
|
||||
|
||||
|
@ -68,15 +68,15 @@ export default class TabList extends PureComponent {
|
|||
</div>
|
||||
);
|
||||
|
||||
server.channels.forEach(({ name, joined }) =>
|
||||
network.channels.forEach(({ name, joined }) =>
|
||||
tabs.push(
|
||||
<TabListItem
|
||||
key={address + name}
|
||||
server={address}
|
||||
network={address}
|
||||
target={name}
|
||||
content={name}
|
||||
joined={joined}
|
||||
selected={tab.server === address && tab.name === name}
|
||||
selected={tab.network === address && tab.name === name}
|
||||
onClick={this.handleTabClick}
|
||||
/>
|
||||
)
|
||||
|
@ -99,10 +99,10 @@ export default class TabList extends PureComponent {
|
|||
tabs.push(
|
||||
<TabListItem
|
||||
key={address + nick}
|
||||
server={address}
|
||||
network={address}
|
||||
target={nick}
|
||||
content={nick}
|
||||
selected={tab.server === address && tab.name === nick}
|
||||
selected={tab.network === address && tab.name === nick}
|
||||
onClick={this.handleTabClick}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -4,7 +4,7 @@ import classnames from 'classnames';
|
|||
const TabListItem = ({
|
||||
target,
|
||||
content,
|
||||
server,
|
||||
network,
|
||||
selected,
|
||||
connected,
|
||||
joined,
|
||||
|
@ -12,7 +12,7 @@ const TabListItem = ({
|
|||
onClick
|
||||
}) => {
|
||||
const className = classnames({
|
||||
'tab-server': !target,
|
||||
'tab-network': !target,
|
||||
success: !target && connected,
|
||||
error: (!target && !connected) || (!joined && error),
|
||||
disabled: !!target && !error && joined === false,
|
||||
|
@ -20,7 +20,7 @@ const TabListItem = ({
|
|||
});
|
||||
|
||||
return (
|
||||
<p className={className} onClick={() => onClick(server, target)}>
|
||||
<p className={className} onClick={() => onClick(network, target)}>
|
||||
<span className="tab-content">{content}</span>
|
||||
</p>
|
||||
);
|
||||
|
|
|
@ -9,10 +9,10 @@ import { select } from 'state/tab';
|
|||
import { searchChannels } from 'state/channelSearch';
|
||||
import { linkify } from 'utils';
|
||||
|
||||
const Channel = memo(({ server, name, topic, userCount, joined }) => {
|
||||
const Channel = memo(({ network, name, topic, userCount, joined }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleClick = () => dispatch(join([name], server));
|
||||
const handleClick = () => dispatch(join([name], network));
|
||||
|
||||
return (
|
||||
<div className="modal-channel-result">
|
||||
|
@ -40,7 +40,7 @@ const Channel = memo(({ server, name, topic, userCount, joined }) => {
|
|||
});
|
||||
|
||||
const AddChannel = () => {
|
||||
const [modal, server, closeModal] = useModal('channel');
|
||||
const [modal, network, closeModal] = useModal('channel');
|
||||
|
||||
const channels = useSelector(state => state.channels);
|
||||
const search = useSelector(state => state.channelSearch);
|
||||
|
@ -53,7 +53,7 @@ const AddChannel = () => {
|
|||
|
||||
useEffect(() => {
|
||||
if (modal.isOpen) {
|
||||
dispatch(searchChannels(server, ''));
|
||||
dispatch(searchChannels(network, ''));
|
||||
setTimeout(() => inputEl.current.focus(), 0);
|
||||
} else {
|
||||
prevSearch.current = '';
|
||||
|
@ -74,7 +74,7 @@ const AddChannel = () => {
|
|||
|
||||
if (nextQ !== prevSearch.current) {
|
||||
prevSearch.current = nextQ;
|
||||
dispatch(searchChannels(server, nextQ));
|
||||
dispatch(searchChannels(network, nextQ));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -90,14 +90,14 @@ const AddChannel = () => {
|
|||
channel = `#${channel}`;
|
||||
}
|
||||
|
||||
dispatch(join([channel], server));
|
||||
dispatch(select(server, channel));
|
||||
dispatch(join([channel], network));
|
||||
dispatch(select(network, channel));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoadMore = () =>
|
||||
dispatch(searchChannels(server, q, search.results.length));
|
||||
dispatch(searchChannels(network, q, search.results.length));
|
||||
|
||||
let hasMore = !search.end;
|
||||
if (hasMore) {
|
||||
|
@ -131,9 +131,9 @@ const AddChannel = () => {
|
|||
<div ref={resultsEl} className="modal-channel-results">
|
||||
{search.results.map(channel => (
|
||||
<Channel
|
||||
key={`${server} ${channel.name}`}
|
||||
server={server}
|
||||
joined={channels[server]?.[channel.name]?.joined}
|
||||
key={`${network} ${channel.name}`}
|
||||
network={network}
|
||||
joined={channels[network]?.[channel.name]?.joined}
|
||||
{...channel}
|
||||
/>
|
||||
))}
|
||||
|
|
|
@ -11,40 +11,40 @@ export default class Chat extends Component {
|
|||
const { tab, part, closePrivateChat, disconnect } = this.props;
|
||||
|
||||
if (isChannel(tab)) {
|
||||
part([tab.name], tab.server);
|
||||
part([tab.name], tab.network);
|
||||
} else if (tab.name) {
|
||||
closePrivateChat(tab.server, tab.name);
|
||||
closePrivateChat(tab.network, tab.name);
|
||||
} else {
|
||||
disconnect(tab.server);
|
||||
disconnect(tab.network);
|
||||
}
|
||||
};
|
||||
|
||||
handleSearch = phrase => {
|
||||
const { tab, searchMessages } = this.props;
|
||||
if (isChannel(tab)) {
|
||||
searchMessages(tab.server, tab.name, phrase);
|
||||
searchMessages(tab.network, tab.name, phrase);
|
||||
}
|
||||
};
|
||||
|
||||
handleNickClick = nick => {
|
||||
const { tab, openPrivateChat, select } = this.props;
|
||||
openPrivateChat(tab.server, nick);
|
||||
select(tab.server, nick);
|
||||
openPrivateChat(tab.network, nick);
|
||||
select(tab.network, nick);
|
||||
};
|
||||
|
||||
handleTitleChange = title => {
|
||||
const { setServerName, tab } = this.props;
|
||||
setServerName(title, tab.server);
|
||||
const { setNetworkName, tab } = this.props;
|
||||
setNetworkName(title, tab.network);
|
||||
};
|
||||
|
||||
handleNickChange = nick => {
|
||||
const { setNick, tab } = this.props;
|
||||
setNick(nick, tab.server, true);
|
||||
setNick(nick, tab.network, true);
|
||||
};
|
||||
|
||||
handleNickEditDone = nick => {
|
||||
const { setNick, tab } = this.props;
|
||||
setNick(nick, tab.server);
|
||||
setNick(nick, tab.network);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -57,7 +57,7 @@ export default class Chat extends Component {
|
|||
nick,
|
||||
search,
|
||||
showUserList,
|
||||
status,
|
||||
error,
|
||||
tab,
|
||||
title,
|
||||
users,
|
||||
|
@ -77,14 +77,14 @@ export default class Chat extends Component {
|
|||
} else if (tab.name) {
|
||||
chatClass = 'chat-private';
|
||||
} else {
|
||||
chatClass = 'chat-server';
|
||||
chatClass = 'chat-network';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={chatClass}>
|
||||
<ChatTitle
|
||||
channel={channel}
|
||||
status={status}
|
||||
error={error}
|
||||
tab={tab}
|
||||
title={title}
|
||||
openModal={openModal}
|
||||
|
|
|
@ -3,11 +3,11 @@ import { FiUsers, FiSearch, FiX } from 'react-icons/fi';
|
|||
import Navicon from 'components/ui/Navicon';
|
||||
import Button from 'components/ui/Button';
|
||||
import Editable from 'components/ui/Editable';
|
||||
import { isValidServerName } from 'state/servers';
|
||||
import { isValidNetworkName } from 'state/networks';
|
||||
import { isChannel } from 'utils';
|
||||
|
||||
const ChatTitle = ({
|
||||
status,
|
||||
error,
|
||||
title,
|
||||
tab,
|
||||
channel,
|
||||
|
@ -26,11 +26,9 @@ const ChatTitle = ({
|
|||
closeTitle = 'Disconnect';
|
||||
}
|
||||
|
||||
let serverError = null;
|
||||
if (!tab.name && status.error) {
|
||||
serverError = (
|
||||
<span className="chat-topic error">Error: {status.error}</span>
|
||||
);
|
||||
let networkError = null;
|
||||
if (!tab.name && error) {
|
||||
networkError = <span className="chat-topic error">Error: {error}</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -41,13 +39,13 @@ const ChatTitle = ({
|
|||
className="chat-title"
|
||||
editable={!tab.name}
|
||||
value={title}
|
||||
validate={isValidServerName}
|
||||
validate={isValidNetworkName}
|
||||
onChange={onTitleChange}
|
||||
>
|
||||
<span className="chat-title">{title}</span>
|
||||
</Editable>
|
||||
<div className="chat-topic-wrap">
|
||||
{channel && channel.topic && (
|
||||
{channel?.topic && (
|
||||
<span
|
||||
className="chat-topic"
|
||||
onClick={() => openModal('topic', channel.name)}
|
||||
|
@ -55,7 +53,7 @@ const ChatTitle = ({
|
|||
{channel.topic}
|
||||
</span>
|
||||
)}
|
||||
{serverError}
|
||||
{networkError}
|
||||
</div>
|
||||
{tab.name && (
|
||||
<Button
|
||||
|
@ -80,7 +78,7 @@ const ChatTitle = ({
|
|||
</div>
|
||||
<div className="userlist-bar">
|
||||
<FiUsers />
|
||||
{channel && channel.users.length}
|
||||
{channel?.users.length}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -36,7 +36,7 @@ export default class MessageBox extends PureComponent {
|
|||
addMore = debounce(() => {
|
||||
const { tab, onAddMore } = this.props;
|
||||
this.ready = true;
|
||||
onAddMore(tab.server, tab.name);
|
||||
onAddMore(tab.network, tab.name);
|
||||
}, scrollbackDebounce);
|
||||
|
||||
constructor(props) {
|
||||
|
@ -130,7 +130,7 @@ export default class MessageBox extends PureComponent {
|
|||
|
||||
updateScrollKey = () => {
|
||||
const { tab } = this.props;
|
||||
this.scrollKey = `msg:${tab.server}:${tab.name}`;
|
||||
this.scrollKey = `msg:${tab.network}:${tab.name}`;
|
||||
return this.scrollKey;
|
||||
};
|
||||
|
||||
|
@ -222,7 +222,7 @@ export default class MessageBox extends PureComponent {
|
|||
if (this.shouldAdd) {
|
||||
const { tab, onAddMore } = this.props;
|
||||
this.shouldAdd = false;
|
||||
onAddMore(tab.server, tab.name);
|
||||
onAddMore(tab.network, tab.name);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -21,9 +21,9 @@ const MessageInput = ({
|
|||
const handleKey = e => {
|
||||
if (e.key === 'Enter' && e.target.value) {
|
||||
if (e.target.value[0] === '/') {
|
||||
onCommand(e.target.value, tab.name, tab.server);
|
||||
onCommand(e.target.value, tab.name, tab.network);
|
||||
} else if (tab.name) {
|
||||
onMessage(e.target.value, tab.name, tab.server);
|
||||
onMessage(e.target.value, tab.name, tab.network);
|
||||
}
|
||||
|
||||
add(e.target.value);
|
||||
|
|
|
@ -4,7 +4,7 @@ import { getConnected } from 'state/app';
|
|||
import { getSortedChannels } from 'state/channels';
|
||||
import { openModal, getHasOpenModals } from 'state/modals';
|
||||
import { getPrivateChats } from 'state/privateChats';
|
||||
import { getServers } from 'state/servers';
|
||||
import { getNetworks } from 'state/networks';
|
||||
import { getSelectedTab, select } from 'state/tab';
|
||||
import { getShowTabList, hideMenu } from 'state/ui';
|
||||
import connect from 'utils/connect';
|
||||
|
@ -14,7 +14,7 @@ const mapState = createStructuredSelector({
|
|||
channels: getSortedChannels,
|
||||
connected: getConnected,
|
||||
privateChats: getPrivateChats,
|
||||
servers: getServers,
|
||||
networks: getNetworks,
|
||||
showTabList: getShowTabList,
|
||||
tab: getSelectedTab,
|
||||
newVersionAvailable: state => state.app.newVersionAvailable,
|
||||
|
|
|
@ -27,11 +27,11 @@ import { openPrivateChat, closePrivateChat } from 'state/privateChats';
|
|||
import { getSearch, searchMessages, toggleSearch } from 'state/search';
|
||||
import {
|
||||
getCurrentNick,
|
||||
getCurrentServerStatus,
|
||||
getCurrentNetworkError,
|
||||
disconnect,
|
||||
setNick,
|
||||
setServerName
|
||||
} from 'state/servers';
|
||||
setNetworkName
|
||||
} from 'state/networks';
|
||||
import { getSettings } from 'state/settings';
|
||||
import { getSelectedTab, select } from 'state/tab';
|
||||
import { getShowUserList, toggleUserList } from 'state/ui';
|
||||
|
@ -45,7 +45,7 @@ const mapState = createStructuredSelector({
|
|||
nick: getCurrentNick,
|
||||
search: getSearch,
|
||||
showUserList: getShowUserList,
|
||||
status: getCurrentServerStatus,
|
||||
error: getCurrentNetworkError,
|
||||
tab: getSelectedTab,
|
||||
title: getSelectedTabTitle,
|
||||
users: getSelectedChannelUsers,
|
||||
|
@ -67,7 +67,7 @@ const mapDispatch = dispatch => ({
|
|||
select,
|
||||
sendMessage,
|
||||
setNick,
|
||||
setServerName,
|
||||
setNetworkName,
|
||||
toggleSearch,
|
||||
toggleUserList
|
||||
},
|
||||
|
|
|
@ -2,7 +2,7 @@ import { createStructuredSelector } from 'reselect';
|
|||
import Connect from 'components/pages/Connect';
|
||||
import { getConnectDefaults, getApp } from 'state/app';
|
||||
import { join } from 'state/channels';
|
||||
import { connect as connectServer } from 'state/servers';
|
||||
import { connect as connectNetwork } from 'state/networks';
|
||||
import { select } from 'state/tab';
|
||||
import connect from 'utils/connect';
|
||||
|
||||
|
@ -14,7 +14,7 @@ const mapState = createStructuredSelector({
|
|||
|
||||
const mapDispatch = {
|
||||
join,
|
||||
connect: connectServer,
|
||||
connect: connectNetwork,
|
||||
select
|
||||
};
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@ import TabListItem from 'components/TabListItem';
|
|||
import connect from 'utils/connect';
|
||||
|
||||
const mapState = createStructuredSelector({
|
||||
error: (state, { server, target }) => {
|
||||
const messages = get(state, ['messages', server, target]);
|
||||
error: (state, { network, target }) => {
|
||||
const messages = get(state, ['messages', network, target]);
|
||||
|
||||
if (messages && messages.length > 0) {
|
||||
return messages[messages.length - 1].type === 'error';
|
||||
|
|
|
@ -4,22 +4,28 @@ import { isChannel } from 'utils';
|
|||
export const beforeHandler = '_before';
|
||||
export const notFoundHandler = 'commandNotFound';
|
||||
|
||||
function createContext({ dispatch, getState }, { server, channel }) {
|
||||
return { dispatch, getState, server, channel, inChannel: isChannel(channel) };
|
||||
function createContext({ dispatch, getState }, { network, channel }) {
|
||||
return {
|
||||
dispatch,
|
||||
getState,
|
||||
network,
|
||||
channel,
|
||||
inChannel: isChannel(channel)
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Pull this out as convenience action
|
||||
function process({ dispatch, server, channel }, result) {
|
||||
function process({ dispatch, network, channel }, result) {
|
||||
if (typeof result === 'string') {
|
||||
dispatch(inform(result, server, channel));
|
||||
dispatch(inform(result, network, channel));
|
||||
} else if (Array.isArray(result)) {
|
||||
if (typeof result[0] === 'string') {
|
||||
dispatch(inform(result, server, channel));
|
||||
dispatch(inform(result, network, channel));
|
||||
} else if (typeof result[0] === 'object') {
|
||||
dispatch(addMessages(result, server, channel));
|
||||
dispatch(addMessages(result, network, channel));
|
||||
}
|
||||
} else if (typeof result === 'object' && result) {
|
||||
dispatch(print(result.content, server, channel, result.type));
|
||||
dispatch(print(result.content, network, channel, result.type));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ const message = store => next => {
|
|||
|
||||
return action => {
|
||||
if (action.type === ADD_MESSAGES && action.prepend) {
|
||||
const key = `${action.server} ${action.channel}`;
|
||||
const key = `${action.network} ${action.channel}`;
|
||||
|
||||
if (ready[key]) {
|
||||
ready[key] = false;
|
||||
|
@ -19,7 +19,7 @@ const message = store => next => {
|
|||
|
||||
cache[key] = action;
|
||||
} else if (action.type === ADD_FETCHED_MESSAGES) {
|
||||
const key = `${action.server} ${action.channel}`;
|
||||
const key = `${action.network} ${action.channel}`;
|
||||
ready[key] = true;
|
||||
|
||||
if (cache[key]) {
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import capitalize from 'lodash/capitalize';
|
||||
import { getRouter } from 'state';
|
||||
import { getCurrentServerName } from 'state/servers';
|
||||
import { getCurrentNetworkName } from 'state/networks';
|
||||
import { observe } from 'utils/observe';
|
||||
|
||||
export default function documentTitle({ store }) {
|
||||
observe(store, [getRouter, getCurrentServerName], (router, serverName) => {
|
||||
observe(store, [getRouter, getCurrentNetworkName], (router, networkName) => {
|
||||
let title;
|
||||
|
||||
if (router.route === 'chat') {
|
||||
const { server, name } = router.params;
|
||||
const { network, name } = router.params;
|
||||
if (name) {
|
||||
title = `${name} @ ${serverName || server}`;
|
||||
title = `${name} @ ${networkName || network}`;
|
||||
} else {
|
||||
title = serverName || server;
|
||||
title = networkName || network;
|
||||
}
|
||||
} else {
|
||||
title = capitalize(router.route);
|
||||
|
|
|
@ -8,15 +8,15 @@ import { when } from 'utils/observe';
|
|||
function loadState({ store }, env) {
|
||||
store.dispatch(setSettings(env.settings, true));
|
||||
|
||||
if (env.servers) {
|
||||
if (env.networks) {
|
||||
store.dispatch({
|
||||
type: socketActions.SERVERS,
|
||||
data: env.servers
|
||||
type: socketActions.NETWORKS,
|
||||
data: env.networks
|
||||
});
|
||||
|
||||
when(store, getConnected, () =>
|
||||
// Cache top channels for each server
|
||||
env.servers.forEach(({ host }) =>
|
||||
// Cache top channels for each network
|
||||
env.networks.forEach(({ host }) =>
|
||||
store.dispatch(searchChannels(host, ''))
|
||||
)
|
||||
);
|
||||
|
@ -56,8 +56,8 @@ function loadState({ store }, env) {
|
|||
// Wait until wrapWidth gets initialized so that height calculations
|
||||
// only happen once for these messages
|
||||
when(store, getWrapWidth, () => {
|
||||
const { messages, server, to, next } = env.messages;
|
||||
store.dispatch(addMessages(messages, server, to, false, next));
|
||||
const { messages, network, to, next } = env.messages;
|
||||
store.dispatch(addMessages(messages, network, to, false, next));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { socketAction } from 'state/actions';
|
||||
import { kicked } from 'state/channels';
|
||||
import {
|
||||
print,
|
||||
addMessage,
|
||||
|
@ -7,15 +8,15 @@ import {
|
|||
broadcastEvent
|
||||
} from 'state/messages';
|
||||
import { openModal } from 'state/modals';
|
||||
import { reconnect } from 'state/servers';
|
||||
import { reconnect } from 'state/networks';
|
||||
import { select } from 'state/tab';
|
||||
import { find } from 'utils';
|
||||
|
||||
function findChannels(state, server, user) {
|
||||
function findChannels(state, network, user) {
|
||||
const channels = [];
|
||||
|
||||
Object.keys(state.channels[server]).forEach(channel => {
|
||||
if (find(state.channels[server][channel].users, u => u.nick === user)) {
|
||||
Object.keys(state.channels[network]).forEach(channel => {
|
||||
if (find(state.channels[network][channel].users, u => u.nick === user)) {
|
||||
channels.push(channel);
|
||||
}
|
||||
});
|
||||
|
@ -29,51 +30,56 @@ export default function handleSocket({
|
|||
}) {
|
||||
const handlers = {
|
||||
message(message) {
|
||||
dispatch(addMessage(message, message.server, message.to));
|
||||
dispatch(addMessage(message, message.network, message.to));
|
||||
return false;
|
||||
},
|
||||
|
||||
pm(message) {
|
||||
dispatch(addMessage(message, message.server, message.from));
|
||||
dispatch(addMessage(message, message.network, message.from));
|
||||
return false;
|
||||
},
|
||||
|
||||
messages({ messages, server, to, prepend, next }) {
|
||||
dispatch(addMessages(messages, server, to, prepend, next));
|
||||
messages({ messages, network, to, prepend, next }) {
|
||||
dispatch(addMessages(messages, network, to, prepend, next));
|
||||
return false;
|
||||
},
|
||||
|
||||
join({ user, server, channels }) {
|
||||
dispatch(addEvent(server, channels[0], 'join', user));
|
||||
join({ user, network, channels }) {
|
||||
dispatch(addEvent(network, channels[0], 'join', user));
|
||||
},
|
||||
|
||||
part({ user, server, channel, reason }) {
|
||||
dispatch(addEvent(server, channel, 'part', user, reason));
|
||||
part({ user, network, channel, reason }) {
|
||||
dispatch(addEvent(network, channel, 'part', user, reason));
|
||||
},
|
||||
|
||||
quit({ user, server, reason }) {
|
||||
const channels = findChannels(getState(), server, user);
|
||||
dispatch(broadcastEvent(server, channels, 'quit', user, reason));
|
||||
quit({ user, network, reason }) {
|
||||
const channels = findChannels(getState(), network, user);
|
||||
dispatch(broadcastEvent(network, channels, 'quit', user, reason));
|
||||
},
|
||||
|
||||
nick({ server, oldNick, newNick }) {
|
||||
kick({ network, channel, sender, user, reason }) {
|
||||
dispatch(kicked(network, channel, user));
|
||||
dispatch(addEvent(network, channel, 'kick', user, sender, reason));
|
||||
},
|
||||
|
||||
nick({ network, oldNick, newNick }) {
|
||||
if (oldNick) {
|
||||
const channels = findChannels(getState(), server, oldNick);
|
||||
dispatch(broadcastEvent(server, channels, 'nick', oldNick, newNick));
|
||||
const channels = findChannels(getState(), network, oldNick);
|
||||
dispatch(broadcastEvent(network, channels, 'nick', oldNick, newNick));
|
||||
}
|
||||
},
|
||||
|
||||
topic({ server, channel, topic, nick }) {
|
||||
topic({ network, channel, topic, nick }) {
|
||||
if (nick) {
|
||||
dispatch(addEvent(server, channel, 'topic', nick, topic));
|
||||
dispatch(addEvent(network, channel, 'topic', nick, topic));
|
||||
}
|
||||
},
|
||||
|
||||
motd({ content, server }) {
|
||||
motd({ content, network }) {
|
||||
dispatch(
|
||||
addMessages(
|
||||
content.map(line => ({ content: line })),
|
||||
server
|
||||
network
|
||||
)
|
||||
);
|
||||
return false;
|
||||
|
@ -92,7 +98,7 @@ export default function handleSocket({
|
|||
`Server: ${data.server}`,
|
||||
`Channels: ${data.channels}`
|
||||
],
|
||||
tab.server,
|
||||
tab.network,
|
||||
tab.name
|
||||
)
|
||||
);
|
||||
|
@ -101,24 +107,26 @@ export default function handleSocket({
|
|||
|
||||
print(message) {
|
||||
const tab = getState().tab.selected;
|
||||
dispatch(addMessage(message, tab.server, tab.name));
|
||||
dispatch(addMessage(message, tab.network, tab.name));
|
||||
return false;
|
||||
},
|
||||
|
||||
error({ server, target, message }) {
|
||||
dispatch(addMessage({ content: message, type: 'error' }, server, target));
|
||||
error({ network, target, message }) {
|
||||
dispatch(
|
||||
addMessage({ content: message, type: 'error' }, network, target)
|
||||
);
|
||||
return false;
|
||||
},
|
||||
|
||||
connection_update({ server, errorType }) {
|
||||
connection_update({ network, errorType }) {
|
||||
if (errorType === 'verify') {
|
||||
dispatch(
|
||||
openModal('confirm', {
|
||||
question:
|
||||
'The server is using a self-signed certificate, continue anyway?',
|
||||
'The network is using a self-signed certificate, continue anyway?',
|
||||
onConfirm: () =>
|
||||
dispatch(
|
||||
reconnect(server, {
|
||||
reconnect(network, {
|
||||
skipVerify: true
|
||||
})
|
||||
)
|
||||
|
@ -127,12 +135,12 @@ export default function handleSocket({
|
|||
}
|
||||
},
|
||||
|
||||
dcc_send({ server, from, filename, url }) {
|
||||
const serverName = getState().servers[server]?.name || server;
|
||||
dcc_send({ network, from, filename, url }) {
|
||||
const networkName = getState().networks[network]?.name || network;
|
||||
|
||||
dispatch(
|
||||
openModal('confirm', {
|
||||
question: `${from} on ${serverName} is sending you: ${filename}`,
|
||||
question: `${from} on ${networkName} is sending you: ${filename}`,
|
||||
confirmation: 'Download',
|
||||
onConfirm: () => {
|
||||
const a = document.createElement('a');
|
||||
|
@ -148,8 +156,11 @@ export default function handleSocket({
|
|||
channel_forward(forward) {
|
||||
const { selected } = getState().tab;
|
||||
|
||||
if (selected.server === forward.server && selected.name === forward.old) {
|
||||
dispatch(select(forward.server, forward.new, true));
|
||||
if (
|
||||
selected.network === forward.network &&
|
||||
selected.name === forward.old
|
||||
) {
|
||||
dispatch(select(forward.network, forward.new, true));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -11,7 +11,7 @@ const saveTab = debounce(
|
|||
|
||||
export default function storage({ store }) {
|
||||
observe(store, getSelectedTab, tab => {
|
||||
if (tab.server) {
|
||||
if (tab.network) {
|
||||
saveTab(tab);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export default {
|
||||
connect: '/connect',
|
||||
settings: '/settings',
|
||||
chat: '/:server(/:name)'
|
||||
chat: '/:network(/:name)'
|
||||
};
|
||||
|
|
20
client/js/state/__tests__/actions-networks.test.js
Normal file
20
client/js/state/__tests__/actions-networks.test.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { connect, setNetworkName } from '../networks';
|
||||
|
||||
describe('setNetworkName()', () => {
|
||||
it('passes valid names to the network', () => {
|
||||
const name = 'cake';
|
||||
const network = 'srv';
|
||||
|
||||
expect(setNetworkName(name, network)).toMatchObject({
|
||||
socket: {
|
||||
type: 'set_network_name',
|
||||
data: { name, network }
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('does not pass invalid names to the network', () => {
|
||||
expect(setNetworkName('', 'srv').socket).toBeUndefined();
|
||||
expect(setNetworkName(' ', 'srv').socket).toBeUndefined();
|
||||
});
|
||||
});
|
|
@ -1,20 +0,0 @@
|
|||
import { connect, setServerName } from '../servers';
|
||||
|
||||
describe('setServerName()', () => {
|
||||
it('passes valid names to the server', () => {
|
||||
const name = 'cake';
|
||||
const server = 'srv';
|
||||
|
||||
expect(setServerName(name, server)).toMatchObject({
|
||||
socket: {
|
||||
type: 'set_server_name',
|
||||
data: { name, server }
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('does not pass invalid names to the server', () => {
|
||||
expect(setServerName('', 'srv').socket).toBeUndefined();
|
||||
expect(setServerName(' ', 'srv').socket).toBeUndefined();
|
||||
});
|
||||
});
|
|
@ -1,5 +1,5 @@
|
|||
import reducer, { compareUsers, getSortedChannels } from '../channels';
|
||||
import { connect } from '../servers';
|
||||
import { connect } from '../networks';
|
||||
import * as actions from '../actions';
|
||||
|
||||
describe('channel reducer', () => {
|
||||
|
@ -17,7 +17,7 @@ describe('channel reducer', () => {
|
|||
|
||||
state = reducer(state, {
|
||||
type: actions.PART,
|
||||
server: 'srv1',
|
||||
network: 'srv1',
|
||||
channels: ['chan1', 'chan3']
|
||||
});
|
||||
|
||||
|
@ -38,7 +38,7 @@ describe('channel reducer', () => {
|
|||
|
||||
state = reducer(state, {
|
||||
type: actions.socket.PART,
|
||||
server: 'srv',
|
||||
network: 'srv',
|
||||
channel: 'chan1',
|
||||
user: 'nick2'
|
||||
});
|
||||
|
@ -80,7 +80,7 @@ describe('channel reducer', () => {
|
|||
|
||||
state = reducer(state, {
|
||||
type: actions.socket.QUIT,
|
||||
server: 'srv',
|
||||
network: 'srv',
|
||||
user: 'nick2'
|
||||
});
|
||||
|
||||
|
@ -100,6 +100,67 @@ describe('channel reducer', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('handles KICKED', () => {
|
||||
let state = reducer(
|
||||
undefined,
|
||||
connect({
|
||||
host: 'srv',
|
||||
nick: 'nick2'
|
||||
})
|
||||
);
|
||||
state = reducer(state, socket_join('srv', 'chan1', 'nick1'));
|
||||
state = reducer(state, socket_join('srv', 'chan1', 'nick2'));
|
||||
state = reducer(state, socket_join('srv', 'chan2', 'nick2'));
|
||||
|
||||
state = reducer(state, {
|
||||
type: actions.KICKED,
|
||||
network: 'srv',
|
||||
channel: 'chan2',
|
||||
user: 'nick2',
|
||||
self: true
|
||||
});
|
||||
|
||||
expect(state).toEqual({
|
||||
srv: {
|
||||
chan1: {
|
||||
name: 'chan1',
|
||||
joined: true,
|
||||
users: [
|
||||
{ mode: '', nick: 'nick1', renderName: 'nick1' },
|
||||
{ mode: '', nick: 'nick2', renderName: 'nick2' }
|
||||
]
|
||||
},
|
||||
chan2: {
|
||||
name: 'chan2',
|
||||
joined: false,
|
||||
users: []
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
state = reducer(state, {
|
||||
type: actions.KICKED,
|
||||
network: 'srv',
|
||||
channel: 'chan1',
|
||||
user: 'nick1'
|
||||
});
|
||||
|
||||
expect(state).toEqual({
|
||||
srv: {
|
||||
chan1: {
|
||||
name: 'chan1',
|
||||
joined: true,
|
||||
users: [{ mode: '', nick: 'nick2', renderName: 'nick2' }]
|
||||
},
|
||||
chan2: {
|
||||
name: 'chan2',
|
||||
joined: false,
|
||||
users: []
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('handles SOCKET_NICK', () => {
|
||||
let state = reducer(undefined, socket_join('srv', 'chan1', 'nick1'));
|
||||
state = reducer(state, socket_join('srv', 'chan1', 'nick2'));
|
||||
|
@ -107,7 +168,7 @@ describe('channel reducer', () => {
|
|||
|
||||
state = reducer(state, {
|
||||
type: actions.socket.NICK,
|
||||
server: 'srv',
|
||||
network: 'srv',
|
||||
oldNick: 'nick1',
|
||||
newNick: 'nick3'
|
||||
});
|
||||
|
@ -135,7 +196,7 @@ describe('channel reducer', () => {
|
|||
let state = reducer(undefined, socket_join('srv', 'chan1', 'nick1'));
|
||||
state = reducer(state, {
|
||||
type: actions.socket.USERS,
|
||||
server: 'srv',
|
||||
network: 'srv',
|
||||
channel: 'chan1',
|
||||
users: ['user3', 'user2', '@user4', 'user1', '+user5']
|
||||
});
|
||||
|
@ -161,7 +222,7 @@ describe('channel reducer', () => {
|
|||
let state = reducer(undefined, socket_join('srv', 'chan1', 'nick1'));
|
||||
state = reducer(state, {
|
||||
type: actions.socket.TOPIC,
|
||||
server: 'srv',
|
||||
network: 'srv',
|
||||
channel: 'chan1',
|
||||
topic: 'the topic'
|
||||
});
|
||||
|
@ -219,9 +280,9 @@ describe('channel reducer', () => {
|
|||
const state = reducer(undefined, {
|
||||
type: actions.socket.CHANNELS,
|
||||
data: [
|
||||
{ server: 'srv', name: 'chan1', topic: 'the topic' },
|
||||
{ server: 'srv', name: 'chan2' },
|
||||
{ server: 'srv2', name: 'chan1' }
|
||||
{ network: 'srv', name: 'chan1', topic: 'the topic' },
|
||||
{ network: 'srv', name: 'chan2' },
|
||||
{ network: 'srv2', name: 'chan1' }
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -236,9 +297,9 @@ describe('channel reducer', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('handles SOCKET_SERVERS', () => {
|
||||
it('handles SOCKET_NETWORKS', () => {
|
||||
const state = reducer(undefined, {
|
||||
type: actions.socket.SERVERS,
|
||||
type: actions.socket.NETWORKS,
|
||||
data: [{ host: '127.0.0.1' }, { host: 'thehost' }]
|
||||
});
|
||||
|
||||
|
@ -248,7 +309,7 @@ describe('channel reducer', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('optimistically adds the server on CONNECT', () => {
|
||||
it('optimistically adds the network on CONNECT', () => {
|
||||
const state = reducer(
|
||||
undefined,
|
||||
connect({ host: '127.0.0.1', nick: 'nick' })
|
||||
|
@ -259,7 +320,7 @@ describe('channel reducer', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('removes the server on DISCONNECT', () => {
|
||||
it('removes the network on DISCONNECT', () => {
|
||||
let state = {
|
||||
srv: {},
|
||||
srv2: {}
|
||||
|
@ -267,7 +328,7 @@ describe('channel reducer', () => {
|
|||
|
||||
state = reducer(state, {
|
||||
type: actions.DISCONNECT,
|
||||
server: 'srv2'
|
||||
network: 'srv2'
|
||||
});
|
||||
|
||||
expect(state).toEqual({
|
||||
|
@ -276,19 +337,19 @@ describe('channel reducer', () => {
|
|||
});
|
||||
});
|
||||
|
||||
function socket_join(server, channel, user) {
|
||||
function socket_join(network, channel, user) {
|
||||
return {
|
||||
type: actions.socket.JOIN,
|
||||
server,
|
||||
network,
|
||||
user,
|
||||
channels: [channel]
|
||||
};
|
||||
}
|
||||
|
||||
function socket_mode(server, channel, user, add, remove) {
|
||||
function socket_mode(network, channel, user, add, remove) {
|
||||
return {
|
||||
type: actions.socket.MODE,
|
||||
server,
|
||||
network,
|
||||
channel,
|
||||
user,
|
||||
add,
|
||||
|
@ -323,7 +384,7 @@ describe('compareUsers()', () => {
|
|||
});
|
||||
|
||||
describe('getSortedChannels', () => {
|
||||
it('sorts servers and channels', () => {
|
||||
it('sorts networks and channels', () => {
|
||||
expect(
|
||||
getSortedChannels({
|
||||
channels: {
|
||||
|
|
|
@ -7,7 +7,7 @@ describe('message reducer', () => {
|
|||
it('adds the message on ADD_MESSAGE', () => {
|
||||
const state = reducer(undefined, {
|
||||
type: actions.ADD_MESSAGE,
|
||||
server: 'srv',
|
||||
network: 'srv',
|
||||
tab: '#chan1',
|
||||
message: {
|
||||
from: 'foo',
|
||||
|
@ -30,7 +30,7 @@ describe('message reducer', () => {
|
|||
it('adds all the messages on ADD_MESSAGES', () => {
|
||||
const state = reducer(undefined, {
|
||||
type: actions.ADD_MESSAGES,
|
||||
server: 'srv',
|
||||
network: 'srv',
|
||||
tab: '#chan1',
|
||||
messages: [
|
||||
{
|
||||
|
@ -80,7 +80,7 @@ describe('message reducer', () => {
|
|||
|
||||
state = reducer(state, {
|
||||
type: actions.ADD_MESSAGES,
|
||||
server: 'srv',
|
||||
network: 'srv',
|
||||
tab: '#chan1',
|
||||
prepend: true,
|
||||
messages: [
|
||||
|
@ -105,7 +105,7 @@ describe('message reducer', () => {
|
|||
|
||||
state = reducer(state, {
|
||||
type: actions.ADD_MESSAGES,
|
||||
server: 'srv',
|
||||
network: 'srv',
|
||||
tab: '#chan1',
|
||||
prepend: true,
|
||||
messages: [
|
||||
|
@ -136,7 +136,7 @@ describe('message reducer', () => {
|
|||
|
||||
state = reducer(state, {
|
||||
type: actions.ADD_MESSAGE,
|
||||
server: 'srv',
|
||||
network: 'srv',
|
||||
tab: '#chan1',
|
||||
message: { id: 1, date: new Date(1990, 0, 2) }
|
||||
});
|
||||
|
@ -157,7 +157,7 @@ describe('message reducer', () => {
|
|||
|
||||
state = reducer(state, {
|
||||
type: actions.ADD_MESSAGES,
|
||||
server: 'srv',
|
||||
network: 'srv',
|
||||
tab: '#chan1',
|
||||
messages: [
|
||||
{ id: 1, time: unix(new Date(1990, 0, 2)) },
|
||||
|
@ -202,7 +202,7 @@ describe('message reducer', () => {
|
|||
expect(messages.srv['#chan3'][0].content).toBe('test');
|
||||
});
|
||||
|
||||
it('deletes all messages related to server when disconnecting', () => {
|
||||
it('deletes all messages related to network when disconnecting', () => {
|
||||
let state = {
|
||||
srv: {
|
||||
'#chan1': [{ content: 'msg1' }, { content: 'msg2' }],
|
||||
|
@ -215,7 +215,7 @@ describe('message reducer', () => {
|
|||
|
||||
state = reducer(state, {
|
||||
type: actions.DISCONNECT,
|
||||
server: 'srv'
|
||||
network: 'srv'
|
||||
});
|
||||
|
||||
expect(state).toEqual({
|
||||
|
@ -238,7 +238,7 @@ describe('message reducer', () => {
|
|||
|
||||
state = reducer(state, {
|
||||
type: actions.PART,
|
||||
server: 'srv',
|
||||
network: 'srv',
|
||||
channels: ['#chan1']
|
||||
});
|
||||
|
||||
|
@ -265,7 +265,7 @@ describe('message reducer', () => {
|
|||
|
||||
state = reducer(state, {
|
||||
type: actions.CLOSE_PRIVATE_CHAT,
|
||||
server: 'srv',
|
||||
network: 'srv',
|
||||
nick: 'bob'
|
||||
});
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import reducer, { connect, setServerName } from '../servers';
|
||||
import reducer, { connect, setNetworkName } from '../networks';
|
||||
import * as actions from '../actions';
|
||||
|
||||
describe('server reducer', () => {
|
||||
it('adds the server on CONNECT', () => {
|
||||
describe('network reducer', () => {
|
||||
it('adds the network on CONNECT', () => {
|
||||
let state = reducer(
|
||||
undefined,
|
||||
connect({ host: '127.0.0.1', nick: 'nick' })
|
||||
|
@ -13,10 +13,8 @@ describe('server reducer', () => {
|
|||
name: '127.0.0.1',
|
||||
nick: 'nick',
|
||||
editedNick: null,
|
||||
status: {
|
||||
connected: false,
|
||||
error: null
|
||||
},
|
||||
connected: false,
|
||||
error: null,
|
||||
features: {}
|
||||
}
|
||||
});
|
||||
|
@ -28,10 +26,8 @@ describe('server reducer', () => {
|
|||
name: '127.0.0.1',
|
||||
nick: 'nick',
|
||||
editedNick: null,
|
||||
status: {
|
||||
connected: false,
|
||||
error: null
|
||||
},
|
||||
connected: false,
|
||||
error: null,
|
||||
features: {}
|
||||
}
|
||||
});
|
||||
|
@ -46,26 +42,22 @@ describe('server reducer', () => {
|
|||
name: '127.0.0.1',
|
||||
nick: 'nick',
|
||||
editedNick: null,
|
||||
status: {
|
||||
connected: false,
|
||||
error: null
|
||||
},
|
||||
connected: false,
|
||||
error: null,
|
||||
features: {}
|
||||
},
|
||||
'127.0.0.2': {
|
||||
name: 'srv',
|
||||
nick: 'nick',
|
||||
editedNick: null,
|
||||
status: {
|
||||
connected: false,
|
||||
error: null
|
||||
},
|
||||
connected: false,
|
||||
error: null,
|
||||
features: {}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('removes the server on DISCONNECT', () => {
|
||||
it('removes the network on DISCONNECT', () => {
|
||||
let state = {
|
||||
srv: {},
|
||||
srv2: {}
|
||||
|
@ -73,7 +65,7 @@ describe('server reducer', () => {
|
|||
|
||||
state = reducer(state, {
|
||||
type: actions.DISCONNECT,
|
||||
server: 'srv2'
|
||||
network: 'srv2'
|
||||
});
|
||||
|
||||
expect(state).toEqual({
|
||||
|
@ -81,14 +73,14 @@ describe('server reducer', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('handles SET_SERVER_NAME', () => {
|
||||
it('handles SET_NETWORK_NAME', () => {
|
||||
let state = {
|
||||
srv: {
|
||||
name: 'cake'
|
||||
}
|
||||
};
|
||||
|
||||
state = reducer(state, setServerName('pie', 'srv'));
|
||||
state = reducer(state, setNetworkName('pie', 'srv'));
|
||||
|
||||
expect(state).toEqual({
|
||||
srv: {
|
||||
|
@ -104,7 +96,7 @@ describe('server reducer', () => {
|
|||
);
|
||||
state = reducer(state, {
|
||||
type: actions.SET_NICK,
|
||||
server: '127.0.0.1',
|
||||
network: '127.0.0.1',
|
||||
nick: 'nick2',
|
||||
editing: true
|
||||
});
|
||||
|
@ -125,13 +117,13 @@ describe('server reducer', () => {
|
|||
);
|
||||
state = reducer(state, {
|
||||
type: actions.SET_NICK,
|
||||
server: '127.0.0.1',
|
||||
network: '127.0.0.1',
|
||||
nick: 'nick2',
|
||||
editing: true
|
||||
});
|
||||
state = reducer(state, {
|
||||
type: actions.SET_NICK,
|
||||
server: '127.0.0.1',
|
||||
network: '127.0.0.1',
|
||||
nick: ''
|
||||
});
|
||||
|
||||
|
@ -151,7 +143,7 @@ describe('server reducer', () => {
|
|||
);
|
||||
state = reducer(state, {
|
||||
type: actions.socket.NICK,
|
||||
server: '127.0.0.1',
|
||||
network: '127.0.0.1',
|
||||
oldNick: 'nick',
|
||||
newNick: 'nick2'
|
||||
});
|
||||
|
@ -172,13 +164,13 @@ describe('server reducer', () => {
|
|||
);
|
||||
state = reducer(state, {
|
||||
type: actions.SET_NICK,
|
||||
server: '127.0.0.1',
|
||||
network: '127.0.0.1',
|
||||
nick: 'nick2',
|
||||
editing: true
|
||||
});
|
||||
state = reducer(state, {
|
||||
type: actions.socket.NICK_FAIL,
|
||||
server: '127.0.0.1'
|
||||
network: '127.0.0.1'
|
||||
});
|
||||
|
||||
expect(state).toMatchObject({
|
||||
|
@ -190,25 +182,21 @@ describe('server reducer', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('adds the servers on SOCKET_SERVERS', () => {
|
||||
it('adds the networks on SOCKET_NETWORKS', () => {
|
||||
let state = reducer(undefined, {
|
||||
type: actions.socket.SERVERS,
|
||||
type: actions.socket.NETWORKS,
|
||||
data: [
|
||||
{
|
||||
host: '127.0.0.1',
|
||||
name: 'stuff',
|
||||
nick: 'nick',
|
||||
status: {
|
||||
connected: true
|
||||
}
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
host: '127.0.0.2',
|
||||
name: 'stuffz',
|
||||
nick: 'nick2',
|
||||
status: {
|
||||
connected: false
|
||||
}
|
||||
connected: false
|
||||
}
|
||||
]
|
||||
});
|
||||
|
@ -218,18 +206,14 @@ describe('server reducer', () => {
|
|||
name: 'stuff',
|
||||
nick: 'nick',
|
||||
editedNick: null,
|
||||
status: {
|
||||
connected: true
|
||||
},
|
||||
connected: true,
|
||||
features: {}
|
||||
},
|
||||
'127.0.0.2': {
|
||||
name: 'stuffz',
|
||||
nick: 'nick2',
|
||||
editedNick: null,
|
||||
status: {
|
||||
connected: false
|
||||
},
|
||||
connected: false,
|
||||
features: {}
|
||||
}
|
||||
});
|
||||
|
@ -242,7 +226,7 @@ describe('server reducer', () => {
|
|||
);
|
||||
state = reducer(state, {
|
||||
type: actions.socket.CONNECTION_UPDATE,
|
||||
server: '127.0.0.1',
|
||||
network: '127.0.0.1',
|
||||
connected: true
|
||||
});
|
||||
|
||||
|
@ -251,16 +235,14 @@ describe('server reducer', () => {
|
|||
name: '127.0.0.1',
|
||||
nick: 'nick',
|
||||
editedNick: null,
|
||||
status: {
|
||||
connected: true
|
||||
},
|
||||
connected: true,
|
||||
features: {}
|
||||
}
|
||||
});
|
||||
|
||||
state = reducer(state, {
|
||||
type: actions.socket.CONNECTION_UPDATE,
|
||||
server: '127.0.0.1',
|
||||
network: '127.0.0.1',
|
||||
connected: false,
|
||||
error: 'Bad stuff happened'
|
||||
});
|
||||
|
@ -270,10 +252,8 @@ describe('server reducer', () => {
|
|||
name: '127.0.0.1',
|
||||
nick: 'nick',
|
||||
editedNick: null,
|
||||
status: {
|
||||
connected: false,
|
||||
error: 'Bad stuff happened'
|
||||
},
|
||||
connected: false,
|
||||
error: 'Bad stuff happened',
|
||||
features: {}
|
||||
}
|
||||
});
|
|
@ -7,17 +7,17 @@ describe('tab reducer', () => {
|
|||
let state = reducer(undefined, setSelectedTab('srv', '#chan'));
|
||||
|
||||
expect(state).toEqual({
|
||||
selected: { server: 'srv', name: '#chan' },
|
||||
history: [{ server: 'srv', name: '#chan' }]
|
||||
selected: { network: 'srv', name: '#chan' },
|
||||
history: [{ network: 'srv', name: '#chan' }]
|
||||
});
|
||||
|
||||
state = reducer(state, setSelectedTab('srv', 'user1'));
|
||||
|
||||
expect(state).toEqual({
|
||||
selected: { server: 'srv', name: 'user1' },
|
||||
selected: { network: 'srv', name: 'user1' },
|
||||
history: [
|
||||
{ server: 'srv', name: '#chan' },
|
||||
{ server: 'srv', name: 'user1' }
|
||||
{ network: 'srv', name: '#chan' },
|
||||
{ network: 'srv', name: 'user1' }
|
||||
]
|
||||
});
|
||||
});
|
||||
|
@ -30,15 +30,15 @@ describe('tab reducer', () => {
|
|||
|
||||
state = reducer(state, {
|
||||
type: actions.PART,
|
||||
server: 'srv',
|
||||
network: 'srv',
|
||||
channels: ['#chan']
|
||||
});
|
||||
|
||||
expect(state).toEqual({
|
||||
selected: { server: 'srv', name: '#chan3' },
|
||||
selected: { network: 'srv', name: '#chan3' },
|
||||
history: [
|
||||
{ server: 'srv1', name: 'bob' },
|
||||
{ server: 'srv', name: '#chan3' }
|
||||
{ network: 'srv1', name: 'bob' },
|
||||
{ network: 'srv', name: '#chan3' }
|
||||
]
|
||||
});
|
||||
});
|
||||
|
@ -51,21 +51,21 @@ describe('tab reducer', () => {
|
|||
|
||||
state = reducer(state, {
|
||||
type: actions.CLOSE_PRIVATE_CHAT,
|
||||
server: 'srv1',
|
||||
network: 'srv1',
|
||||
nick: 'bob'
|
||||
});
|
||||
|
||||
expect(state).toEqual({
|
||||
selected: { server: 'srv', name: '#chan3' },
|
||||
selected: { network: 'srv', name: '#chan3' },
|
||||
history: [
|
||||
{ server: 'srv', name: '#chan' },
|
||||
{ server: 'srv', name: '#chan' },
|
||||
{ server: 'srv', name: '#chan3' }
|
||||
{ network: 'srv', name: '#chan' },
|
||||
{ network: 'srv', name: '#chan' },
|
||||
{ network: 'srv', name: '#chan3' }
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it('removes all tabs related to server from history on DISCONNECT', () => {
|
||||
it('removes all tabs related to network from history on DISCONNECT', () => {
|
||||
let state = reducer(undefined, setSelectedTab('srv', '#chan'));
|
||||
state = reducer(state, setSelectedTab('srv1', 'bob'));
|
||||
state = reducer(state, setSelectedTab('srv', '#chan'));
|
||||
|
@ -73,12 +73,12 @@ describe('tab reducer', () => {
|
|||
|
||||
state = reducer(state, {
|
||||
type: actions.DISCONNECT,
|
||||
server: 'srv'
|
||||
network: 'srv'
|
||||
});
|
||||
|
||||
expect(state).toEqual({
|
||||
selected: { server: 'srv', name: '#chan3' },
|
||||
history: [{ server: 'srv1', name: 'bob' }]
|
||||
selected: { network: 'srv', name: '#chan3' },
|
||||
history: [{ network: 'srv1', name: 'bob' }]
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -89,7 +89,7 @@ describe('tab reducer', () => {
|
|||
|
||||
expect(state).toEqual({
|
||||
selected: {},
|
||||
history: [{ server: 'srv', name: '#chan' }]
|
||||
history: [{ network: 'srv', name: '#chan' }]
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -99,7 +99,7 @@ describe('tab reducer', () => {
|
|||
locationChanged(
|
||||
'chat',
|
||||
{
|
||||
server: 'srv',
|
||||
network: 'srv',
|
||||
name: '#chan'
|
||||
},
|
||||
{}
|
||||
|
@ -107,8 +107,8 @@ describe('tab reducer', () => {
|
|||
);
|
||||
|
||||
expect(state).toEqual({
|
||||
selected: { server: 'srv', name: '#chan' },
|
||||
history: [{ server: 'srv', name: '#chan' }]
|
||||
selected: { network: 'srv', name: '#chan' },
|
||||
history: [{ network: 'srv', name: '#chan' }]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,6 +3,7 @@ export const APP_SET = 'APP_SET';
|
|||
export const INVITE = 'INVITE';
|
||||
export const JOIN = 'JOIN';
|
||||
export const KICK = 'KICK';
|
||||
export const KICKED = 'KICKED';
|
||||
export const PART = 'PART';
|
||||
export const SET_TOPIC = 'SET_TOPIC';
|
||||
|
||||
|
@ -36,7 +37,7 @@ export const CONNECT = 'CONNECT';
|
|||
export const DISCONNECT = 'DISCONNECT';
|
||||
export const RECONNECT = 'RECONNECT';
|
||||
export const SET_NICK = 'SET_NICK';
|
||||
export const SET_SERVER_NAME = 'SET_SERVER_NAME';
|
||||
export const SET_NETWORK_NAME = 'SET_NETWORK_NAME';
|
||||
export const WHOIS = 'WHOIS';
|
||||
|
||||
export const SET_CERT = 'SET_CERT';
|
||||
|
@ -83,7 +84,7 @@ export const socket = createSocketActions([
|
|||
'pm',
|
||||
'quit',
|
||||
'search',
|
||||
'servers',
|
||||
'networks',
|
||||
'topic',
|
||||
'users'
|
||||
]);
|
||||
|
|
|
@ -8,7 +8,7 @@ const initialState = {
|
|||
};
|
||||
|
||||
export default createReducer(initialState, {
|
||||
[actions.socket.CHANNEL_SEARCH](state, { results, start, server, q }) {
|
||||
[actions.socket.CHANNEL_SEARCH](state, { results, start, network, q }) {
|
||||
if (results) {
|
||||
state.end = false;
|
||||
|
||||
|
@ -18,7 +18,7 @@ export default createReducer(initialState, {
|
|||
state.results = results;
|
||||
|
||||
if (!q) {
|
||||
state.topCache[server] = results;
|
||||
state.topCache[network] = results;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -34,14 +34,14 @@ export default createReducer(initialState, {
|
|||
}
|
||||
});
|
||||
|
||||
export function searchChannels(server, q, start) {
|
||||
export function searchChannels(network, q, start) {
|
||||
return {
|
||||
type: actions.CHANNEL_SEARCH,
|
||||
server,
|
||||
network,
|
||||
q,
|
||||
socket: {
|
||||
type: 'channel_search',
|
||||
data: { server, q, start }
|
||||
data: { network, q, start }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -56,13 +56,18 @@ function removeUser(users, nick) {
|
|||
}
|
||||
}
|
||||
|
||||
function init(state, server, channel) {
|
||||
if (!state[server]) {
|
||||
state[server] = {};
|
||||
function init(state, network, channel) {
|
||||
if (!state[network]) {
|
||||
state[network] = {};
|
||||
}
|
||||
if (channel && !state[server][channel]) {
|
||||
state[server][channel] = { name: channel, users: [], joined: false };
|
||||
if (channel && !state[network][channel]) {
|
||||
state[network][channel] = {
|
||||
name: channel,
|
||||
users: [],
|
||||
joined: false
|
||||
};
|
||||
}
|
||||
return state[network][channel];
|
||||
}
|
||||
|
||||
export function compareUsers(a, b) {
|
||||
|
@ -93,18 +98,18 @@ export const getChannels = state => state.channels;
|
|||
|
||||
export const getSortedChannels = createSelector(getChannels, channels =>
|
||||
sortBy(
|
||||
Object.keys(channels).map(server => ({
|
||||
address: server,
|
||||
channels: sortBy(channels[server], channel => channel.name.toLowerCase())
|
||||
Object.keys(channels).map(network => ({
|
||||
address: network,
|
||||
channels: sortBy(channels[network], channel => channel.name.toLowerCase())
|
||||
})),
|
||||
server => server.address.toLowerCase()
|
||||
network => network.address.toLowerCase()
|
||||
)
|
||||
);
|
||||
|
||||
export const getSelectedChannel = createSelector(
|
||||
getSelectedTab,
|
||||
getChannels,
|
||||
(tab, channels) => get(channels, [tab.server, tab.name])
|
||||
(tab, channels) => get(channels, [tab.network, tab.name])
|
||||
);
|
||||
|
||||
export const getSelectedChannelUsers = createSelector(
|
||||
|
@ -120,43 +125,53 @@ export const getSelectedChannelUsers = createSelector(
|
|||
export default createReducer(
|
||||
{},
|
||||
{
|
||||
[actions.JOIN](state, { server, channels }) {
|
||||
channels.forEach(channel => init(state, server, channel));
|
||||
[actions.JOIN](state, { network, channels }) {
|
||||
channels.forEach(channel => init(state, network, channel));
|
||||
},
|
||||
|
||||
[actions.PART](state, { server, channels }) {
|
||||
channels.forEach(channel => delete state[server][channel]);
|
||||
[actions.PART](state, { network, channels }) {
|
||||
channels.forEach(channel => delete state[network][channel]);
|
||||
},
|
||||
|
||||
[actions.socket.JOIN](state, { server, channels, user }) {
|
||||
[actions.socket.JOIN](state, { network, channels, user }) {
|
||||
const channel = channels[0];
|
||||
init(state, server, channel);
|
||||
state[server][channel].name = channel;
|
||||
state[server][channel].joined = true;
|
||||
state[server][channel].users.push(createUser(user));
|
||||
const chan = init(state, network, channel);
|
||||
chan.name = channel;
|
||||
chan.joined = true;
|
||||
chan.users.push(createUser(user));
|
||||
},
|
||||
|
||||
[actions.socket.CHANNEL_FORWARD](state, action) {
|
||||
init(state, action.server, action.new);
|
||||
delete state[action.server][action.old];
|
||||
init(state, action.network, action.new);
|
||||
delete state[action.network][action.old];
|
||||
},
|
||||
|
||||
[actions.socket.PART](state, { server, channel, user }) {
|
||||
if (state[server][channel]) {
|
||||
removeUser(state[server][channel].users, user);
|
||||
[actions.socket.PART](state, { network, channel, user }) {
|
||||
if (state[network][channel]) {
|
||||
removeUser(state[network][channel].users, user);
|
||||
}
|
||||
},
|
||||
|
||||
[actions.socket.QUIT](state, { server, user }) {
|
||||
Object.keys(state[server]).forEach(channel => {
|
||||
removeUser(state[server][channel].users, user);
|
||||
[actions.socket.QUIT](state, { network, user }) {
|
||||
Object.keys(state[network]).forEach(channel => {
|
||||
removeUser(state[network][channel].users, user);
|
||||
});
|
||||
},
|
||||
|
||||
[actions.socket.NICK](state, { server, oldNick, newNick }) {
|
||||
Object.keys(state[server]).forEach(channel => {
|
||||
[actions.KICKED](state, { network, channel, user, self }) {
|
||||
const chan = state[network][channel];
|
||||
if (self) {
|
||||
chan.joined = false;
|
||||
chan.users = [];
|
||||
} else {
|
||||
removeUser(chan.users, user);
|
||||
}
|
||||
},
|
||||
|
||||
[actions.socket.NICK](state, { network, oldNick, newNick }) {
|
||||
Object.keys(state[network]).forEach(channel => {
|
||||
const user = find(
|
||||
state[server][channel].users,
|
||||
state[network][channel].users,
|
||||
u => u.nick === oldNick
|
||||
);
|
||||
if (user) {
|
||||
|
@ -166,16 +181,16 @@ export default createReducer(
|
|||
});
|
||||
},
|
||||
|
||||
[actions.socket.USERS](state, { server, channel, users }) {
|
||||
state[server][channel].users = users.map(nick => loadUser(nick));
|
||||
[actions.socket.USERS](state, { network, channel, users }) {
|
||||
state[network][channel].users = users.map(nick => loadUser(nick));
|
||||
},
|
||||
|
||||
[actions.socket.TOPIC](state, { server, channel, topic }) {
|
||||
state[server][channel].topic = topic;
|
||||
[actions.socket.TOPIC](state, { network, channel, topic }) {
|
||||
state[network][channel].topic = topic;
|
||||
},
|
||||
|
||||
[actions.socket.MODE](state, { server, channel, user, remove, add }) {
|
||||
const u = find(state[server][channel].users, v => v.nick === user);
|
||||
[actions.socket.MODE](state, { network, channel, user, remove, add }) {
|
||||
const u = find(state[network][channel].users, v => v.nick === user);
|
||||
if (u) {
|
||||
if (remove) {
|
||||
let j = remove.length;
|
||||
|
@ -194,15 +209,15 @@ export default createReducer(
|
|||
|
||||
[actions.socket.CHANNELS](state, { data }) {
|
||||
if (data) {
|
||||
data.forEach(({ server, name, topic }) => {
|
||||
init(state, server, name);
|
||||
state[server][name].joined = true;
|
||||
state[server][name].topic = topic;
|
||||
data.forEach(({ network, name, topic }) => {
|
||||
const chan = init(state, network, name);
|
||||
chan.joined = true;
|
||||
chan.topic = topic;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
[actions.socket.SERVERS](state, { data }) {
|
||||
[actions.socket.NETWORKS](state, { data }) {
|
||||
if (data) {
|
||||
data.forEach(({ host }) => init(state, host));
|
||||
}
|
||||
|
@ -212,33 +227,33 @@ export default createReducer(
|
|||
init(state, host);
|
||||
},
|
||||
|
||||
[actions.DISCONNECT](state, { server }) {
|
||||
delete state[server];
|
||||
[actions.DISCONNECT](state, { network }) {
|
||||
delete state[network];
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export function join(channels, server) {
|
||||
export function join(channels, network) {
|
||||
return {
|
||||
type: actions.JOIN,
|
||||
channels,
|
||||
server,
|
||||
network,
|
||||
socket: {
|
||||
type: 'join',
|
||||
data: { channels, server }
|
||||
data: { channels, network }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function part(channels, server) {
|
||||
export function part(channels, network) {
|
||||
return (dispatch, getState) => {
|
||||
const action = {
|
||||
type: actions.PART,
|
||||
channels,
|
||||
server
|
||||
network
|
||||
};
|
||||
|
||||
const state = getState().channels[server];
|
||||
const state = getState().channels[network];
|
||||
const joined = channels.filter(c => state[c] && state[c].joined);
|
||||
|
||||
if (joined.length > 0) {
|
||||
|
@ -246,7 +261,7 @@ export function part(channels, server) {
|
|||
type: 'part',
|
||||
data: {
|
||||
channels: joined,
|
||||
server
|
||||
network
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -256,41 +271,55 @@ export function part(channels, server) {
|
|||
};
|
||||
}
|
||||
|
||||
export function invite(user, channel, server) {
|
||||
export function invite(user, channel, network) {
|
||||
return {
|
||||
type: actions.INVITE,
|
||||
user,
|
||||
channel,
|
||||
server,
|
||||
network,
|
||||
socket: {
|
||||
type: 'invite',
|
||||
data: { user, channel, server }
|
||||
data: { user, channel, network }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function kick(user, channel, server) {
|
||||
export function kick(user, channel, network) {
|
||||
return {
|
||||
type: actions.KICK,
|
||||
user,
|
||||
channel,
|
||||
server,
|
||||
network,
|
||||
socket: {
|
||||
type: 'kick',
|
||||
data: { user, channel, server }
|
||||
data: { user, channel, network }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function setTopic(topic, channel, server) {
|
||||
export function kicked(network, channel, user) {
|
||||
return (dispatch, getState) => {
|
||||
const nick = getState().networks[network]?.nick;
|
||||
|
||||
dispatch({
|
||||
type: actions.KICKED,
|
||||
network,
|
||||
channel,
|
||||
user,
|
||||
self: nick === user
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function setTopic(topic, channel, network) {
|
||||
return {
|
||||
type: actions.SET_TOPIC,
|
||||
topic,
|
||||
channel,
|
||||
server,
|
||||
network,
|
||||
socket: {
|
||||
type: 'topic',
|
||||
data: { topic, channel, server }
|
||||
data: { topic, channel, network }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@ import channelSearch from './channelSearch';
|
|||
import input from './input';
|
||||
import messages from './messages';
|
||||
import modals from './modals';
|
||||
import networks from './networks';
|
||||
import privateChats from './privateChats';
|
||||
import search from './search';
|
||||
import servers from './servers';
|
||||
import settings from './settings';
|
||||
import tab from './tab';
|
||||
import ui from './ui';
|
||||
|
@ -24,9 +24,9 @@ export default function createReducer(router) {
|
|||
input,
|
||||
messages,
|
||||
modals,
|
||||
networks,
|
||||
privateChats,
|
||||
search,
|
||||
servers,
|
||||
settings,
|
||||
tab,
|
||||
ui
|
||||
|
|
|
@ -23,9 +23,9 @@ export const getSelectedMessages = createSelector(
|
|||
getSelectedTab,
|
||||
getMessages,
|
||||
(tab, messages) => {
|
||||
const target = tab.name || tab.server;
|
||||
if (has(messages, [tab.server, target])) {
|
||||
return messages[tab.server][target];
|
||||
const target = tab.name || tab.network;
|
||||
if (has(messages, [tab.network, target])) {
|
||||
return messages[tab.network][target];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
@ -39,12 +39,12 @@ export const getHasMoreMessages = createSelector(
|
|||
}
|
||||
);
|
||||
|
||||
function init(state, server, tab) {
|
||||
if (!state[server]) {
|
||||
state[server] = {};
|
||||
function init(state, network, tab) {
|
||||
if (!state[network]) {
|
||||
state[network] = {};
|
||||
}
|
||||
if (!state[server][tab]) {
|
||||
state[server][tab] = [];
|
||||
if (!state[network][tab]) {
|
||||
state[network][tab] = [];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,6 +117,13 @@ function renderEvents(events) {
|
|||
|
||||
return [renderNick(oldNick), ' changed nick to ', renderNick(newNick)];
|
||||
}
|
||||
|
||||
if (first.type === 'kick') {
|
||||
const [kicked, by] = first.params;
|
||||
|
||||
return [renderNick(by), ' kicked ', renderNick(kicked)];
|
||||
}
|
||||
|
||||
if (first.type === 'topic') {
|
||||
const [nick, newTopic] = first.params;
|
||||
const topic = colorify(linkify(newTopic));
|
||||
|
@ -176,14 +183,14 @@ let nextID = 0;
|
|||
function initMessage(
|
||||
state,
|
||||
message,
|
||||
server,
|
||||
network,
|
||||
tab,
|
||||
wrapWidth,
|
||||
charWidth,
|
||||
windowWidth,
|
||||
prepend
|
||||
) {
|
||||
const messages = state[server][tab];
|
||||
const messages = state[network][tab];
|
||||
|
||||
if (messages.length > 0 && !prepend) {
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
|
@ -280,7 +287,7 @@ function isSameDay(d1, d2) {
|
|||
function reducerPrependMessages(
|
||||
state,
|
||||
messages,
|
||||
server,
|
||||
network,
|
||||
tab,
|
||||
wrapWidth,
|
||||
charWidth,
|
||||
|
@ -293,7 +300,7 @@ function reducerPrependMessages(
|
|||
initMessage(
|
||||
state,
|
||||
message,
|
||||
server,
|
||||
network,
|
||||
tab,
|
||||
wrapWidth,
|
||||
charWidth,
|
||||
|
@ -307,7 +314,7 @@ function reducerPrependMessages(
|
|||
msgs.push(message);
|
||||
}
|
||||
|
||||
const m = state[server][tab];
|
||||
const m = state[network][tab];
|
||||
|
||||
if (m.length > 0) {
|
||||
const lastNewMessage = msgs[msgs.length - 1];
|
||||
|
@ -323,8 +330,8 @@ function reducerPrependMessages(
|
|||
m.unshift(...msgs);
|
||||
}
|
||||
|
||||
function reducerAddMessage(message, server, tab, state) {
|
||||
const messages = state[server][tab];
|
||||
function reducerAddMessage(message, network, tab, state) {
|
||||
const messages = state[network][tab];
|
||||
|
||||
if (messages.length > 0) {
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
|
@ -341,34 +348,34 @@ export default createReducer(
|
|||
{
|
||||
[actions.ADD_MESSAGE](
|
||||
state,
|
||||
{ server, tab, message, wrapWidth, charWidth, windowWidth }
|
||||
{ network, tab, message, wrapWidth, charWidth, windowWidth }
|
||||
) {
|
||||
init(state, server, tab);
|
||||
init(state, network, tab);
|
||||
|
||||
const shouldAdd = initMessage(
|
||||
state,
|
||||
message,
|
||||
server,
|
||||
network,
|
||||
tab,
|
||||
wrapWidth,
|
||||
charWidth,
|
||||
windowWidth
|
||||
);
|
||||
if (shouldAdd) {
|
||||
reducerAddMessage(message, server, tab, state);
|
||||
reducerAddMessage(message, network, tab, state);
|
||||
}
|
||||
},
|
||||
|
||||
[actions.ADD_MESSAGES](
|
||||
state,
|
||||
{ server, tab, messages, prepend, wrapWidth, charWidth, windowWidth }
|
||||
{ network, tab, messages, prepend, wrapWidth, charWidth, windowWidth }
|
||||
) {
|
||||
if (prepend) {
|
||||
init(state, server, tab);
|
||||
init(state, network, tab);
|
||||
reducerPrependMessages(
|
||||
state,
|
||||
messages,
|
||||
server,
|
||||
network,
|
||||
tab,
|
||||
wrapWidth,
|
||||
charWidth,
|
||||
|
@ -376,45 +383,45 @@ export default createReducer(
|
|||
);
|
||||
} else {
|
||||
if (!messages[0].tab) {
|
||||
init(state, server, tab);
|
||||
init(state, network, tab);
|
||||
}
|
||||
|
||||
messages.forEach(message => {
|
||||
if (message.tab) {
|
||||
init(state, server, message.tab);
|
||||
init(state, network, message.tab);
|
||||
}
|
||||
|
||||
const shouldAdd = initMessage(
|
||||
state,
|
||||
message,
|
||||
server,
|
||||
network,
|
||||
message.tab || tab,
|
||||
wrapWidth,
|
||||
charWidth,
|
||||
windowWidth
|
||||
);
|
||||
if (shouldAdd) {
|
||||
reducerAddMessage(message, server, message.tab || tab, state);
|
||||
reducerAddMessage(message, network, message.tab || tab, state);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
[actions.DISCONNECT](state, { server }) {
|
||||
delete state[server];
|
||||
[actions.DISCONNECT](state, { network }) {
|
||||
delete state[network];
|
||||
},
|
||||
|
||||
[actions.PART](state, { server, channels }) {
|
||||
channels.forEach(channel => delete state[server][channel]);
|
||||
[actions.PART](state, { network, channels }) {
|
||||
channels.forEach(channel => delete state[network][channel]);
|
||||
},
|
||||
|
||||
[actions.CLOSE_PRIVATE_CHAT](state, { server, nick }) {
|
||||
delete state[server][nick];
|
||||
[actions.CLOSE_PRIVATE_CHAT](state, { network, nick }) {
|
||||
delete state[network][nick];
|
||||
},
|
||||
|
||||
[actions.socket.CHANNEL_FORWARD](state, { server, old }) {
|
||||
if (state[server]) {
|
||||
delete state[server][old];
|
||||
[actions.socket.CHANNEL_FORWARD](state, { network, old }) {
|
||||
if (state[network]) {
|
||||
delete state[network][old];
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -422,9 +429,9 @@ export default createReducer(
|
|||
state,
|
||||
{ wrapWidth, charWidth, windowWidth }
|
||||
) {
|
||||
Object.keys(state).forEach(server =>
|
||||
Object.keys(state[server]).forEach(target =>
|
||||
state[server][target].forEach(message => {
|
||||
Object.keys(state).forEach(network =>
|
||||
Object.keys(state[network]).forEach(target =>
|
||||
state[network][target].forEach(message => {
|
||||
if (message.type === 'date') {
|
||||
return;
|
||||
}
|
||||
|
@ -441,7 +448,7 @@ export default createReducer(
|
|||
);
|
||||
},
|
||||
|
||||
[actions.socket.SERVERS](state, { data }) {
|
||||
[actions.socket.NETWORKS](state, { data }) {
|
||||
if (data) {
|
||||
data.forEach(({ host }) => {
|
||||
state[host] = {};
|
||||
|
@ -451,9 +458,9 @@ export default createReducer(
|
|||
}
|
||||
);
|
||||
|
||||
export function getMessageTab(server, to) {
|
||||
export function getMessageTab(network, to) {
|
||||
if (!to || to === '*' || (!isChannel(to) && to.indexOf('.') !== -1)) {
|
||||
return server;
|
||||
return network;
|
||||
}
|
||||
return to;
|
||||
}
|
||||
|
@ -474,7 +481,7 @@ export function fetchMessages() {
|
|||
socket: {
|
||||
type: 'fetch_messages',
|
||||
data: {
|
||||
server: tab.server,
|
||||
network: tab.network,
|
||||
channel: tab.name,
|
||||
next: first.id
|
||||
}
|
||||
|
@ -484,10 +491,10 @@ export function fetchMessages() {
|
|||
};
|
||||
}
|
||||
|
||||
export function addFetchedMessages(server, tab) {
|
||||
export function addFetchedMessages(network, tab) {
|
||||
return {
|
||||
type: actions.ADD_FETCHED_MESSAGES,
|
||||
server,
|
||||
network,
|
||||
tab
|
||||
};
|
||||
}
|
||||
|
@ -501,17 +508,17 @@ export function updateMessageHeight(wrapWidth, charWidth, windowWidth) {
|
|||
};
|
||||
}
|
||||
|
||||
export function sendMessage(content, to, server) {
|
||||
export function sendMessage(content, to, network) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const { wrapWidth, charWidth, windowWidth } = getApp(state);
|
||||
|
||||
dispatch({
|
||||
type: actions.ADD_MESSAGE,
|
||||
server,
|
||||
network,
|
||||
tab: to,
|
||||
message: {
|
||||
from: state.servers[server].nick,
|
||||
from: state.networks[network].nick,
|
||||
content
|
||||
},
|
||||
wrapWidth,
|
||||
|
@ -519,21 +526,21 @@ export function sendMessage(content, to, server) {
|
|||
windowWidth,
|
||||
socket: {
|
||||
type: 'message',
|
||||
data: { content, to, server }
|
||||
data: { content, to, network }
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function addMessage(message, server, to) {
|
||||
const tab = getMessageTab(server, to);
|
||||
export function addMessage(message, network, to) {
|
||||
const tab = getMessageTab(network, to);
|
||||
|
||||
return (dispatch, getState) => {
|
||||
const { wrapWidth, charWidth, windowWidth } = getApp(getState());
|
||||
|
||||
dispatch({
|
||||
type: actions.ADD_MESSAGE,
|
||||
server,
|
||||
network,
|
||||
tab,
|
||||
message,
|
||||
wrapWidth,
|
||||
|
@ -543,8 +550,8 @@ export function addMessage(message, server, to) {
|
|||
};
|
||||
}
|
||||
|
||||
export function addMessages(messages, server, to, prepend, next) {
|
||||
const tab = getMessageTab(server, to);
|
||||
export function addMessages(messages, network, to, prepend, next) {
|
||||
const tab = getMessageTab(network, to);
|
||||
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
|
@ -558,7 +565,7 @@ export function addMessages(messages, server, to, prepend, next) {
|
|||
|
||||
dispatch({
|
||||
type: actions.ADD_MESSAGES,
|
||||
server,
|
||||
network,
|
||||
tab,
|
||||
messages,
|
||||
prepend,
|
||||
|
@ -569,7 +576,7 @@ export function addMessages(messages, server, to, prepend, next) {
|
|||
};
|
||||
}
|
||||
|
||||
export function addEvent(server, tab, type, ...params) {
|
||||
export function addEvent(network, tab, type, ...params) {
|
||||
return addMessage(
|
||||
{
|
||||
type: 'info',
|
||||
|
@ -581,12 +588,12 @@ export function addEvent(server, tab, type, ...params) {
|
|||
}
|
||||
]
|
||||
},
|
||||
server,
|
||||
network,
|
||||
tab
|
||||
);
|
||||
}
|
||||
|
||||
export function broadcastEvent(server, channels, type, ...params) {
|
||||
export function broadcastEvent(network, channels, type, ...params) {
|
||||
const now = unix();
|
||||
|
||||
return addMessages(
|
||||
|
@ -601,29 +608,29 @@ export function broadcastEvent(server, channels, type, ...params) {
|
|||
}
|
||||
]
|
||||
})),
|
||||
server
|
||||
network
|
||||
);
|
||||
}
|
||||
|
||||
export function broadcast(message, server, channels) {
|
||||
export function broadcast(message, network, channels) {
|
||||
return addMessages(
|
||||
channels.map(channel => ({
|
||||
tab: channel,
|
||||
content: message,
|
||||
type: 'info'
|
||||
})),
|
||||
server
|
||||
network
|
||||
);
|
||||
}
|
||||
|
||||
export function print(message, server, channel, type) {
|
||||
export function print(message, network, channel, type) {
|
||||
if (Array.isArray(message)) {
|
||||
return addMessages(
|
||||
message.map(line => ({
|
||||
content: line,
|
||||
type
|
||||
})),
|
||||
server,
|
||||
network,
|
||||
channel
|
||||
);
|
||||
}
|
||||
|
@ -633,32 +640,32 @@ export function print(message, server, channel, type) {
|
|||
content: message,
|
||||
type
|
||||
},
|
||||
server,
|
||||
network,
|
||||
channel
|
||||
);
|
||||
}
|
||||
|
||||
export function inform(message, server, channel) {
|
||||
return print(message, server, channel, 'info');
|
||||
export function inform(message, network, channel) {
|
||||
return print(message, network, channel, 'info');
|
||||
}
|
||||
|
||||
export function runCommand(command, channel, server) {
|
||||
export function runCommand(command, channel, network) {
|
||||
return {
|
||||
type: actions.COMMAND,
|
||||
command,
|
||||
channel,
|
||||
server
|
||||
network
|
||||
};
|
||||
}
|
||||
|
||||
export function raw(message, server) {
|
||||
export function raw(message, network) {
|
||||
return {
|
||||
type: actions.RAW,
|
||||
message,
|
||||
server,
|
||||
network,
|
||||
socket: {
|
||||
type: 'raw',
|
||||
data: { message, server }
|
||||
data: { message, network }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
229
client/js/state/networks.js
Normal file
229
client/js/state/networks.js
Normal file
|
@ -0,0 +1,229 @@
|
|||
import { createSelector } from 'reselect';
|
||||
import get from 'lodash/get';
|
||||
import createReducer from 'utils/createReducer';
|
||||
import { getSelectedTab, updateSelection } from './tab';
|
||||
import * as actions from './actions';
|
||||
|
||||
export const getNetworks = state => state.networks;
|
||||
|
||||
export const getCurrentNick = createSelector(
|
||||
getNetworks,
|
||||
getSelectedTab,
|
||||
(networks, tab) => {
|
||||
if (!networks[tab.network]) {
|
||||
return;
|
||||
}
|
||||
const { editedNick } = networks[tab.network];
|
||||
if (editedNick === null) {
|
||||
return networks[tab.network].nick;
|
||||
}
|
||||
return editedNick;
|
||||
}
|
||||
);
|
||||
|
||||
export const getCurrentNetworkName = createSelector(
|
||||
getNetworks,
|
||||
getSelectedTab,
|
||||
(networks, tab) => get(networks, [tab.network, 'name'])
|
||||
);
|
||||
|
||||
export const getCurrentNetworkError = createSelector(
|
||||
getNetworks,
|
||||
getSelectedTab,
|
||||
(networks, tab) => get(networks, [tab.network, 'error'], null)
|
||||
);
|
||||
|
||||
export default createReducer(
|
||||
{},
|
||||
{
|
||||
[actions.CONNECT](state, { host, nick, name }) {
|
||||
if (!state[host]) {
|
||||
state[host] = {
|
||||
nick,
|
||||
editedNick: null,
|
||||
name: name || host,
|
||||
connected: false,
|
||||
error: null,
|
||||
features: {}
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
[actions.DISCONNECT](state, { network }) {
|
||||
delete state[network];
|
||||
},
|
||||
|
||||
[actions.SET_NETWORK_NAME](state, { network, name }) {
|
||||
state[network].name = name;
|
||||
},
|
||||
|
||||
[actions.SET_NICK](state, { network, nick, editing }) {
|
||||
if (editing) {
|
||||
state[network].editedNick = nick;
|
||||
} else if (nick === '') {
|
||||
state[network].editedNick = null;
|
||||
}
|
||||
},
|
||||
|
||||
[actions.socket.NICK](state, { network, oldNick, newNick }) {
|
||||
if (!oldNick || oldNick === state[network].nick) {
|
||||
state[network].nick = newNick;
|
||||
state[network].editedNick = null;
|
||||
}
|
||||
},
|
||||
|
||||
[actions.socket.NICK_FAIL](state, { network }) {
|
||||
state[network].editedNick = null;
|
||||
},
|
||||
|
||||
[actions.socket.NETWORKS](state, { data }) {
|
||||
if (data) {
|
||||
data.forEach(
|
||||
({ host, name = host, nick, connected, error, features = {} }) => {
|
||||
state[host] = {
|
||||
name,
|
||||
nick,
|
||||
connected,
|
||||
error,
|
||||
features,
|
||||
editedNick: null
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
[actions.socket.CONNECTION_UPDATE](state, { network, connected, error }) {
|
||||
if (state[network]) {
|
||||
state[network].connected = connected;
|
||||
state[network].error = error;
|
||||
}
|
||||
},
|
||||
|
||||
[actions.socket.FEATURES](state, { network, features }) {
|
||||
const srv = state[network];
|
||||
if (srv) {
|
||||
srv.features = features;
|
||||
|
||||
if (features.NETWORK && srv.name === network) {
|
||||
srv.name = features.NETWORK;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export function connect(config) {
|
||||
return {
|
||||
type: actions.CONNECT,
|
||||
...config,
|
||||
socket: {
|
||||
type: 'connect',
|
||||
data: config
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function disconnect(network) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: actions.DISCONNECT,
|
||||
network,
|
||||
socket: {
|
||||
type: 'quit',
|
||||
data: { network }
|
||||
}
|
||||
});
|
||||
dispatch(updateSelection());
|
||||
};
|
||||
}
|
||||
|
||||
export function reconnect(network, settings) {
|
||||
return {
|
||||
type: actions.RECONNECT,
|
||||
network,
|
||||
settings,
|
||||
socket: {
|
||||
type: 'reconnect',
|
||||
data: {
|
||||
...settings,
|
||||
network
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function whois(user, network) {
|
||||
return {
|
||||
type: actions.WHOIS,
|
||||
user,
|
||||
network,
|
||||
socket: {
|
||||
type: 'whois',
|
||||
data: { user, network }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function away(message, network) {
|
||||
return {
|
||||
type: actions.AWAY,
|
||||
message,
|
||||
network,
|
||||
socket: {
|
||||
type: 'away',
|
||||
data: { message, network }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function setNick(nick, network, editing) {
|
||||
nick = nick.trim().replace(' ', '');
|
||||
|
||||
const action = {
|
||||
type: actions.SET_NICK,
|
||||
nick,
|
||||
network,
|
||||
editing
|
||||
};
|
||||
|
||||
if (!editing && nick !== '') {
|
||||
action.socket = {
|
||||
type: 'nick',
|
||||
data: {
|
||||
newNick: nick,
|
||||
network
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
export function isValidNetworkName(name) {
|
||||
return name.trim() !== '';
|
||||
}
|
||||
|
||||
export function setNetworkName(name, network) {
|
||||
const action = {
|
||||
type: actions.SET_NETWORK_NAME,
|
||||
name,
|
||||
network
|
||||
};
|
||||
|
||||
if (isValidNetworkName(name)) {
|
||||
action.socket = {
|
||||
type: 'set_network_name',
|
||||
data: {
|
||||
name,
|
||||
network
|
||||
},
|
||||
debounce: {
|
||||
delay: 500,
|
||||
key: `network_name:${network}`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
|
@ -5,13 +5,13 @@ import * as actions from './actions';
|
|||
|
||||
export const getPrivateChats = state => state.privateChats;
|
||||
|
||||
function open(state, server, nick) {
|
||||
if (!state[server]) {
|
||||
state[server] = [];
|
||||
function open(state, network, nick) {
|
||||
if (!state[network]) {
|
||||
state[network] = [];
|
||||
}
|
||||
if (!state[server].includes(nick)) {
|
||||
state[server].push(nick);
|
||||
state[server] = sortBy(state[server], v => v.toLowerCase());
|
||||
if (!state[network].includes(nick)) {
|
||||
state[network].push(nick);
|
||||
state[network] = sortBy(state[network], v => v.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,63 +19,63 @@ export default createReducer(
|
|||
{},
|
||||
{
|
||||
[actions.OPEN_PRIVATE_CHAT](state, action) {
|
||||
open(state, action.server, action.nick);
|
||||
open(state, action.network, action.nick);
|
||||
},
|
||||
|
||||
[actions.CLOSE_PRIVATE_CHAT](state, { server, nick }) {
|
||||
const i = state[server]?.findIndex(n => n === nick);
|
||||
[actions.CLOSE_PRIVATE_CHAT](state, { network, nick }) {
|
||||
const i = state[network]?.findIndex(n => n === nick);
|
||||
if (i !== -1) {
|
||||
state[server].splice(i, 1);
|
||||
state[network].splice(i, 1);
|
||||
}
|
||||
},
|
||||
|
||||
[actions.PRIVATE_CHATS](state, { privateChats }) {
|
||||
privateChats.forEach(({ server, name }) => {
|
||||
if (!state[server]) {
|
||||
state[server] = [];
|
||||
privateChats.forEach(({ network, name }) => {
|
||||
if (!state[network]) {
|
||||
state[network] = [];
|
||||
}
|
||||
|
||||
state[server].push(name);
|
||||
state[network].push(name);
|
||||
});
|
||||
},
|
||||
|
||||
[actions.socket.PM](state, action) {
|
||||
if (action.from.indexOf('.') === -1) {
|
||||
open(state, action.server, action.from);
|
||||
open(state, action.network, action.from);
|
||||
}
|
||||
},
|
||||
|
||||
[actions.DISCONNECT](state, { server }) {
|
||||
delete state[server];
|
||||
[actions.DISCONNECT](state, { network }) {
|
||||
delete state[network];
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export function openPrivateChat(server, nick) {
|
||||
export function openPrivateChat(network, nick) {
|
||||
return (dispatch, getState) => {
|
||||
if (!getState().privateChats[server]?.includes(nick)) {
|
||||
if (!getState().privateChats[network]?.includes(nick)) {
|
||||
dispatch({
|
||||
type: actions.OPEN_PRIVATE_CHAT,
|
||||
server,
|
||||
network,
|
||||
nick,
|
||||
socket: {
|
||||
type: 'open_dm',
|
||||
data: { server, name: nick }
|
||||
data: { network, name: nick }
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function closePrivateChat(server, nick) {
|
||||
export function closePrivateChat(network, nick) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: actions.CLOSE_PRIVATE_CHAT,
|
||||
server,
|
||||
network,
|
||||
nick,
|
||||
socket: {
|
||||
type: 'close_dm',
|
||||
data: { server, name: nick }
|
||||
data: { network, name: nick }
|
||||
}
|
||||
});
|
||||
dispatch(updateSelection());
|
||||
|
|
|
@ -18,15 +18,15 @@ export default createReducer(initialState, {
|
|||
}
|
||||
});
|
||||
|
||||
export function searchMessages(server, channel, phrase) {
|
||||
export function searchMessages(network, channel, phrase) {
|
||||
return {
|
||||
type: actions.SEARCH_MESSAGES,
|
||||
server,
|
||||
network,
|
||||
channel,
|
||||
phrase,
|
||||
socket: {
|
||||
type: 'search',
|
||||
data: { server, channel, phrase }
|
||||
data: { network, channel, phrase }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { createSelector } from 'reselect';
|
||||
import get from 'lodash/get';
|
||||
import { getServers } from './servers';
|
||||
import { getNetworks } from './networks';
|
||||
import { getSelectedTab } from './tab';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const getSelectedTabTitle = createSelector(
|
||||
getSelectedTab,
|
||||
getServers,
|
||||
(tab, servers) => tab.name || get(servers, [tab.server, 'name'])
|
||||
getNetworks,
|
||||
(tab, networks) => tab.name || get(networks, [tab.network, 'name'])
|
||||
);
|
||||
|
|
|
@ -1,222 +0,0 @@
|
|||
import { createSelector } from 'reselect';
|
||||
import get from 'lodash/get';
|
||||
import createReducer from 'utils/createReducer';
|
||||
import { getSelectedTab, updateSelection } from './tab';
|
||||
import * as actions from './actions';
|
||||
|
||||
export const getServers = state => state.servers;
|
||||
|
||||
export const getCurrentNick = createSelector(
|
||||
getServers,
|
||||
getSelectedTab,
|
||||
(servers, tab) => {
|
||||
if (!servers[tab.server]) {
|
||||
return;
|
||||
}
|
||||
const { editedNick } = servers[tab.server];
|
||||
if (editedNick === null) {
|
||||
return servers[tab.server].nick;
|
||||
}
|
||||
return editedNick;
|
||||
}
|
||||
);
|
||||
|
||||
export const getCurrentServerName = createSelector(
|
||||
getServers,
|
||||
getSelectedTab,
|
||||
(servers, tab) => get(servers, [tab.server, 'name'])
|
||||
);
|
||||
|
||||
export const getCurrentServerStatus = createSelector(
|
||||
getServers,
|
||||
getSelectedTab,
|
||||
(servers, tab) => get(servers, [tab.server, 'status'], {})
|
||||
);
|
||||
|
||||
export default createReducer(
|
||||
{},
|
||||
{
|
||||
[actions.CONNECT](state, { host, nick, name }) {
|
||||
if (!state[host]) {
|
||||
state[host] = {
|
||||
nick,
|
||||
editedNick: null,
|
||||
name: name || host,
|
||||
status: {
|
||||
connected: false,
|
||||
error: null
|
||||
},
|
||||
features: {}
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
[actions.DISCONNECT](state, { server }) {
|
||||
delete state[server];
|
||||
},
|
||||
|
||||
[actions.SET_SERVER_NAME](state, { server, name }) {
|
||||
state[server].name = name;
|
||||
},
|
||||
|
||||
[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 = host, nick, status, features = {} }) => {
|
||||
state[host] = { name, nick, status, features, editedNick: null };
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
[actions.socket.CONNECTION_UPDATE](state, { server, connected, error }) {
|
||||
if (state[server]) {
|
||||
state[server].status.connected = connected;
|
||||
state[server].status.error = error;
|
||||
}
|
||||
},
|
||||
|
||||
[actions.socket.FEATURES](state, { server, features }) {
|
||||
const srv = state[server];
|
||||
if (srv) {
|
||||
srv.features = features;
|
||||
|
||||
if (features.NETWORK && srv.name === server) {
|
||||
srv.name = features.NETWORK;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export function connect(config) {
|
||||
return {
|
||||
type: actions.CONNECT,
|
||||
...config,
|
||||
socket: {
|
||||
type: 'connect',
|
||||
data: config
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function disconnect(server) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: actions.DISCONNECT,
|
||||
server,
|
||||
socket: {
|
||||
type: 'quit',
|
||||
data: { server }
|
||||
}
|
||||
});
|
||||
dispatch(updateSelection());
|
||||
};
|
||||
}
|
||||
|
||||
export function reconnect(server, settings) {
|
||||
return {
|
||||
type: actions.RECONNECT,
|
||||
server,
|
||||
settings,
|
||||
socket: {
|
||||
type: 'reconnect',
|
||||
data: {
|
||||
...settings,
|
||||
server
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function whois(user, server) {
|
||||
return {
|
||||
type: actions.WHOIS,
|
||||
user,
|
||||
server,
|
||||
socket: {
|
||||
type: 'whois',
|
||||
data: { user, server }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function away(message, server) {
|
||||
return {
|
||||
type: actions.AWAY,
|
||||
message,
|
||||
server,
|
||||
socket: {
|
||||
type: 'away',
|
||||
data: { message, server }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function setNick(nick, server, editing) {
|
||||
nick = nick.trim().replace(' ', '');
|
||||
|
||||
const action = {
|
||||
type: actions.SET_NICK,
|
||||
nick,
|
||||
server,
|
||||
editing
|
||||
};
|
||||
|
||||
if (!editing && nick !== '') {
|
||||
action.socket = {
|
||||
type: 'nick',
|
||||
data: {
|
||||
newNick: nick,
|
||||
server
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
export function isValidServerName(name) {
|
||||
return name.trim() !== '';
|
||||
}
|
||||
|
||||
export function setServerName(name, server) {
|
||||
const action = {
|
||||
type: actions.SET_SERVER_NAME,
|
||||
name,
|
||||
server
|
||||
};
|
||||
|
||||
if (isValidServerName(name)) {
|
||||
action.socket = {
|
||||
type: 'set_server_name',
|
||||
data: {
|
||||
name,
|
||||
server
|
||||
},
|
||||
debounce: {
|
||||
delay: 500,
|
||||
key: `server_name:${server}`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
|
@ -12,7 +12,7 @@ const initialState = {
|
|||
|
||||
function selectTab(state, action) {
|
||||
state.selected = {
|
||||
server: action.server,
|
||||
network: action.network,
|
||||
name: action.name
|
||||
};
|
||||
state.history.push(state.selected);
|
||||
|
@ -25,18 +25,19 @@ export default createReducer(initialState, {
|
|||
|
||||
[actions.PART](state, action) {
|
||||
state.history = state.history.filter(
|
||||
tab => !(tab.server === action.server && tab.name === action.channels[0])
|
||||
tab =>
|
||||
!(tab.network === action.network && tab.name === action.channels[0])
|
||||
);
|
||||
},
|
||||
|
||||
[actions.CLOSE_PRIVATE_CHAT](state, action) {
|
||||
state.history = state.history.filter(
|
||||
tab => !(tab.server === action.server && tab.name === action.nick)
|
||||
tab => !(tab.network === action.network && tab.name === action.nick)
|
||||
);
|
||||
},
|
||||
|
||||
[actions.DISCONNECT](state, action) {
|
||||
state.history = state.history.filter(tab => tab.server !== action.server);
|
||||
state.history = state.history.filter(tab => tab.network !== action.network);
|
||||
},
|
||||
|
||||
[LOCATION_CHANGED](state, action) {
|
||||
|
@ -49,30 +50,30 @@ export default createReducer(initialState, {
|
|||
}
|
||||
});
|
||||
|
||||
export function select(server, name, doReplace) {
|
||||
export function select(network, name, doReplace) {
|
||||
const navigate = doReplace ? replace : push;
|
||||
if (name) {
|
||||
return navigate(`/${server}/${encodeURIComponent(name)}`);
|
||||
return navigate(`/${network}/${encodeURIComponent(name)}`);
|
||||
}
|
||||
return navigate(`/${server}`);
|
||||
return navigate(`/${network}`);
|
||||
}
|
||||
|
||||
export function tabExists(
|
||||
{ server, name },
|
||||
{ servers, channels, privateChats }
|
||||
{ network, name },
|
||||
{ networks, channels, privateChats }
|
||||
) {
|
||||
return (
|
||||
(name && get(channels, [server, name])) ||
|
||||
(!name && server && servers[server]) ||
|
||||
(name && find(privateChats[server], nick => nick === name))
|
||||
(name && get(channels, [network, name])) ||
|
||||
(!name && network && networks[network]) ||
|
||||
(name && find(privateChats[network], nick => nick === name))
|
||||
);
|
||||
}
|
||||
|
||||
function parseTabCookie() {
|
||||
const cookie = Cookie.get('tab');
|
||||
if (cookie) {
|
||||
const [server, name = null] = cookie.split(/;(.+)/);
|
||||
return { server, name };
|
||||
const [network, name = null] = cookie.split(/;(.+)/);
|
||||
return { network, name };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -88,35 +89,35 @@ export function updateSelection(tryCookie) {
|
|||
if (tryCookie) {
|
||||
const tab = parseTabCookie();
|
||||
if (tab && tabExists(tab, state)) {
|
||||
return dispatch(select(tab.server, tab.name, true));
|
||||
return dispatch(select(tab.network, tab.name, true));
|
||||
}
|
||||
}
|
||||
|
||||
const { servers } = state;
|
||||
const { networks } = state;
|
||||
const { history } = state.tab;
|
||||
const { server } = state.tab.selected;
|
||||
const serverAddrs = Object.keys(servers);
|
||||
const { network } = state.tab.selected;
|
||||
const networkAddrs = Object.keys(networks);
|
||||
|
||||
if (serverAddrs.length === 0) {
|
||||
if (networkAddrs.length === 0) {
|
||||
dispatch(replace('/connect'));
|
||||
} else if (
|
||||
history.length > 0 &&
|
||||
tabExists(history[history.length - 1], state)
|
||||
) {
|
||||
const tab = history[history.length - 1];
|
||||
dispatch(select(tab.server, tab.name, true));
|
||||
} else if (servers[server]) {
|
||||
dispatch(select(server, null, true));
|
||||
dispatch(select(tab.network, tab.name, true));
|
||||
} else if (networks[network]) {
|
||||
dispatch(select(network, null, true));
|
||||
} else {
|
||||
dispatch(select(serverAddrs.sort()[0], null, true));
|
||||
dispatch(select(networkAddrs.sort()[0], null, true));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function setSelectedTab(server, name = null) {
|
||||
export function setSelectedTab(network, name = null) {
|
||||
return {
|
||||
type: actions.SELECT_TAB,
|
||||
server,
|
||||
network,
|
||||
name
|
||||
};
|
||||
}
|
||||
|
|
|
@ -19,17 +19,17 @@ export function isChannel(name) {
|
|||
return typeof name === 'string' && name[0] === '#';
|
||||
}
|
||||
|
||||
export function stringifyTab(server, name) {
|
||||
if (typeof server === 'object') {
|
||||
if (server.name) {
|
||||
return `${server.server};${server.name}`;
|
||||
export function stringifyTab(network, name) {
|
||||
if (typeof network === 'object') {
|
||||
if (network.name) {
|
||||
return `${network.network};${network.name}`;
|
||||
}
|
||||
return server.server;
|
||||
return network.network;
|
||||
}
|
||||
if (name) {
|
||||
return `${server};${name}`;
|
||||
return `${network};${name}`;
|
||||
}
|
||||
return server;
|
||||
return network;
|
||||
}
|
||||
|
||||
function isString(s, maxLength) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue