jschat 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +23 -0
- data/README.textile +71 -0
- data/bin/jschat-client +3 -0
- data/bin/jschat-server +18 -0
- data/bin/jschat-web +7 -0
- data/lib/jschat/client.rb +745 -0
- data/lib/jschat/errors.rb +41 -0
- data/lib/jschat/flood_protection.rb +39 -0
- data/lib/jschat/http/config/sprockets.yml +7 -0
- data/lib/jschat/http/config.ru +12 -0
- data/lib/jschat/http/jschat.rb +264 -0
- data/lib/jschat/http/public/favicon.ico +0 -0
- data/lib/jschat/http/public/images/emoticons/angry.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/arr.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/blink.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/blush.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/brucelee.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/btw.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/chuckle.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/clap.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/cool.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/drool.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/drunk.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/dry.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/eek.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/flex.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/happy.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/holmes.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/huh.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/laugh.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/lol.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/mad.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/mellow.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/noclue.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/oh.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/ohmy.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/ph34r.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/pimp.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/punch.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/realmad.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/rock.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/rofl.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/rolleyes.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/sad.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/scratch.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/shifty.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/shock.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/shrug.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/sleep.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/sleeping.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/smile.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/suicide.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/sweat.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/thumbs.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/tongue.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/unsure.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/w00t.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/wacko.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/whistling.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/wink.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/worship.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/yucky.gif +0 -0
- data/lib/jschat/http/public/images/jschat.gif +0 -0
- data/lib/jschat/http/public/images/shadow.png +0 -0
- data/lib/jschat/http/public/javascripts/app/controllers/chat_controller.js +191 -0
- data/lib/jschat/http/public/javascripts/app/controllers/signon_controller.js +56 -0
- data/lib/jschat/http/public/javascripts/app/helpers/emote_helper.js +23 -0
- data/lib/jschat/http/public/javascripts/app/helpers/form_helpers.js +37 -0
- data/lib/jschat/http/public/javascripts/app/helpers/link_helper.js +47 -0
- data/lib/jschat/http/public/javascripts/app/helpers/page_helper.js +27 -0
- data/lib/jschat/http/public/javascripts/app/helpers/text_helper.js +92 -0
- data/lib/jschat/http/public/javascripts/app/lib/split.js +78 -0
- data/lib/jschat/http/public/javascripts/app/models/cookie.js +27 -0
- data/lib/jschat/http/public/javascripts/app/protocol/change.js +15 -0
- data/lib/jschat/http/public/javascripts/app/protocol/chat_request.js +13 -0
- data/lib/jschat/http/public/javascripts/app/protocol/display.js +147 -0
- data/lib/jschat/http/public/javascripts/app/ui/commands.js +55 -0
- data/lib/jschat/http/public/javascripts/app/ui/tab_completion.js +122 -0
- data/lib/jschat/http/public/javascripts/init.js +19 -0
- data/lib/jschat/http/public/stylesheets/iphone.css +3 -0
- data/lib/jschat/http/public/stylesheets/screen.css +68 -0
- data/lib/jschat/http/script/sprockets.rb +14 -0
- data/lib/jschat/http/tmp/restart.txt +0 -0
- data/lib/jschat/http/views/index.erb +23 -0
- data/lib/jschat/http/views/iphone.erb +29 -0
- data/lib/jschat/http/views/layout.erb +29 -0
- data/lib/jschat/http/views/message_form.erb +15 -0
- data/lib/jschat/server.rb +503 -0
- data/test/server_test.rb +175 -0
- data/test/stateless_test.rb +33 -0
- data/test/test_helper.rb +61 -0
- metadata +223 -0
@@ -0,0 +1,147 @@
|
|
1
|
+
var Display = {
|
2
|
+
scrolled: false,
|
3
|
+
|
4
|
+
add_message: function(text, className, time) {
|
5
|
+
var time_html = '<span class="time">\#{time}</span>'.interpolate({ time: TextHelper.dateText(time) });
|
6
|
+
$('messages').insert({ bottom: '<li class="' + className + '">' + time_html + ' ' + text + '</li>' });
|
7
|
+
this.scrollMessagesToTop();
|
8
|
+
},
|
9
|
+
|
10
|
+
addImageOnLoads: function() {
|
11
|
+
$$('#messages li').last().select('img').each(function(element) {
|
12
|
+
element.observe('load', this.scrollMessagesToTop);
|
13
|
+
}.bind(this));
|
14
|
+
},
|
15
|
+
|
16
|
+
message: function(message) {
|
17
|
+
var name = $('name').innerHTML;
|
18
|
+
var user_class = name == message['user'] ? 'user active' : 'user';
|
19
|
+
var text = '<span class="\#{user_class}">\#{user}</span> <span class="\#{message_class}">\#{message}</span>';
|
20
|
+
|
21
|
+
if (message['message'].match(new RegExp(name, 'i')) && name != message['user']) {
|
22
|
+
user_class = 'user mentioned';
|
23
|
+
}
|
24
|
+
|
25
|
+
Display.clearIdleState(message['user']);
|
26
|
+
|
27
|
+
text = text.interpolate({ user_class: user_class, room: message['room'], user: TextHelper.truncateName(message['user']), message: TextHelper.decorateMessage(message['message']), message_class: 'message' });
|
28
|
+
this.add_message(text, 'message', message['time']);
|
29
|
+
|
30
|
+
this.addImageOnLoads();
|
31
|
+
|
32
|
+
if (this.show_unread) {
|
33
|
+
this.unread++;
|
34
|
+
document.title = 'JsChat: (' + this.unread + ') new messages';
|
35
|
+
}
|
36
|
+
},
|
37
|
+
|
38
|
+
messages: function(messages) {
|
39
|
+
$('messages').innerHTML = '';
|
40
|
+
this.ignore_notices = true;
|
41
|
+
|
42
|
+
$A(messages).each(function(json) {
|
43
|
+
try {
|
44
|
+
if (json['change']) {
|
45
|
+
Change[json['change']](json[json['change']]);
|
46
|
+
} else {
|
47
|
+
this[json['display']](json[json['display']]);
|
48
|
+
}
|
49
|
+
} catch (exception) {
|
50
|
+
}
|
51
|
+
}.bind(this));
|
52
|
+
|
53
|
+
this.ignore_notices = false;
|
54
|
+
this.scrollMessagesToTop();
|
55
|
+
/* This is assumed to be the point at which displaying /lastlog completes */
|
56
|
+
$('loading').hide();
|
57
|
+
Cookie.create('jschat-name', $('name').innerHTML, 28, '/');
|
58
|
+
},
|
59
|
+
|
60
|
+
scrollMessagesToTop: function() {
|
61
|
+
if (!this.scrolled) {
|
62
|
+
$('messages').scrollTop = $('messages').scrollHeight;
|
63
|
+
}
|
64
|
+
},
|
65
|
+
|
66
|
+
clearIdleState: function(user_name) {
|
67
|
+
$$('#names li').each(function(element) {
|
68
|
+
if (element.innerHTML == user_name && element.hasClassName('idle')) {
|
69
|
+
element.lastIdle = (new Date());
|
70
|
+
element.removeClassName('idle');
|
71
|
+
}
|
72
|
+
});
|
73
|
+
},
|
74
|
+
|
75
|
+
isIdle: function(dateValue) {
|
76
|
+
try {
|
77
|
+
var d = typeof dateValue == 'string' ? new Date(Date.parse(dateValue)) : dateValue,
|
78
|
+
now = new Date();
|
79
|
+
if (((now - d) / 1000) > (60 * 5)) {
|
80
|
+
return true;
|
81
|
+
}
|
82
|
+
} catch (exception) {
|
83
|
+
console.log(exception);
|
84
|
+
}
|
85
|
+
return false;
|
86
|
+
},
|
87
|
+
|
88
|
+
names: function(users) {
|
89
|
+
$('names').innerHTML = '';
|
90
|
+
users.each(function(user) {
|
91
|
+
var name = user['name'],
|
92
|
+
list_class = this.isIdle(user['last_activity']) ? 'idle' : '',
|
93
|
+
element = $(document.createElement('li'));
|
94
|
+
|
95
|
+
element.addClassName(list_class);
|
96
|
+
element.innerHTML = TextHelper.truncateName(name);
|
97
|
+
$('names').insert({ bottom: element });
|
98
|
+
|
99
|
+
try {
|
100
|
+
// Record the last idle time so the idle state can be dynamically updated
|
101
|
+
element.lastIdle = new Date(Date.parse(user['last_activity']));
|
102
|
+
} catch (exception) {
|
103
|
+
element.lastIdle = null;
|
104
|
+
}
|
105
|
+
}.bind(this));
|
106
|
+
},
|
107
|
+
|
108
|
+
join: function(join) {
|
109
|
+
$('room-name').innerHTML = TextHelper.truncateRoomName(join['room']);
|
110
|
+
$('room-name').title = PageHelper.currentRoom();
|
111
|
+
},
|
112
|
+
|
113
|
+
join_notice: function(join) {
|
114
|
+
this.add_user(join['user']);
|
115
|
+
this.add_message(join['user'] + ' has joined the room', 'server', join['time']);
|
116
|
+
},
|
117
|
+
|
118
|
+
add_user: function(name) {
|
119
|
+
if (!this.ignore_notices) {
|
120
|
+
$('names').insert({ bottom: '<li>' + TextHelper.truncateName(name) + '</li>' });
|
121
|
+
}
|
122
|
+
},
|
123
|
+
|
124
|
+
remove_user: function(name) {
|
125
|
+
if (!this.ignore_notices) {
|
126
|
+
$$('#names li').each(function(element) { if (element.innerHTML == name) element.remove(); });
|
127
|
+
}
|
128
|
+
},
|
129
|
+
|
130
|
+
part_notice: function(part) {
|
131
|
+
this.remove_user(part['user']);
|
132
|
+
this.add_message(part['user'] + ' has left the room', 'server', part['time']);
|
133
|
+
},
|
134
|
+
|
135
|
+
quit_notice: function(quit) {
|
136
|
+
this.remove_user(quit['user']);
|
137
|
+
this.add_message(quit['user'] + ' has quit', 'server', quit['time']);
|
138
|
+
},
|
139
|
+
|
140
|
+
notice: function(notice) {
|
141
|
+
this.add_message(notice, 'server');
|
142
|
+
},
|
143
|
+
|
144
|
+
error: function(error) {
|
145
|
+
this.add_message(error['message'], 'error');
|
146
|
+
}
|
147
|
+
};
|
@@ -0,0 +1,55 @@
|
|
1
|
+
var UserCommands = {
|
2
|
+
'/emotes': function() {
|
3
|
+
var text = '';
|
4
|
+
Display.add_message('<strong>Available Emotes</strong> — Prefix with a : to use', 'help');
|
5
|
+
Display.add_message(EmoteHelper.legalEmotes.join(', '), 'help');
|
6
|
+
},
|
7
|
+
|
8
|
+
'/help': function() {
|
9
|
+
var help = [];
|
10
|
+
Display.add_message('<strong>JsChat Help</strong> — Type the following commands into the message field:', 'help')
|
11
|
+
help.push(['/clear', 'Clears messages']);
|
12
|
+
help.push(['/lastlog', 'Shows recent activity']);
|
13
|
+
help.push(['/names', 'Refreshes the names list']);
|
14
|
+
help.push(['/name new_name', 'Changes your name']);
|
15
|
+
help.push(['/emotes', 'Shows available emotes']);
|
16
|
+
$A(help).each(function(options) {
|
17
|
+
var help_text = '<span class="command">#{command}</span><span class="command_help">#{text}</span>'.interpolate({ command: options[0], text: options[1]});
|
18
|
+
Display.add_message(help_text, 'help');
|
19
|
+
});
|
20
|
+
},
|
21
|
+
|
22
|
+
'/clear': function() {
|
23
|
+
$('messages').innerHTML = '';
|
24
|
+
},
|
25
|
+
|
26
|
+
'/lastlog': function() {
|
27
|
+
$('messages').innerHTML = '';
|
28
|
+
JsChat.Request.get('/lastlog', function(transport) {
|
29
|
+
this.displayMessages(transport.responseText);
|
30
|
+
$('names').innerHTML = '';
|
31
|
+
this.updateNames();
|
32
|
+
}.bind(this));
|
33
|
+
},
|
34
|
+
|
35
|
+
'/(name|nick)\\s+(.*)': function(name) {
|
36
|
+
name = name[2];
|
37
|
+
new Ajax.Request('/change-name', {
|
38
|
+
method: 'post',
|
39
|
+
parameters: { name: name },
|
40
|
+
onSuccess: function(response) {
|
41
|
+
this.displayMessages(response.responseText, function() {
|
42
|
+
$('name').innerHTML = name;
|
43
|
+
Cookie.create('jschat-name', name, 28, '/');
|
44
|
+
});
|
45
|
+
}.bind(this),
|
46
|
+
onFailure: function() {
|
47
|
+
Display.add_message("Server error: couldn't access: #{url}".interpolate({ url: url }), 'server');
|
48
|
+
}
|
49
|
+
});
|
50
|
+
},
|
51
|
+
|
52
|
+
'/names': function() {
|
53
|
+
JsChat.Request.get('/names', function(t) { this.displayMessages(t.responseText); }.bind(this));
|
54
|
+
}
|
55
|
+
};
|
@@ -0,0 +1,122 @@
|
|
1
|
+
var TabCompletion = Class.create({
|
2
|
+
initialize: function(element) {
|
3
|
+
this.element = $(element);
|
4
|
+
this.matches = [];
|
5
|
+
this.match_offset = 0;
|
6
|
+
this.cycling = false;
|
7
|
+
this.has_focus = true;
|
8
|
+
|
9
|
+
document.observe('keydown', this.keyboardEvents.bindAsEventListener(this));
|
10
|
+
this.element.observe('focus', this.onFocus.bindAsEventListener(this));
|
11
|
+
this.element.observe('blur', this.onBlur.bindAsEventListener(this));
|
12
|
+
this.element.observe('click', this.onFocus.bindAsEventListener(this));
|
13
|
+
},
|
14
|
+
|
15
|
+
onBlur: function() {
|
16
|
+
this.has_focus = false;
|
17
|
+
this.reset();
|
18
|
+
},
|
19
|
+
|
20
|
+
onFocus: function() {
|
21
|
+
this.has_focus = true;
|
22
|
+
this.reset();
|
23
|
+
},
|
24
|
+
|
25
|
+
tabSearch: function(input) {
|
26
|
+
var names = $$('#names li').collect(function(element) { return element.innerHTML });
|
27
|
+
return names.findAll(function(name) { return name.toLowerCase().match(input.toLowerCase()) });
|
28
|
+
},
|
29
|
+
|
30
|
+
textToLeft: function() {
|
31
|
+
var text = this.element.value;
|
32
|
+
var caret_position = FormHelpers.getCaretPosition(this.element);
|
33
|
+
if (caret_position < text.length) {
|
34
|
+
text = text.slice(0, caret_position);
|
35
|
+
}
|
36
|
+
|
37
|
+
text = text.split(' ').last();
|
38
|
+
return text;
|
39
|
+
},
|
40
|
+
|
41
|
+
elementFocused: function(e) {
|
42
|
+
if (typeof document.activeElement == 'undefined') {
|
43
|
+
return this.has_focus;
|
44
|
+
} else {
|
45
|
+
return document.activeElement == this.element;
|
46
|
+
}
|
47
|
+
},
|
48
|
+
|
49
|
+
keyboardEvents: function(e) {
|
50
|
+
if (this.elementFocused()) {
|
51
|
+
switch (e.keyCode) {
|
52
|
+
case Event.KEY_TAB:
|
53
|
+
var caret_position = FormHelpers.getCaretPosition(this.element);
|
54
|
+
|
55
|
+
if (this.element.value.length > 0) {
|
56
|
+
var search_text = '';
|
57
|
+
var search_result = '';
|
58
|
+
var replace_inline = false;
|
59
|
+
var editedText = this.element.value.match(/[^a-z0-9]/i);
|
60
|
+
|
61
|
+
if (this.cycling) {
|
62
|
+
if (this.element.value == '#{last_result}: '.interpolate({ last_result: this.last_result })) {
|
63
|
+
editedText = false;
|
64
|
+
} else {
|
65
|
+
replace_inline = true;
|
66
|
+
}
|
67
|
+
search_text = this.last_result;
|
68
|
+
} else if (editedText && this.matches.length == 0) {
|
69
|
+
search_text = this.textToLeft();
|
70
|
+
replace_inline = true;
|
71
|
+
} else {
|
72
|
+
search_text = this.element.value;
|
73
|
+
}
|
74
|
+
|
75
|
+
if (this.matches.length == 0) {
|
76
|
+
this.matches = this.tabSearch(search_text);
|
77
|
+
search_result = this.matches.first();
|
78
|
+
this.cycling = true;
|
79
|
+
} else {
|
80
|
+
this.match_offset++;
|
81
|
+
if (this.match_offset >= this.matches.length) {
|
82
|
+
this.match_offset = 0;
|
83
|
+
}
|
84
|
+
search_result = this.matches[this.match_offset];
|
85
|
+
}
|
86
|
+
|
87
|
+
if (search_result && search_result.length > 0) {
|
88
|
+
if (this.cycling && this.last_result) {
|
89
|
+
search_text = this.last_result;
|
90
|
+
}
|
91
|
+
this.last_result = search_result;
|
92
|
+
|
93
|
+
if (replace_inline) {
|
94
|
+
var slice_start = caret_position - search_text.length;
|
95
|
+
if (slice_start > 0) {
|
96
|
+
this.element.value = this.element.value.substr(0, slice_start) + search_result + this.element.value.substr(caret_position, this.element.value.length);
|
97
|
+
FormHelpers.setCaretPosition(this.element, slice_start + search_result.length);
|
98
|
+
}
|
99
|
+
} else if (!editedText) {
|
100
|
+
this.element.value = '#{search_result}: '.interpolate({ search_result: search_result });
|
101
|
+
}
|
102
|
+
}
|
103
|
+
}
|
104
|
+
|
105
|
+
Event.stop(e);
|
106
|
+
return false;
|
107
|
+
break;
|
108
|
+
|
109
|
+
default:
|
110
|
+
this.reset();
|
111
|
+
break;
|
112
|
+
}
|
113
|
+
}
|
114
|
+
},
|
115
|
+
|
116
|
+
reset: function() {
|
117
|
+
this.matches = [];
|
118
|
+
this.match_offset = 0;
|
119
|
+
this.last_result = null;
|
120
|
+
this.cycling = false;
|
121
|
+
}
|
122
|
+
});
|
@@ -0,0 +1,19 @@
|
|
1
|
+
var JsChat = {};
|
2
|
+
|
3
|
+
document.observe('dom:loaded', function() {
|
4
|
+
if ($('post_message')) {
|
5
|
+
var chatController = new JsChat.ChatController();
|
6
|
+
}
|
7
|
+
|
8
|
+
if ($('sign-on')) {
|
9
|
+
if (Cookie.find('jschat-name')) {
|
10
|
+
$('name').value = Cookie.find('jschat-name');
|
11
|
+
}
|
12
|
+
|
13
|
+
if ($('room') && window.location.hash) {
|
14
|
+
$('room').value = window.location.hash;
|
15
|
+
}
|
16
|
+
|
17
|
+
var signOnController = new JsChat.SignOnController();
|
18
|
+
}
|
19
|
+
});
|
@@ -0,0 +1,68 @@
|
|
1
|
+
body { margin: 0 auto; padding: 0; font-family: 'Lucida Grande', arial, helvetica, sans-serif; color: #111; text-align: center; }
|
2
|
+
html, body { background: #f0f0f0; }
|
3
|
+
|
4
|
+
h1, h2, h3, h4 { margin: 0; padding: 0; }
|
5
|
+
|
6
|
+
h1 { text-align: left; margin-left: 20px }
|
7
|
+
|
8
|
+
.header { position: absolute; top: 0; height: 59px; left: 0; width: 100%; z-index: 10; background-color: #ffc; clear: both; }
|
9
|
+
.header img { border: none; margin: 5px 0 0 0 }
|
10
|
+
.header h1 { width: 200px; float: left }
|
11
|
+
.header .navigation { float: right; list-style-type: none; margin: 18px 0 0 0; padding: 0 }
|
12
|
+
.header .navigation li { float: left; margin: 0 20px 0 0; padding: 0 }
|
13
|
+
.header .navigation li a { text-decoration: none; color: #fff; background-color: #777; padding: 1px 3px }
|
14
|
+
.header .navigation li a:hover { background-color: #fff; color: #555 }
|
15
|
+
.header .navigation li#quit-nav a { background-color: #990000 }
|
16
|
+
.header .navigation li#quit-nav a:hover { color: #990000; background-color: #fff }
|
17
|
+
.header-shadow { width: 100%; height: 6px; position: absolute; top: 59px; left: 0; background-image: url('/images/shadow.png'); background-repeat: repeat-x }
|
18
|
+
|
19
|
+
.page { margin-top: 60px }
|
20
|
+
|
21
|
+
#messages { width: 500px; height: 300px; margin: 0 20px 10px 20px; padding: 0; overflow: auto; background-color: #fff; float: left; text-align: left; display: inline; }
|
22
|
+
#messages { list-style-type: none; overflow: auto }
|
23
|
+
#messages li { padding: 0.25em 0; border-bottom: 1px solid #f0f0f0; float: left; width: 100%; line-height: 1.5em }
|
24
|
+
#messages span.user { margin: 0; text-align: left; font-weight: bold; display: inline; float: left }
|
25
|
+
#messages .active { color: #000099 }
|
26
|
+
#messages .mentioned { color: #cccc00 }
|
27
|
+
#messages span.time { text-align: left; margin: 0 10px; display: block; float: left; color: #ccc !important; font-style: normal !important }
|
28
|
+
#messages span.message { display: inline; padding: 0 0 0 10px }
|
29
|
+
#messages .help { color: #990000 }
|
30
|
+
#messages .help span.command { width: 12em; display: block; float: left; font-weight: bold }
|
31
|
+
#messages li.server { color: #999; font-style: italic }
|
32
|
+
#messages li.error { color: #990000; font-style: italic; font-weight: bold }
|
33
|
+
#messages li.server span.time,
|
34
|
+
#messages li.error span.time { color: #999 }
|
35
|
+
img.inline-image { border: 1px solid #ccc; padding: 2px }
|
36
|
+
img.inline-image {
|
37
|
+
max-width: 200px;
|
38
|
+
width: expression(this.width > 200 ? 200: true);
|
39
|
+
}
|
40
|
+
|
41
|
+
#input { clear: both; text-align: left; margin: 0 0 0 20px; padding: 0; }
|
42
|
+
form { margin: 0; padding: 0; }
|
43
|
+
#message { width: 100% }
|
44
|
+
#message:focus { background-color: #ffc }
|
45
|
+
|
46
|
+
#info { text-align: left; margin: 0 0 0 20px }
|
47
|
+
#room-name { padding: 10px 0; font-size: 125% }
|
48
|
+
|
49
|
+
ul#names { margin: 0; padding: 0; list-style-type: none; height: 200px; overflow: auto; }
|
50
|
+
ul#names li { margin: 0; padding: 0.25em 0; }
|
51
|
+
ul#names li.idle { color: #777 }
|
52
|
+
|
53
|
+
.footer { border-top: 1px solid #ccc; padding: 8px 0 0 0; font-size: 80%; font-style: italic; color: #444; margin: 40px 0 0 0 }
|
54
|
+
|
55
|
+
/* Front page */
|
56
|
+
#sign-on { padding: 10px 0; margin: 10px auto; background-color: #fff; border: 1px solid #ccc; clear: both; text-align: center }
|
57
|
+
#sign-on input[type="text"] { width: 12em }
|
58
|
+
#sign-on input:focus { background-color: #ffc }
|
59
|
+
.content { text-align: left; margin: 0 20px; padding: 5px 0 }
|
60
|
+
.content em { padding: 1px 2px; font-style: normal; background-color: #ffc; color: #880000 }
|
61
|
+
.content h2 { margin-top: 20px }
|
62
|
+
|
63
|
+
#feedback .error { padding: 10px; border: 2px solid #990000; background-color: #fff; margin: 10px 0 }
|
64
|
+
#loading { background-color: #990000; color: #fff; font-weight: bold; padding: 3px; margin: 0 auto; position: absolute; z-index: 1020; top: 0; right: 10px; border-top: none }
|
65
|
+
.angry { color: #990000 }
|
66
|
+
.big_message { background-color: #fff; border: 1px solid #ccc; padding: 10px; }
|
67
|
+
.big_message h2, .big_message p { margin: 0; padding 0 }
|
68
|
+
.big_message h2 { margin-bottom: 10px }
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'sprockets'
|
4
|
+
|
5
|
+
sprockets_root = File.join(File.dirname(__FILE__), '..')
|
6
|
+
sprockets_config = YAML.load(IO.read(File.join(sprockets_root, 'config', 'sprockets.yml')))
|
7
|
+
|
8
|
+
secretary = Sprockets::Secretary.new(
|
9
|
+
:root => sprockets_root,
|
10
|
+
:load_path => sprockets_config[:load_path],
|
11
|
+
:source_files => sprockets_config[:source_files]
|
12
|
+
)
|
13
|
+
|
14
|
+
secretary.concatenation.save_to(File.join(sprockets_root, sprockets_config[:output_file]))
|
File without changes
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<div class="content">
|
2
|
+
<h2>About</h2>
|
3
|
+
<p>JsChat is an <em>open source</em> chat system that uses a simple protocol based on <acronym title="JavaScript Object Notation">JSON</acronym>.</p>
|
4
|
+
<p>To download the code and an irssi-like <em>console client</em>, visit <a href="http://github.com/alexyoung/jschat/">GitHub</a>.</p>
|
5
|
+
<p>Read more on the <a href="http://blog.jschat.org">JsChat Blog</a>.</p>
|
6
|
+
<h2>Try JsChat Now</h2>
|
7
|
+
<div id="feedback" class="error" style="display: none"></div>
|
8
|
+
<form method="post" action="/identify" id="sign-on">
|
9
|
+
Enter name: <input name="name" id="name" value="" type="text" />
|
10
|
+
and room: <input name="room" id="room" value="#jschat" type="text" />
|
11
|
+
<input type="submit" value="Go" id="sign-on-submit" />
|
12
|
+
</form>
|
13
|
+
<h2>Features</h2>
|
14
|
+
<ul>
|
15
|
+
<li>Simple protocol that makes it easy to implement clients and bots</li>
|
16
|
+
<li>Console client designed to look and feel like IRC clients</li>
|
17
|
+
<li>Web client auto links and displays images/YouTube videos inline</li>
|
18
|
+
<li>Protocol designed to be close to executable code, so creating clients and bots is easy</li>
|
19
|
+
</ul>
|
20
|
+
</div>
|
21
|
+
<div class="footer">
|
22
|
+
An open source project by <a href="http://helicoid.net">Helicoid</a>
|
23
|
+
</div>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
2
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
3
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
4
|
+
<head>
|
5
|
+
<title>JsChat iPhone</title>
|
6
|
+
<link rel="icon" href="/favicon.ico" />
|
7
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
8
|
+
<script src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.3/prototype.js" type="text/javascript"></script>
|
9
|
+
<script src="/javascripts/all.js?<%= Time.now.to_i %>" type="text/javascript"></script>
|
10
|
+
<link href="/stylesheets/screen.css?<%= Time.now.to_i %>" media="screen" rel="stylesheet" type="text/css" />
|
11
|
+
<link href="/stylesheets/iphone.css?<%= Time.now.to_i %>" media="screen" rel="stylesheet" type="text/css" />
|
12
|
+
</head>
|
13
|
+
<body class="iphone">
|
14
|
+
<div id="loading" style="display: none">Loading...</div>
|
15
|
+
<div class="header">
|
16
|
+
<h1><a href="/"><img src="/images/jschat.gif" alt="JsChat" /></a></h1>
|
17
|
+
<ul class="navigation">
|
18
|
+
<li><a href="/">Home</a></li>
|
19
|
+
<li><a href="http://github.com/alexyoung/jschat">Download</a>
|
20
|
+
<li id="help-nav" style="display: none"><a href="#" id="help-link">Help</a>
|
21
|
+
<li id="quit-nav" style="display: none"><a href="/quit">Quit</a>
|
22
|
+
</ul>
|
23
|
+
</div>
|
24
|
+
<div class="header-shadow"></div>
|
25
|
+
<div class="page">
|
26
|
+
<%= yield %>
|
27
|
+
</div>
|
28
|
+
</body>
|
29
|
+
</html>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
2
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
3
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
4
|
+
<head>
|
5
|
+
<title>JsChat</title>
|
6
|
+
<link rel="icon" href="/favicon.ico" />
|
7
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
8
|
+
<script src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.3/prototype.js" type="text/javascript"></script>
|
9
|
+
<script src="/javascripts/all.js?<%= Time.now.to_i %>" type="text/javascript"></script>
|
10
|
+
<link href="/stylesheets/screen.css?<%= Time.now.to_i %>" media="screen" rel="stylesheet" type="text/css" />
|
11
|
+
</head>
|
12
|
+
<body>
|
13
|
+
<div id="loading" style="display: none">Loading...</div>
|
14
|
+
<div class="header">
|
15
|
+
<h1><a href="/"><img src="/images/jschat.gif" alt="JsChat" /></a></h1>
|
16
|
+
<ul class="navigation">
|
17
|
+
<li><a href="/">Home</a></li>
|
18
|
+
<li><a href="http://github.com/alexyoung/jschat">Download</a>
|
19
|
+
<li id="help-nav" style="display: none"><a href="#" id="help-link">Help</a>
|
20
|
+
<li id="quit-nav" style="display: none"><a href="/quit">Quit</a>
|
21
|
+
</ul>
|
22
|
+
</div>
|
23
|
+
<div class="header-shadow"></div>
|
24
|
+
<div class="page">
|
25
|
+
<%= yield %>
|
26
|
+
</div>
|
27
|
+
</body>
|
28
|
+
</html>
|
29
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<ul id="messages">
|
2
|
+
</ul>
|
3
|
+
<div id="info">
|
4
|
+
<h2 id="room-name">Loading...</h2>
|
5
|
+
<div id="name" style="display: none"><%= nickname %></div>
|
6
|
+
<ul id="names">
|
7
|
+
</ul>
|
8
|
+
</div>
|
9
|
+
<div id="input">
|
10
|
+
<form method="post" action="/message" id="post_message">
|
11
|
+
<input name="message" id="message" value="" type="text" autocomplete="off" />
|
12
|
+
<input name="submit" type="submit" id="send_button" value="Send" />
|
13
|
+
</form>
|
14
|
+
</div>
|
15
|
+
|