Added title bar and basic message and command input
This commit is contained in:
parent
508a04cf4c
commit
f42d6011c6
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
bin/
|
bin/
|
||||||
client/dist/
|
client/dist/
|
||||||
client/node_modules/
|
client/node_modules/
|
||||||
|
data.db
|
@ -24,4 +24,4 @@
|
|||||||
"react-router": "~0.11.6",
|
"react-router": "~0.11.6",
|
||||||
"react": "~0.12.2"
|
"react": "~0.12.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
var Reflux = require('reflux');
|
var Reflux = require('reflux');
|
||||||
|
|
||||||
var sock = require('../socket.js')('/ws');
|
var sock = require('../socket.js')('/ws');
|
||||||
|
|
||||||
var channelActions = Reflux.createActions([
|
var channelActions = Reflux.createActions([
|
||||||
@ -6,7 +7,9 @@ var channelActions = Reflux.createActions([
|
|||||||
'joined',
|
'joined',
|
||||||
'part',
|
'part',
|
||||||
'parted',
|
'parted',
|
||||||
|
'quit',
|
||||||
'setUsers',
|
'setUsers',
|
||||||
|
'setTopic',
|
||||||
'load'
|
'load'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -26,10 +29,18 @@ sock.on('part', function(data) {
|
|||||||
channelActions.parted(data.user, data.server, data.channels[0]);
|
channelActions.parted(data.user, data.server, data.channels[0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sock.on('quit', function(data) {
|
||||||
|
channelActions.quit(data.user, data.server);
|
||||||
|
});
|
||||||
|
|
||||||
sock.on('users', function(data) {
|
sock.on('users', function(data) {
|
||||||
channelActions.setUsers(data.users, data.server, data.channel);
|
channelActions.setUsers(data.users, data.server, data.channel);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sock.on('topic', function(data) {
|
||||||
|
channelActions.setTopic(data.topic, data.server, data.channel);
|
||||||
|
});
|
||||||
|
|
||||||
sock.on('channels', function(data) {
|
sock.on('channels', function(data) {
|
||||||
channelActions.load(data);
|
channelActions.load(data);
|
||||||
});
|
});
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
var Reflux = require('reflux');
|
var Reflux = require('reflux');
|
||||||
|
|
||||||
|
var sock = require('../socket.js')('/ws');
|
||||||
|
|
||||||
var messageActions = Reflux.createActions([
|
var messageActions = Reflux.createActions([
|
||||||
'send',
|
'send',
|
||||||
'add',
|
'add',
|
||||||
'selectTab'
|
'selectTab'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
messageActions.send.preEmit = function() {
|
messageActions.send.preEmit = function(message, to, server) {
|
||||||
|
sock.send('chat', {
|
||||||
|
server: server,
|
||||||
|
to: to,
|
||||||
|
message: message
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = messageActions;
|
module.exports = messageActions;
|
@ -1,4 +1,5 @@
|
|||||||
var Reflux = require('reflux');
|
var Reflux = require('reflux');
|
||||||
|
|
||||||
var sock = require('../socket.js')('/ws');
|
var sock = require('../socket.js')('/ws');
|
||||||
|
|
||||||
var serverActions = Reflux.createActions([
|
var serverActions = Reflux.createActions([
|
||||||
|
@ -26,7 +26,7 @@ sock.on('connect', function() {
|
|||||||
|
|
||||||
channelActions.join({
|
channelActions.join({
|
||||||
server: 'irc.freenode.net',
|
server: 'irc.freenode.net',
|
||||||
channels: [ '#stuff', '#go-nuts' ]
|
channels: [ '#stuff' ]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -35,7 +35,8 @@ channelActions.joined.listen(function(user, server, channel) {
|
|||||||
server: server,
|
server: server,
|
||||||
from: '',
|
from: '',
|
||||||
to: channel,
|
to: channel,
|
||||||
message: user + ' joined the channel'
|
message: user + ' joined the channel',
|
||||||
|
type: 'info'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -44,7 +45,8 @@ channelActions.parted.listen(function(user, server, channel) {
|
|||||||
server: server,
|
server: server,
|
||||||
from: '',
|
from: '',
|
||||||
to: channel,
|
to: channel,
|
||||||
message: user + ' left the channel'
|
message: user + ' left the channel',
|
||||||
|
type: 'info'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -56,15 +58,6 @@ sock.on('pm', function(data) {
|
|||||||
messageActions.add(data);
|
messageActions.add(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
sock.on('topic', function(data) {
|
|
||||||
messageActions.add({
|
|
||||||
server: data.server,
|
|
||||||
from: '',
|
|
||||||
to: data.channel,
|
|
||||||
message: data.topic
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
sock.on('motd', function(data) {
|
sock.on('motd', function(data) {
|
||||||
_.each(data.content.split('\n'), function(line) {
|
_.each(data.content.split('\n'), function(line) {
|
||||||
messageActions.add({
|
messageActions.add({
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
var React = require('react');
|
var React = require('react');
|
||||||
|
|
||||||
var TabList = require('./TabList.jsx');
|
var TabList = require('./TabList.jsx');
|
||||||
var MessageBox = require('./MessageBox.jsx');
|
var Chat = require('./Chat.jsx');
|
||||||
var UserList = require('./UserList.jsx');
|
|
||||||
|
|
||||||
var App = React.createClass({
|
var App = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<TabList />
|
<TabList />
|
||||||
<MessageBox />
|
<Chat />
|
||||||
<UserList />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
21
client/src/js/components/Chat.jsx
Normal file
21
client/src/js/components/Chat.jsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
var React = require('react');
|
||||||
|
|
||||||
|
var ChatTitle = require('./ChatTitle.jsx');
|
||||||
|
var MessageBox = require('./MessageBox.jsx');
|
||||||
|
var MessageInput = require('./MessageInput.jsx');
|
||||||
|
var UserList = require('./UserList.jsx');
|
||||||
|
|
||||||
|
var Chat = React.createClass({
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ChatTitle />
|
||||||
|
<MessageBox />
|
||||||
|
<MessageInput />
|
||||||
|
<UserList />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Chat;
|
48
client/src/js/components/ChatTitle.jsx
Normal file
48
client/src/js/components/ChatTitle.jsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
var React = require('react');
|
||||||
|
var Reflux = require('reflux');
|
||||||
|
|
||||||
|
var channelStore = require('../stores/channel.js');
|
||||||
|
var selectedTabStore = require('../stores/selectedTab.js');
|
||||||
|
|
||||||
|
var ChatTitle = React.createClass({
|
||||||
|
mixins: [
|
||||||
|
Reflux.connect(channelStore, 'channels'),
|
||||||
|
Reflux.connect(selectedTabStore, 'selectedTab')
|
||||||
|
],
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
channels: channelStore.getState(),
|
||||||
|
selectedTab: selectedTabStore.getState()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
var tab = this.state.selectedTab;
|
||||||
|
var title;
|
||||||
|
|
||||||
|
if (tab.channel) {
|
||||||
|
var channel = this.state.channels[tab.server][tab.channel];
|
||||||
|
if (channel) {
|
||||||
|
title = tab.channel
|
||||||
|
title += ' [';
|
||||||
|
title += channel.users.length;
|
||||||
|
title += ']';
|
||||||
|
|
||||||
|
if (channel.topic) {
|
||||||
|
title += ': ' + channel.topic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
title = tab.server;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="chat-title-bar">
|
||||||
|
<span className="chat-title" title={title}>{title}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = ChatTitle;
|
@ -1,6 +1,7 @@
|
|||||||
var React = require('react');
|
var React = require('react');
|
||||||
var Reflux = require('reflux');
|
var Reflux = require('reflux');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
|
||||||
var messageStore = require('../stores/message.js');
|
var messageStore = require('../stores/message.js');
|
||||||
var selectedTabStore = require('../stores/selectedTab.js');
|
var selectedTabStore = require('../stores/selectedTab.js');
|
||||||
|
|
||||||
@ -32,7 +33,13 @@ var MessageBox = React.createClass({
|
|||||||
render: function() {
|
render: function() {
|
||||||
var tab = this.state.selectedTab.channel || this.state.selectedTab.server;
|
var tab = this.state.selectedTab.channel || this.state.selectedTab.server;
|
||||||
var messages = _.map(this.state.messages[tab], function(message) {
|
var messages = _.map(this.state.messages[tab], function(message) {
|
||||||
return <p>{message.from ? message.from + ': ' : null}{message.message}</p>;
|
var messageClass = 'message';
|
||||||
|
switch (message.type) {
|
||||||
|
case 'info':
|
||||||
|
messageClass += ' message-info';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return <p className={messageClass}>{message.from ? message.from + ': ' : null}{message.message}</p>;
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
65
client/src/js/components/MessageInput.jsx
Normal file
65
client/src/js/components/MessageInput.jsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
var React = require('react');
|
||||||
|
var Reflux = require('reflux');
|
||||||
|
|
||||||
|
var selectedTabStore = require('../stores/selectedTab.js');
|
||||||
|
var messageActions = require('../actions/message.js');
|
||||||
|
var channelActions = require('../actions/channel.js');
|
||||||
|
|
||||||
|
function dispatchCommand(cmd, channel, server) {
|
||||||
|
var params = cmd.slice(1).split(' ');
|
||||||
|
|
||||||
|
switch (params[0].toLowerCase()) {
|
||||||
|
case 'join':
|
||||||
|
if (params[1]) {
|
||||||
|
channelActions.join({
|
||||||
|
server: server,
|
||||||
|
channels: [params[1]]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'part':
|
||||||
|
if (channel) {
|
||||||
|
channelActions.part({
|
||||||
|
server: server,
|
||||||
|
channels: [channel]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var MessageInput = React.createClass({
|
||||||
|
mixins: [
|
||||||
|
Reflux.connect(selectedTabStore, 'selectedTab')
|
||||||
|
],
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
selectedTab: selectedTabStore.getState()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
handleKey: function(e) {
|
||||||
|
if (e.which === 13 && e.target.value) {
|
||||||
|
var tab = this.state.selectedTab;
|
||||||
|
|
||||||
|
if (e.target.value.charAt(0) === '/') {
|
||||||
|
dispatchCommand(e.target.value, tab.channel, tab.server);
|
||||||
|
} else {
|
||||||
|
messageActions.send(e.target.value, tab.channel, tab.server);
|
||||||
|
}
|
||||||
|
e.target.value = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<div className="message-input-wrap">
|
||||||
|
<input className="message-input" type="text" onKeyDown={this.handleKey} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = MessageInput;
|
@ -1,6 +1,7 @@
|
|||||||
var React = require('react');
|
var React = require('react');
|
||||||
var Reflux = require('reflux');
|
var Reflux = require('reflux');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
|
||||||
var channelStore = require('../stores/channel.js');
|
var channelStore = require('../stores/channel.js');
|
||||||
var selectedTabStore = require('../stores/selectedTab.js');
|
var selectedTabStore = require('../stores/selectedTab.js');
|
||||||
|
|
||||||
@ -20,11 +21,14 @@ var UserList = React.createClass({
|
|||||||
render: function() {
|
render: function() {
|
||||||
var users = null;
|
var users = null;
|
||||||
var tab = this.state.selectedTab;
|
var tab = this.state.selectedTab;
|
||||||
|
|
||||||
if (tab.channel) {
|
if (tab.channel) {
|
||||||
users = _.map(this.state.channels[tab.server][tab.channel].users, function(user) {
|
var channel = this.state.channels[tab.server][tab.channel];
|
||||||
return <p>{user}</p>;
|
if (channel) {
|
||||||
});
|
users = _.map(channel.users, function(user) {
|
||||||
|
return <p>{user}</p>;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
var EventEmitter = require('events').EventEmitter;
|
var EventEmitter = require('events').EventEmitter;
|
||||||
|
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
|
||||||
var sockets = {};
|
var sockets = {};
|
||||||
@ -8,7 +9,7 @@ function createSocket(path) {
|
|||||||
return sockets[path];
|
return sockets[path];
|
||||||
} else {
|
} else {
|
||||||
var ws = new WebSocket('ws://' + window.location.host + path);
|
var ws = new WebSocket('ws://' + window.location.host + path);
|
||||||
|
|
||||||
var sock = {
|
var sock = {
|
||||||
send: function(type, data) {
|
send: function(type, data) {
|
||||||
ws.send(JSON.stringify({ type: type, request: data }));
|
ws.send(JSON.stringify({ type: type, request: data }));
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
var Reflux = require('reflux');
|
var Reflux = require('reflux');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
|
||||||
var actions = require('../actions/channel.js');
|
var actions = require('../actions/channel.js');
|
||||||
|
|
||||||
var channels = {};
|
var channels = {};
|
||||||
@ -36,16 +37,29 @@ var channelStore = Reflux.createStore({
|
|||||||
this.trigger(channels);
|
this.trigger(channels);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
quit: function(user, server) {
|
||||||
|
_.each(channels[server], function(channel) {
|
||||||
|
_.pull(channel.users, user);
|
||||||
|
});
|
||||||
|
this.trigger(channels);
|
||||||
|
},
|
||||||
|
|
||||||
setUsers: function(users, server, channel) {
|
setUsers: function(users, server, channel) {
|
||||||
initChannel(server, channel);
|
initChannel(server, channel);
|
||||||
channels[server][channel].users = users;
|
channels[server][channel].users = users;
|
||||||
this.trigger(channels);
|
this.trigger(channels);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setTopic: function(topic, server, channel) {
|
||||||
|
channels[server][channel].topic = topic;
|
||||||
|
this.trigger(channels);
|
||||||
|
},
|
||||||
|
|
||||||
load: function(storedChannels) {
|
load: function(storedChannels) {
|
||||||
_.each(storedChannels, function(channel) {
|
_.each(storedChannels, function(channel) {
|
||||||
initChannel(channel.server, channel.name);
|
initChannel(channel.server, channel.name);
|
||||||
channels[channel.server][channel.name].users = channel.users;
|
channels[channel.server][channel.name].users = channel.users;
|
||||||
|
channels[channel.server][channel.name].topic = channel.topic;
|
||||||
});
|
});
|
||||||
this.trigger(channels);
|
this.trigger(channels);
|
||||||
},
|
},
|
||||||
|
@ -1,25 +1,40 @@
|
|||||||
var Reflux = require('reflux');
|
var Reflux = require('reflux');
|
||||||
|
|
||||||
var actions = require('../actions/message.js');
|
var actions = require('../actions/message.js');
|
||||||
|
|
||||||
var messages = {};
|
var messages = {};
|
||||||
|
|
||||||
|
function addMessage(message, dest) {
|
||||||
|
if (!(dest in messages)) {
|
||||||
|
messages[dest] = [message];
|
||||||
|
} else {
|
||||||
|
messages[dest].push(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var messageStore = Reflux.createStore({
|
var messageStore = Reflux.createStore({
|
||||||
init: function() {
|
init: function() {
|
||||||
this.listenToMany(actions);
|
this.listenToMany(actions);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
send: function(message, to, server) {
|
||||||
|
addMessage({
|
||||||
|
server: server,
|
||||||
|
from: 'self',
|
||||||
|
to: to,
|
||||||
|
message: message
|
||||||
|
}, to);
|
||||||
|
|
||||||
|
this.trigger(messages);
|
||||||
|
},
|
||||||
|
|
||||||
add: function(message) {
|
add: function(message) {
|
||||||
var dest = message.to || message.from;
|
var dest = message.to || message.from;
|
||||||
if (message.from.indexOf('.') !== -1) {
|
if (message.from.indexOf('.') !== -1) {
|
||||||
dest = message.server;
|
dest = message.server;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(dest in messages)) {
|
|
||||||
messages[dest] = [message];
|
|
||||||
} else {
|
|
||||||
messages[dest].push(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
addMessage(message, dest);
|
||||||
this.trigger(messages);
|
this.trigger(messages);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
var Reflux = require('reflux');
|
var Reflux = require('reflux');
|
||||||
|
var _ = require('lodash');
|
||||||
|
|
||||||
var actions = require('../actions/tab.js');
|
var actions = require('../actions/tab.js');
|
||||||
|
var channelActions = require('../actions/channel.js');
|
||||||
|
|
||||||
var selectedTab = {};
|
var selectedTab = {};
|
||||||
|
|
||||||
var selectedTabStore = Reflux.createStore({
|
var selectedTabStore = Reflux.createStore({
|
||||||
init: function() {
|
init: function() {
|
||||||
this.listenToMany(actions);
|
this.listenToMany(actions);
|
||||||
|
this.listenTo(channelActions.part, 'part');
|
||||||
},
|
},
|
||||||
|
|
||||||
select: function(server, channel) {
|
select: function(server, channel) {
|
||||||
@ -14,6 +18,19 @@ var selectedTabStore = Reflux.createStore({
|
|||||||
this.trigger(selectedTab);
|
this.trigger(selectedTab);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
part: function(data) {
|
||||||
|
var self = this;
|
||||||
|
if (data.server === selectedTab.server) {
|
||||||
|
_.each(data.channels, function(channel) {
|
||||||
|
if (channel === selectedTab.channel) {
|
||||||
|
delete selectedTab.channel;
|
||||||
|
self.trigger(selectedTab);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
getState: function() {
|
getState: function() {
|
||||||
return selectedTab;
|
return selectedTab;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
var Reflux = require('reflux');
|
var Reflux = require('reflux');
|
||||||
|
|
||||||
var actions = require('../actions/server.js');
|
var actions = require('../actions/server.js');
|
||||||
|
|
||||||
var servers = {};
|
var servers = {};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@ -9,6 +10,14 @@ body {
|
|||||||
color: #FFF;
|
color: #FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
font: 16px Inconsolata, sans-serif;
|
||||||
|
background: rgba(0,0,0,0.25);
|
||||||
|
color: #FFF;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
@ -19,7 +28,7 @@ p {
|
|||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 200px;
|
right: 200px;
|
||||||
padding: 20px;
|
padding: 15px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,23 +40,64 @@ p {
|
|||||||
color: #AAA;
|
color: #AAA;
|
||||||
}
|
}
|
||||||
|
|
||||||
.messagebox {
|
.chat-title-bar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 200px;
|
left: 200px;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
|
||||||
right: 200px;
|
right: 200px;
|
||||||
padding: 20px;
|
height: 50px;
|
||||||
|
padding: 0 15px;
|
||||||
|
line-height: 50px;
|
||||||
|
background: rgba(0,0,0,0.25);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-title {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.messagebox {
|
||||||
|
position: fixed;
|
||||||
|
left: 200px;
|
||||||
|
top: 50px;
|
||||||
|
bottom: 50px;
|
||||||
|
right: 200px;
|
||||||
|
padding: 15px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-info {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-input-wrap {
|
||||||
|
position: fixed;
|
||||||
|
left: 200px;
|
||||||
|
bottom: 0px;
|
||||||
|
right: 0;
|
||||||
|
height: 50px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
.userlist {
|
.userlist {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 50px;
|
||||||
right: 0;
|
right: 0;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
padding: 20px;
|
padding: 15px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
@ -26,6 +26,11 @@ type Join struct {
|
|||||||
Channels []string `json:"channels"`
|
Channels []string `json:"channels"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Quit struct {
|
||||||
|
Server string `json:"server"`
|
||||||
|
User string `json:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
type Chat struct {
|
type Chat struct {
|
||||||
Server string `json:"server"`
|
Server string `json:"server"`
|
||||||
From string `json:"from"`
|
From string `json:"from"`
|
||||||
|
@ -2,7 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/khlieng/irc/storage"
|
"github.com/khlieng/irc/storage"
|
||||||
@ -16,7 +16,7 @@ func handleMessages(irc *IRC, session *Session) {
|
|||||||
for msg := range irc.Messages {
|
for msg := range irc.Messages {
|
||||||
switch msg.Command {
|
switch msg.Command {
|
||||||
case JOIN:
|
case JOIN:
|
||||||
user := parseUser(msg.Prefix)
|
user := msg.Prefix
|
||||||
|
|
||||||
session.sendJSON("join", Join{
|
session.sendJSON("join", Join{
|
||||||
Server: irc.Host,
|
Server: irc.Host,
|
||||||
@ -34,7 +34,7 @@ func handleMessages(irc *IRC, session *Session) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case PART:
|
case PART:
|
||||||
user := parseUser(msg.Prefix)
|
user := msg.Prefix
|
||||||
|
|
||||||
session.sendJSON("part", Join{
|
session.sendJSON("part", Join{
|
||||||
Server: irc.Host,
|
Server: irc.Host,
|
||||||
@ -65,15 +65,18 @@ func handleMessages(irc *IRC, session *Session) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case QUIT:
|
case QUIT:
|
||||||
/*
|
user := msg.Prefix
|
||||||
session.sendJSON("quit", Quit{
|
|
||||||
Server: irc.Host,
|
session.sendJSON("quit", Quit{
|
||||||
User: user,
|
Server: irc.Host,
|
||||||
})
|
User: user,
|
||||||
*/
|
})
|
||||||
|
|
||||||
|
channelStore.RemoveUserAll(user, irc.Host)
|
||||||
|
|
||||||
case RPL_WELCOME,
|
case RPL_WELCOME,
|
||||||
RPL_YOURHOST,
|
RPL_YOURHOST,
|
||||||
|
RPL_CREATED,
|
||||||
RPL_LUSERCLIENT,
|
RPL_LUSERCLIENT,
|
||||||
RPL_LUSEROP,
|
RPL_LUSEROP,
|
||||||
RPL_LUSERUNKNOWN,
|
RPL_LUSERUNKNOWN,
|
||||||
@ -92,6 +95,8 @@ func handleMessages(irc *IRC, session *Session) {
|
|||||||
Topic: msg.Trailing,
|
Topic: msg.Trailing,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
channelStore.SetTopic(msg.Trailing, irc.Host, msg.Params[1])
|
||||||
|
|
||||||
case RPL_NAMREPLY:
|
case RPL_NAMREPLY:
|
||||||
users := strings.Split(msg.Trailing, " ")
|
users := strings.Split(msg.Trailing, " ")
|
||||||
|
|
||||||
@ -138,10 +143,5 @@ func handleMessages(irc *IRC, session *Session) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func printMessage(msg *Message, irc *IRC) {
|
func printMessage(msg *Message, irc *IRC) {
|
||||||
fmt.Printf("%s: %s %s %s\n", irc.nick, msg.Prefix, msg.Command, msg.Params)
|
log.Println(irc.nick+":", msg.Prefix, msg.Command, msg.Params, msg.Trailing)
|
||||||
|
|
||||||
if msg.Trailing != "" {
|
|
||||||
fmt.Println(msg.Trailing)
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
}
|
}
|
||||||
|
@ -5,63 +5,98 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ChannelStore struct {
|
type ChannelStore struct {
|
||||||
data map[string]map[string][]string
|
users map[string]map[string][]string
|
||||||
lock sync.Mutex
|
userLock sync.Mutex
|
||||||
|
|
||||||
|
topic map[string]map[string]string
|
||||||
|
topicLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewChannelStore() *ChannelStore {
|
func NewChannelStore() *ChannelStore {
|
||||||
return &ChannelStore{
|
return &ChannelStore{
|
||||||
data: make(map[string]map[string][]string),
|
users: make(map[string]map[string][]string),
|
||||||
|
topic: make(map[string]map[string]string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ChannelStore) GetUsers(server, channel string) []string {
|
func (c *ChannelStore) GetUsers(server, channel string) []string {
|
||||||
c.lock.Lock()
|
c.userLock.Lock()
|
||||||
|
|
||||||
users := make([]string, len(c.data[server][channel]))
|
users := make([]string, len(c.users[server][channel]))
|
||||||
copy(users, c.data[server][channel])
|
copy(users, c.users[server][channel])
|
||||||
|
|
||||||
c.lock.Unlock()
|
c.userLock.Unlock()
|
||||||
|
|
||||||
return users
|
return users
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ChannelStore) SetUsers(users []string, server, channel string) {
|
func (c *ChannelStore) SetUsers(users []string, server, channel string) {
|
||||||
c.lock.Lock()
|
c.userLock.Lock()
|
||||||
|
|
||||||
if _, ok := c.data[server]; !ok {
|
if _, ok := c.users[server]; !ok {
|
||||||
c.data[server] = make(map[string][]string)
|
c.users[server] = make(map[string][]string)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.data[server][channel] = users
|
c.users[server][channel] = users
|
||||||
|
|
||||||
c.lock.Unlock()
|
c.userLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ChannelStore) AddUser(user, server, channel string) {
|
func (c *ChannelStore) AddUser(user, server, channel string) {
|
||||||
c.lock.Lock()
|
c.userLock.Lock()
|
||||||
|
|
||||||
if _, ok := c.data[server]; !ok {
|
if _, ok := c.users[server]; !ok {
|
||||||
c.data[server] = make(map[string][]string)
|
c.users[server] = make(map[string][]string)
|
||||||
}
|
}
|
||||||
|
|
||||||
if users, ok := c.data[server][channel]; ok {
|
if users, ok := c.users[server][channel]; ok {
|
||||||
c.data[server][channel] = append(users, user)
|
c.users[server][channel] = append(users, user)
|
||||||
} else {
|
} else {
|
||||||
c.data[server][channel] = []string{user}
|
c.users[server][channel] = []string{user}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.lock.Unlock()
|
c.userLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ChannelStore) RemoveUser(user, server, channel string) {
|
func (c *ChannelStore) RemoveUser(user, server, channel string) {
|
||||||
c.lock.Lock()
|
c.userLock.Lock()
|
||||||
defer c.lock.Unlock()
|
c.removeUser(user, server, channel)
|
||||||
|
c.userLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
for i, u := range c.data[server][channel] {
|
func (c *ChannelStore) RemoveUserAll(user, server string) {
|
||||||
|
c.userLock.Lock()
|
||||||
|
|
||||||
|
for channel, _ := range c.users[server] {
|
||||||
|
c.removeUser(user, server, channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.userLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChannelStore) GetTopic(server, channel string) string {
|
||||||
|
c.topicLock.Lock()
|
||||||
|
defer c.topicLock.Unlock()
|
||||||
|
|
||||||
|
return c.topic[server][channel]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChannelStore) SetTopic(topic, server, channel string) {
|
||||||
|
c.topicLock.Lock()
|
||||||
|
|
||||||
|
if _, ok := c.topic[server]; !ok {
|
||||||
|
c.topic[server] = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.topic[server][channel] = topic
|
||||||
|
c.topicLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChannelStore) removeUser(user, server, channel string) {
|
||||||
|
for i, u := range c.users[server][channel] {
|
||||||
if u == user {
|
if u == user {
|
||||||
users := c.data[server][channel]
|
users := c.users[server][channel]
|
||||||
c.data[server][channel] = append(users[:i], users[i+1:]...)
|
c.users[server][channel] = append(users[:i], users[i+1:]...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ type Channel struct {
|
|||||||
Server string `json:"server"`
|
Server string `json:"server"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Users []string `json:"users"`
|
Users []string `json:"users"`
|
||||||
|
Topic string `json:"topic,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
@ -99,7 +100,7 @@ func (u User) GetChannels() []Channel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u User) AddServer(server Server) {
|
func (u User) AddServer(server Server) {
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
go db.Update(func(tx *bolt.Tx) error {
|
||||||
b := tx.Bucket([]byte("Servers"))
|
b := tx.Bucket([]byte("Servers"))
|
||||||
data, _ := json.Marshal(server)
|
data, _ := json.Marshal(server)
|
||||||
|
|
||||||
@ -110,7 +111,7 @@ func (u User) AddServer(server Server) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u User) AddChannel(channel Channel) {
|
func (u User) AddChannel(channel Channel) {
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
go db.Update(func(tx *bolt.Tx) error {
|
||||||
b := tx.Bucket([]byte("Channels"))
|
b := tx.Bucket([]byte("Channels"))
|
||||||
data, _ := json.Marshal(channel)
|
data, _ := json.Marshal(channel)
|
||||||
|
|
||||||
@ -121,16 +122,25 @@ func (u User) AddChannel(channel Channel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u User) RemoveServer(address string) {
|
func (u User) RemoveServer(address string) {
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
go db.Update(func(tx *bolt.Tx) error {
|
||||||
tx.Bucket([]byte("Channels")).Delete([]byte(u.UUID + ":" + address))
|
serverID := []byte(u.UUID + ":" + address)
|
||||||
|
|
||||||
|
tx.Bucket([]byte("Servers")).Delete(serverID)
|
||||||
|
|
||||||
|
b := tx.Bucket([]byte("Channels"))
|
||||||
|
c := b.Cursor()
|
||||||
|
|
||||||
|
for k, _ := c.Seek(serverID); bytes.HasPrefix(k, serverID); k, _ = c.Next() {
|
||||||
|
b.Delete(k)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u User) RemoveChannel(server, channel string) {
|
func (u User) RemoveChannel(server, channel string) {
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
go db.Update(func(tx *bolt.Tx) error {
|
||||||
tx.Bucket([]byte("Servers")).Delete([]byte(u.UUID + ":" + server + ":" + channel))
|
tx.Bucket([]byte("Channels")).Delete([]byte(u.UUID + ":" + server + ":" + channel))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
@ -48,6 +48,7 @@ func handleWS(ws *websocket.Conn) {
|
|||||||
channels := session.user.GetChannels()
|
channels := session.user.GetChannels()
|
||||||
for i, channel := range channels {
|
for i, channel := range channels {
|
||||||
channels[i].Users = channelStore.GetUsers(channel.Server, channel.Name)
|
channels[i].Users = channelStore.GetUsers(channel.Server, channel.Name)
|
||||||
|
channels[i].Topic = channelStore.GetTopic(channel.Server, channel.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
session.sendJSON("channels", channels)
|
session.sendJSON("channels", channels)
|
||||||
@ -106,6 +107,17 @@ func handleWS(ws *websocket.Conn) {
|
|||||||
irc.Part(data.Channels...)
|
irc.Part(data.Channels...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "quit":
|
||||||
|
var data Quit
|
||||||
|
|
||||||
|
json.Unmarshal(req.Request, &data)
|
||||||
|
|
||||||
|
if irc, ok := session.getIRC(data.Server); ok {
|
||||||
|
channelStore.RemoveUserAll(irc.nick, data.Server)
|
||||||
|
session.user.RemoveServer(data.Server)
|
||||||
|
irc.Quit()
|
||||||
|
}
|
||||||
|
|
||||||
case "chat":
|
case "chat":
|
||||||
var data Chat
|
var data Chat
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user