2017-05-26 06:20:00 +00:00
|
|
|
import { createSelector } from 'reselect';
|
2018-04-25 03:36:27 +00:00
|
|
|
import get from 'lodash/get';
|
|
|
|
import sortBy from 'lodash/sortBy';
|
2018-04-05 19:13:32 +00:00
|
|
|
import createReducer from 'utils/createReducer';
|
2018-04-25 03:36:27 +00:00
|
|
|
import { find, findIndex } from 'utils';
|
2017-05-26 06:20:00 +00:00
|
|
|
import { getSelectedTab, updateSelection } from './tab';
|
|
|
|
import * as actions from './actions';
|
2015-12-28 23:34:32 +00:00
|
|
|
|
2017-05-10 01:47:34 +00:00
|
|
|
const modePrefixes = [
|
|
|
|
{ mode: 'q', prefix: '~' }, // Owner
|
|
|
|
{ mode: 'a', prefix: '&' }, // Admin
|
|
|
|
{ mode: 'o', prefix: '@' }, // Op
|
|
|
|
{ mode: 'h', prefix: '%' }, // Halfop
|
2018-03-25 00:34:41 +00:00
|
|
|
{ mode: 'v', prefix: '+' } // Voice
|
2017-05-10 01:47:34 +00:00
|
|
|
];
|
2015-12-28 23:34:32 +00:00
|
|
|
|
2018-04-25 03:36:27 +00:00
|
|
|
function getRenderName(user) {
|
2017-05-10 01:47:34 +00:00
|
|
|
for (let i = 0; i < modePrefixes.length; i++) {
|
|
|
|
if (user.mode.indexOf(modePrefixes[i].mode) !== -1) {
|
2018-04-25 03:36:27 +00:00
|
|
|
return `${modePrefixes[i].prefix}${user.nick}`;
|
2017-05-10 01:47:34 +00:00
|
|
|
}
|
2015-12-28 23:34:32 +00:00
|
|
|
}
|
|
|
|
|
2018-04-25 03:36:27 +00:00
|
|
|
return user.nick;
|
2015-12-28 23:34:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function createUser(nick, mode) {
|
2018-04-25 03:36:27 +00:00
|
|
|
const user = {
|
|
|
|
nick,
|
|
|
|
mode: mode || ''
|
|
|
|
};
|
|
|
|
user.renderName = getRenderName(user);
|
|
|
|
return user;
|
2015-12-28 23:34:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function loadUser(nick) {
|
|
|
|
let mode;
|
|
|
|
|
2017-05-10 01:47:34 +00:00
|
|
|
for (let i = 0; i < modePrefixes.length; i++) {
|
|
|
|
if (nick[0] === modePrefixes[i].prefix) {
|
2018-03-25 00:34:41 +00:00
|
|
|
({ mode } = modePrefixes[i]);
|
2017-05-10 01:47:34 +00:00
|
|
|
}
|
2015-12-28 23:34:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (mode) {
|
|
|
|
return createUser(nick.slice(1), mode);
|
|
|
|
}
|
|
|
|
|
2017-05-10 01:47:34 +00:00
|
|
|
return createUser(nick);
|
2015-12-28 23:34:32 +00:00
|
|
|
}
|
|
|
|
|
2018-04-25 03:36:27 +00:00
|
|
|
function removeUser(users, nick) {
|
|
|
|
const i = findIndex(users, u => u.nick === nick);
|
|
|
|
if (i !== -1) {
|
|
|
|
users.splice(i, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function init(state, server, channel) {
|
|
|
|
if (!state[server]) {
|
|
|
|
state[server] = {};
|
|
|
|
}
|
|
|
|
if (channel && !state[server][channel]) {
|
|
|
|
state[server][channel] = { users: [] };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-05 19:13:32 +00:00
|
|
|
export function compareUsers(a, b) {
|
2015-12-28 23:34:32 +00:00
|
|
|
a = a.renderName.toLowerCase();
|
|
|
|
b = b.renderName.toLowerCase();
|
|
|
|
|
2017-05-10 01:47:34 +00:00
|
|
|
for (let i = 0; i < modePrefixes.length; i++) {
|
2018-03-25 00:34:41 +00:00
|
|
|
const { prefix } = modePrefixes[i];
|
2017-05-10 01:47:34 +00:00
|
|
|
|
|
|
|
if (a[0] === prefix && b[0] !== prefix) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (b[0] === prefix && a[0] !== prefix) {
|
|
|
|
return 1;
|
|
|
|
}
|
2015-12-28 23:34:32 +00:00
|
|
|
}
|
2017-05-10 01:47:34 +00:00
|
|
|
|
2015-12-28 23:34:32 +00:00
|
|
|
if (a < b) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (a > b) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-05-26 06:20:00 +00:00
|
|
|
export const getChannels = state => state.channels;
|
|
|
|
|
2018-04-05 23:46:22 +00:00
|
|
|
export const getSortedChannels = createSelector(getChannels, channels =>
|
2018-04-25 03:36:27 +00:00
|
|
|
sortBy(
|
|
|
|
Object.keys(channels).map(server => ({
|
|
|
|
address: server,
|
|
|
|
channels: sortBy(Object.keys(channels[server]), channel =>
|
|
|
|
channel.toLowerCase()
|
2018-04-05 23:46:22 +00:00
|
|
|
)
|
2018-04-25 03:36:27 +00:00
|
|
|
})),
|
|
|
|
server => server.address.toLowerCase()
|
|
|
|
)
|
2017-06-29 04:56:05 +00:00
|
|
|
);
|
|
|
|
|
2017-05-26 06:20:00 +00:00
|
|
|
export const getSelectedChannel = createSelector(
|
|
|
|
getSelectedTab,
|
|
|
|
getChannels,
|
2018-04-25 03:36:27 +00:00
|
|
|
(tab, channels) => get(channels, [tab.server, tab.name])
|
2017-05-26 06:20:00 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
export const getSelectedChannelUsers = createSelector(
|
|
|
|
getSelectedChannel,
|
2018-04-25 03:36:27 +00:00
|
|
|
channel => {
|
|
|
|
if (channel) {
|
|
|
|
return channel.users.concat().sort(compareUsers);
|
|
|
|
}
|
|
|
|
return [];
|
|
|
|
}
|
2017-05-26 06:20:00 +00:00
|
|
|
);
|
|
|
|
|
2018-04-25 03:36:27 +00:00
|
|
|
export default createReducer(
|
|
|
|
{},
|
|
|
|
{
|
|
|
|
[actions.PART](state, { server, channels }) {
|
|
|
|
channels.forEach(channel => delete state[server][channel]);
|
|
|
|
},
|
|
|
|
|
|
|
|
[actions.socket.JOIN](state, { server, channels, user }) {
|
|
|
|
const channel = channels[0];
|
|
|
|
init(state, server, channel);
|
|
|
|
state[server][channel].users.push(createUser(user));
|
|
|
|
},
|
|
|
|
|
|
|
|
[actions.socket.PART](state, { server, channel, user }) {
|
|
|
|
if (state[server][channel]) {
|
|
|
|
removeUser(state[server][channel].users, user);
|
2017-04-23 06:45:35 +00:00
|
|
|
}
|
2018-04-25 03:36:27 +00:00
|
|
|
},
|
2017-04-23 06:45:35 +00:00
|
|
|
|
2018-04-25 03:36:27 +00:00
|
|
|
[actions.socket.QUIT](state, { server, user }) {
|
|
|
|
Object.keys(state[server]).forEach(channel => {
|
|
|
|
removeUser(state[server][channel].users, user);
|
2017-07-06 04:46:53 +00:00
|
|
|
});
|
2018-04-25 03:36:27 +00:00
|
|
|
},
|
2015-12-28 23:34:32 +00:00
|
|
|
|
2018-04-25 03:36:27 +00:00
|
|
|
[actions.socket.NICK](state, { server, oldNick, newNick }) {
|
|
|
|
Object.keys(state[server]).forEach(channel => {
|
|
|
|
const user = find(
|
|
|
|
state[server][channel].users,
|
|
|
|
u => u.nick === oldNick
|
2018-04-05 23:46:22 +00:00
|
|
|
);
|
2018-04-25 03:36:27 +00:00
|
|
|
if (user) {
|
|
|
|
user.nick = newNick;
|
|
|
|
user.renderName = getRenderName(user);
|
|
|
|
}
|
2015-12-28 23:34:32 +00:00
|
|
|
});
|
2018-04-25 03:36:27 +00:00
|
|
|
},
|
2015-12-28 23:34:32 +00:00
|
|
|
|
2018-04-25 03:36:27 +00:00
|
|
|
[actions.socket.USERS](state, { server, channel, users }) {
|
|
|
|
init(state, server, channel);
|
|
|
|
state[server][channel].users = users.map(nick => loadUser(nick));
|
|
|
|
},
|
2015-12-28 23:34:32 +00:00
|
|
|
|
2018-04-25 03:36:27 +00:00
|
|
|
[actions.socket.TOPIC](state, { server, channel, topic }) {
|
|
|
|
init(state, server, channel);
|
|
|
|
state[server][channel].topic = topic;
|
|
|
|
},
|
|
|
|
|
|
|
|
[actions.socket.MODE](state, { server, channel, user, remove, add }) {
|
|
|
|
const u = find(state[server][channel].users, v => v.nick === user);
|
|
|
|
if (u) {
|
2018-05-25 21:54:36 +00:00
|
|
|
if (remove) {
|
|
|
|
let j = remove.length;
|
|
|
|
while (j--) {
|
|
|
|
u.mode = u.mode.replace(remove[j], '');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (add) {
|
|
|
|
u.mode += add;
|
2015-12-28 23:34:32 +00:00
|
|
|
}
|
|
|
|
|
2018-04-25 03:36:27 +00:00
|
|
|
u.renderName = getRenderName(u);
|
|
|
|
}
|
|
|
|
},
|
2016-01-14 04:56:53 +00:00
|
|
|
|
2018-04-25 03:36:27 +00:00
|
|
|
[actions.socket.CHANNELS](state, { data }) {
|
|
|
|
if (data) {
|
|
|
|
data.forEach(({ server, name, topic }) => {
|
|
|
|
init(state, server, name);
|
|
|
|
state[server][name].topic = topic;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
2015-12-28 23:34:32 +00:00
|
|
|
|
2018-04-25 03:36:27 +00:00
|
|
|
[actions.socket.SERVERS](state, { data }) {
|
|
|
|
if (data) {
|
|
|
|
data.forEach(({ host }) => init(state, host));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
[actions.CONNECT](state, { host }) {
|
|
|
|
init(state, host);
|
|
|
|
},
|
|
|
|
|
|
|
|
[actions.DISCONNECT](state, { server }) {
|
|
|
|
delete state[server];
|
|
|
|
}
|
2015-12-28 23:34:32 +00:00
|
|
|
}
|
2018-04-25 03:36:27 +00:00
|
|
|
);
|
2017-05-26 06:20:00 +00:00
|
|
|
|
|
|
|
export function join(channels, server) {
|
|
|
|
return {
|
|
|
|
type: actions.JOIN,
|
|
|
|
channels,
|
|
|
|
server,
|
|
|
|
socket: {
|
|
|
|
type: 'join',
|
|
|
|
data: { channels, server }
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export function part(channels, server) {
|
|
|
|
return dispatch => {
|
|
|
|
dispatch({
|
|
|
|
type: actions.PART,
|
|
|
|
channels,
|
|
|
|
server,
|
|
|
|
socket: {
|
|
|
|
type: 'part',
|
|
|
|
data: { channels, server }
|
|
|
|
}
|
|
|
|
});
|
|
|
|
dispatch(updateSelection());
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export function invite(user, channel, server) {
|
|
|
|
return {
|
|
|
|
type: actions.INVITE,
|
|
|
|
user,
|
|
|
|
channel,
|
|
|
|
server,
|
|
|
|
socket: {
|
|
|
|
type: 'invite',
|
|
|
|
data: { user, channel, server }
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export function kick(user, channel, server) {
|
|
|
|
return {
|
|
|
|
type: actions.KICK,
|
|
|
|
user,
|
|
|
|
channel,
|
|
|
|
server,
|
|
|
|
socket: {
|
|
|
|
type: 'kick',
|
|
|
|
data: { user, channel, server }
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2017-05-28 05:20:43 +00:00
|
|
|
|
|
|
|
export function setTopic(topic, channel, server) {
|
|
|
|
return {
|
|
|
|
type: actions.SET_TOPIC,
|
|
|
|
topic,
|
|
|
|
channel,
|
|
|
|
server,
|
|
|
|
socket: {
|
|
|
|
type: 'topic',
|
|
|
|
data: { topic, channel, server }
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|