Switch to redux and webpack
This commit is contained in:
parent
b247287075
commit
e389454535
97 changed files with 2722 additions and 2656 deletions
191
client/src/js/reducers/channels.js
Normal file
191
client/src/js/reducers/channels.js
Normal file
|
@ -0,0 +1,191 @@
|
|||
import { Map, List, Record } from 'immutable';
|
||||
import createReducer from '../util/createReducer';
|
||||
import * as actions from '../actions';
|
||||
|
||||
const User = Record({
|
||||
nick: null,
|
||||
renderName: null,
|
||||
mode: ''
|
||||
});
|
||||
|
||||
function updateRenderName(user) {
|
||||
let name = user.nick;
|
||||
|
||||
if (user.mode.indexOf('o') !== -1) {
|
||||
name = '@' + name;
|
||||
} else if (user.mode.indexOf('v') !== -1) {
|
||||
name = '+' + name;
|
||||
}
|
||||
|
||||
return user.set('renderName', name);
|
||||
}
|
||||
|
||||
function createUser(nick, mode) {
|
||||
return updateRenderName(new User({
|
||||
nick,
|
||||
renderName: nick,
|
||||
mode: mode || ''
|
||||
}));
|
||||
}
|
||||
|
||||
function loadUser(nick) {
|
||||
let mode;
|
||||
|
||||
if (nick[0] === '@') {
|
||||
mode = 'o';
|
||||
} else if (nick[0] === '+') {
|
||||
mode = 'v';
|
||||
}
|
||||
|
||||
if (mode) {
|
||||
return createUser(nick.slice(1), mode);
|
||||
}
|
||||
|
||||
return createUser(nick, mode);
|
||||
}
|
||||
|
||||
function compareUsers(a, b) {
|
||||
a = a.renderName.toLowerCase();
|
||||
b = b.renderName.toLowerCase();
|
||||
|
||||
if (a[0] === '@' && b[0] !== '@') {
|
||||
return -1;
|
||||
}
|
||||
if (b[0] === '@' && a[0] !== '@') {
|
||||
return 1;
|
||||
}
|
||||
if (a[0] === '+' && b[0] !== '+') {
|
||||
return -1;
|
||||
}
|
||||
if (b[0] === '+' && a[0] !== '+') {
|
||||
return 1;
|
||||
}
|
||||
if (a < b) {
|
||||
return -1;
|
||||
}
|
||||
if (a > b) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export default createReducer(Map(), {
|
||||
[actions.PART](state, action) {
|
||||
const { channels, server } = action;
|
||||
return state.withMutations(s => {
|
||||
channels.forEach(channel => s.deleteIn([server, channel]));
|
||||
});
|
||||
},
|
||||
|
||||
[actions.SOCKET_JOIN](state, action) {
|
||||
const { server, channels, user } = action;
|
||||
const channel = channels[0];
|
||||
return state
|
||||
.setIn([server, channel, 'name'], channels[0])
|
||||
.updateIn([server, channel, 'users'], List(), users => {
|
||||
return users.push(createUser(user)).sort(compareUsers);
|
||||
});
|
||||
},
|
||||
|
||||
[actions.SOCKET_PART](state, action) {
|
||||
const { server, channels, user } = action;
|
||||
const channel = channels[0];
|
||||
if (state.hasIn([server, channel])) {
|
||||
return state.updateIn([server, channel, 'users'], users => users.filter(u => u.nick !== user));
|
||||
}
|
||||
return state;
|
||||
},
|
||||
|
||||
[actions.SOCKET_QUIT](state, action) {
|
||||
const { server, user } = action;
|
||||
return state.withMutations(s => {
|
||||
s.get(server).forEach((v, channel) => {
|
||||
s.updateIn([server, channel, 'users'], users => users.filter(u => u.nick !== user));
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
[actions.SOCKET_NICK](state, action) {
|
||||
const { server, channels } = action;
|
||||
return state.withMutations(s => {
|
||||
channels.forEach(channel => {
|
||||
s.updateIn([server, channel, 'users'], users => {
|
||||
const i = users.findIndex(user => user.nick === action.old);
|
||||
return users.update(i, user => {
|
||||
return updateRenderName(user.set('nick', action.new));
|
||||
}).sort(compareUsers);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
[actions.SOCKET_USERS](state, action) {
|
||||
const { server, channel, users } = action;
|
||||
return state.setIn([server, channel, 'users'],
|
||||
List(users.map(user => loadUser(user)).sort(compareUsers)));
|
||||
},
|
||||
|
||||
[actions.SOCKET_TOPIC](state, action) {
|
||||
const { server, channel, topic } = action;
|
||||
return state.setIn([server, channel, 'topic'], topic);
|
||||
},
|
||||
|
||||
[actions.SOCKET_MODE](state, action) {
|
||||
const { server, channel, user, remove, add } = action;
|
||||
|
||||
const i = state.getIn([server, channel, 'users']).findIndex(u => u.nick === user);
|
||||
return state
|
||||
.updateIn([server, channel, 'users', i], u => {
|
||||
let mode = u.mode;
|
||||
let j = remove.length;
|
||||
while (j--) {
|
||||
mode = mode.replace(remove[j], '');
|
||||
}
|
||||
|
||||
return updateRenderName(u.set('mode', mode + add));
|
||||
})
|
||||
.updateIn([server, channel, 'users'], users => users.sort(compareUsers));
|
||||
},
|
||||
|
||||
[actions.SOCKET_CHANNELS](state, action) {
|
||||
if (!action.data) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return state.withMutations(s => {
|
||||
action.data.forEach(channel => {
|
||||
s.setIn([channel.server, channel.name], Map({
|
||||
users: List(),
|
||||
topic: channel.topic,
|
||||
name: channel.name
|
||||
}));
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
[actions.SOCKET_SERVERS](state, action) {
|
||||
if (!action.data) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return state.withMutations(s => {
|
||||
action.data.forEach(server => {
|
||||
if (!state.has(server.address)) {
|
||||
s.set(server.address, Map());
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
[actions.CONNECT](state, action) {
|
||||
const { server } = action;
|
||||
if (!state.has(server)) {
|
||||
return state.set(server, Map());
|
||||
}
|
||||
return state;
|
||||
},
|
||||
|
||||
[actions.DISCONNECT](state, action) {
|
||||
return state.delete(action.server);
|
||||
}
|
||||
});
|
9
client/src/js/reducers/environment.js
Normal file
9
client/src/js/reducers/environment.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { Map } from 'immutable';
|
||||
import createReducer from '../util/createReducer';
|
||||
import * as actions from '../actions';
|
||||
|
||||
export default createReducer(Map(), {
|
||||
[actions.SET_ENVIRONMENT](state, action) {
|
||||
return state.set(action.key, action.value);
|
||||
}
|
||||
});
|
24
client/src/js/reducers/index.js
Normal file
24
client/src/js/reducers/index.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { combineReducers } from 'redux';
|
||||
import { routeReducer } from 'redux-simple-router';
|
||||
import channels from './channels';
|
||||
import environment from './environment';
|
||||
import input from './input';
|
||||
import messages from './messages';
|
||||
import privateChats from './privateChats';
|
||||
import search from './search';
|
||||
import servers from './servers';
|
||||
import showMenu from './showMenu';
|
||||
import tab from './tab';
|
||||
|
||||
export default combineReducers({
|
||||
routing: routeReducer,
|
||||
channels,
|
||||
environment,
|
||||
input,
|
||||
messages,
|
||||
privateChats,
|
||||
search,
|
||||
servers,
|
||||
showMenu,
|
||||
tab
|
||||
});
|
45
client/src/js/reducers/input.js
Normal file
45
client/src/js/reducers/input.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
import { List, Record } from 'immutable';
|
||||
import createReducer from '../util/createReducer';
|
||||
import * as actions from '../actions';
|
||||
|
||||
const HISTORY_MAX_LENGTH = 128;
|
||||
|
||||
const State = Record({
|
||||
history: List(),
|
||||
index: 0
|
||||
});
|
||||
|
||||
export default createReducer(new State(), {
|
||||
[actions.INPUT_HISTORY_ADD](state, action) {
|
||||
const { line } = action;
|
||||
if (line.trim() && line !== state.history.get(0)) {
|
||||
if (history.length === HISTORY_MAX_LENGTH) {
|
||||
return state.set('history', state.history.unshift(line).pop());
|
||||
}
|
||||
|
||||
return state.set('history', state.history.unshift(line));
|
||||
}
|
||||
|
||||
return state;
|
||||
},
|
||||
|
||||
[actions.INPUT_HISTORY_RESET](state) {
|
||||
return state.set('index', -1);
|
||||
},
|
||||
|
||||
[actions.INPUT_HISTORY_INCREMENT](state) {
|
||||
if (state.index < state.history.size - 1) {
|
||||
return state.set('index', state.index + 1);
|
||||
}
|
||||
|
||||
return state;
|
||||
},
|
||||
|
||||
[actions.INPUT_HISTORY_DECREMENT](state) {
|
||||
if (state.index >= 0) {
|
||||
return state.set('index', state.index - 1);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
});
|
68
client/src/js/reducers/messages.js
Normal file
68
client/src/js/reducers/messages.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
import { List, Map, Record } from 'immutable';
|
||||
import createReducer from '../util/createReducer';
|
||||
import * as actions from '../actions';
|
||||
|
||||
const Message = Record({
|
||||
id: null,
|
||||
server: null,
|
||||
from: null,
|
||||
to: null,
|
||||
message: '',
|
||||
time: null,
|
||||
type: null,
|
||||
lines: []
|
||||
});
|
||||
|
||||
function addMessage(state, message) {
|
||||
let dest = message.to || message.from;
|
||||
if (message.from && message.from.indexOf('.') !== -1) {
|
||||
dest = message.server;
|
||||
}
|
||||
|
||||
if (message.message.indexOf('\x01ACTION') === 0) {
|
||||
const from = message.from;
|
||||
message.from = null;
|
||||
message.type = 'action';
|
||||
message.message = from + message.message.slice(7);
|
||||
}
|
||||
|
||||
return state.updateIn([message.server, dest], List(), list => list.push(new Message(message)));
|
||||
}
|
||||
|
||||
export default createReducer(Map(), {
|
||||
[actions.SEND_MESSAGE](state, action) {
|
||||
return addMessage(state, action);
|
||||
},
|
||||
|
||||
[actions.ADD_MESSAGE](state, action) {
|
||||
return addMessage(state, action.message);
|
||||
},
|
||||
|
||||
[actions.ADD_MESSAGES](state, action) {
|
||||
return state.withMutations(s =>
|
||||
action.messages.forEach(message =>
|
||||
addMessage(s, message)
|
||||
)
|
||||
);
|
||||
},
|
||||
/*
|
||||
[actions.SOCKET_MESSAGE](state, action) {
|
||||
return addMessage(state, action);
|
||||
},
|
||||
|
||||
[actions.SOCKET_PM](state, action) {
|
||||
return addMessage(state, action);
|
||||
},
|
||||
*/
|
||||
[actions.DISCONNECT](state, action) {
|
||||
return state.delete(action.server);
|
||||
},
|
||||
|
||||
[actions.PART](state, action) {
|
||||
return state.withMutations(s =>
|
||||
action.channels.forEach(channel =>
|
||||
s.deleteIn([action.server, channel])
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
29
client/src/js/reducers/privateChats.js
Normal file
29
client/src/js/reducers/privateChats.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { Set, Map } from 'immutable';
|
||||
import createReducer from '../util/createReducer';
|
||||
import * as actions from '../actions';
|
||||
|
||||
function open(state, server, nick) {
|
||||
return state.update(server, Set(), chats => chats.add(nick));
|
||||
}
|
||||
|
||||
export default createReducer(Map(), {
|
||||
[actions.OPEN_PRIVATE_CHAT](state, action) {
|
||||
return open(state, action.server, action.nick);
|
||||
},
|
||||
|
||||
[actions.CLOSE_PRIVATE_CHAT](state, action) {
|
||||
return state.update(action.server, chats => chats.delete(action.nick));
|
||||
},
|
||||
|
||||
[actions.SOCKET_PM](state, action) {
|
||||
if (action.from.indexOf('.') === -1) {
|
||||
return open(state, action.server, action.from);
|
||||
}
|
||||
|
||||
return state;
|
||||
},
|
||||
|
||||
[actions.DISCONNECT](state, action) {
|
||||
return state.delete(action.server);
|
||||
}
|
||||
});
|
18
client/src/js/reducers/search.js
Normal file
18
client/src/js/reducers/search.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { List, Record } from 'immutable';
|
||||
import createReducer from '../util/createReducer';
|
||||
import * as actions from '../actions';
|
||||
|
||||
const State = Record({
|
||||
show: false,
|
||||
results: List()
|
||||
});
|
||||
|
||||
export default createReducer(new State(), {
|
||||
[actions.SOCKET_SEARCH](state, action) {
|
||||
return state.set('results', List(action.results));
|
||||
},
|
||||
|
||||
[actions.TOGGLE_SEARCH](state) {
|
||||
return state.set('show', !state.show);
|
||||
}
|
||||
});
|
46
client/src/js/reducers/servers.js
Normal file
46
client/src/js/reducers/servers.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { Map, Record } from 'immutable';
|
||||
import createReducer from '../util/createReducer';
|
||||
import * as actions from '../actions';
|
||||
|
||||
const Server = Record({
|
||||
nick: null,
|
||||
name: null
|
||||
});
|
||||
|
||||
export default createReducer(Map(), {
|
||||
[actions.CONNECT](state, action) {
|
||||
let { server } = action;
|
||||
const { nick, options } = action;
|
||||
|
||||
const i = server.indexOf(':');
|
||||
if (i > 0) {
|
||||
server = server.slice(0, i);
|
||||
}
|
||||
|
||||
return state.set(server, new Server({
|
||||
nick,
|
||||
name: options.name || server
|
||||
}));
|
||||
},
|
||||
|
||||
[actions.DISCONNECT](state, action) {
|
||||
return state.delete(action.server);
|
||||
},
|
||||
|
||||
[actions.SET_NICK](state, action) {
|
||||
const { server, nick } = action;
|
||||
return state.update(server, s => s.set('nick', nick));
|
||||
},
|
||||
|
||||
[actions.SOCKET_SERVERS](state, action) {
|
||||
if (!action.data) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return state.withMutations(s => {
|
||||
action.data.forEach(server => {
|
||||
s.set(server.address, new Server(server));
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
12
client/src/js/reducers/showMenu.js
Normal file
12
client/src/js/reducers/showMenu.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import createReducer from '../util/createReducer';
|
||||
import * as actions from '../actions';
|
||||
|
||||
export default createReducer(false, {
|
||||
[actions.TOGGLE_MENU](state) {
|
||||
return !state;
|
||||
},
|
||||
|
||||
[actions.HIDE_MENU]() {
|
||||
return false;
|
||||
}
|
||||
});
|
48
client/src/js/reducers/tab.js
Normal file
48
client/src/js/reducers/tab.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { Record, List } from 'immutable';
|
||||
import { UPDATE_PATH } from 'redux-simple-router';
|
||||
import createReducer from '../util/createReducer';
|
||||
import * as actions from '../actions';
|
||||
|
||||
const Tab = Record({
|
||||
server: null,
|
||||
channel: null,
|
||||
user: null
|
||||
});
|
||||
|
||||
const State = Record({
|
||||
selected: new Tab(),
|
||||
history: List()
|
||||
});
|
||||
|
||||
export default createReducer(new State(), {
|
||||
[actions.SELECT_TAB](state, action) {
|
||||
const tab = new Tab(action);
|
||||
return state
|
||||
.set('selected', tab)
|
||||
.update('history', history => history.push(tab));
|
||||
},
|
||||
|
||||
[actions.PART](state, action) {
|
||||
return state.set('history', state.history.filter(tab =>
|
||||
!(tab.server === action.server && tab.channel === action.channels[0])
|
||||
));
|
||||
},
|
||||
|
||||
[actions.CLOSE_PRIVATE_CHAT](state, action) {
|
||||
return state.set('history', state.history.filter(tab =>
|
||||
!(tab.server === action.server && tab.user === action.nick)
|
||||
));
|
||||
},
|
||||
|
||||
[actions.DISCONNECT](state, action) {
|
||||
return state.set('history', state.history.filter(tab => tab.server !== action.server));
|
||||
},
|
||||
|
||||
[UPDATE_PATH](state, action) {
|
||||
if (action.payload.path.indexOf('.') === -1 && state.selected.server) {
|
||||
return state.set('selected', new Tab());
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue