jschat 0.3.3 → 0.3.5
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/jschat-server +10 -0
- data/lib/jschat/client.rb +1 -0
- data/lib/jschat/http/jschat.rb +48 -6
- data/lib/jschat/http/public/javascripts/app/controllers/chat_controller.js +22 -7
- data/lib/jschat/http/public/javascripts/app/helpers/link_helper.js +9 -0
- data/lib/jschat/http/public/javascripts/app/helpers/text_helper.js +2 -0
- data/lib/jschat/http/public/javascripts/app/models/user.js +0 -1
- data/lib/jschat/http/public/javascripts/app/ui/commands.js +7 -1
- data/lib/jschat/http/public/javascripts/app/ui/tab_completion.js +61 -1
- data/lib/jschat/http/views/ipad.erb +32 -0
- data/lib/jschat/http/views/iphone.erb +2 -1
- data/lib/jschat/http/views/iphone_message_form.erb +15 -0
- data/lib/jschat/http/views/message_form.erb +0 -1
- data/lib/jschat/server.rb +1 -0
- data/test/test_helper.rb +1 -0
- metadata +5 -3
data/bin/jschat-server
CHANGED
@@ -4,4 +4,14 @@ require 'logger'
|
|
4
4
|
require 'jschat/server'
|
5
5
|
require 'jschat/server_options'
|
6
6
|
|
7
|
+
def reload!
|
8
|
+
puts 'Reloading Files'
|
9
|
+
Gem.clear_paths
|
10
|
+
spec = Gem.searcher.find('jschat/server')
|
11
|
+
load File.join(spec.full_gem_path, 'lib', 'jschat', 'server.rb')
|
12
|
+
load File.join(spec.full_gem_path, 'lib', 'jschat', 'server_options.rb')
|
13
|
+
end
|
14
|
+
|
15
|
+
trap 'SIGHUP', lambda { reload! }
|
16
|
+
|
7
17
|
JsChat::Server.run!
|
data/lib/jschat/client.rb
CHANGED
data/lib/jschat/http/jschat.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'sinatra'
|
3
3
|
require 'sha1'
|
4
|
+
gem 'json', '>= 1.1.9'
|
4
5
|
require 'json'
|
5
6
|
require 'sprockets'
|
6
7
|
require 'jschat/init'
|
@@ -59,6 +60,10 @@ before do
|
|
59
60
|
|
60
61
|
if twitter_user?
|
61
62
|
load_twitter_user_and_set_bridge_id
|
63
|
+
|
64
|
+
unless valid_twitter_client_id?
|
65
|
+
clear_cookies
|
66
|
+
end
|
62
67
|
end
|
63
68
|
end
|
64
69
|
end
|
@@ -177,11 +182,25 @@ helpers do
|
|
177
182
|
end
|
178
183
|
|
179
184
|
def detected_layout
|
180
|
-
iphone_user_agent?
|
185
|
+
if iphone_user_agent?
|
186
|
+
:iphone
|
187
|
+
elsif ipad_user_agent?
|
188
|
+
:ipad
|
189
|
+
else
|
190
|
+
:layout
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def detected_message_form
|
195
|
+
iphone_user_agent? ? :iphone_message_form : :message_form
|
181
196
|
end
|
182
197
|
|
183
198
|
def iphone_user_agent?
|
184
|
-
request.env["HTTP_USER_AGENT"] && request.env["HTTP_USER_AGENT"][/(
|
199
|
+
request.env["HTTP_USER_AGENT"] && request.env["HTTP_USER_AGENT"][/(\(iPhone)/]
|
200
|
+
end
|
201
|
+
|
202
|
+
def ipad_user_agent?
|
203
|
+
request.env["HTTP_USER_AGENT"] && request.env["HTTP_USER_AGENT"][/(\(iPad)/]
|
185
204
|
end
|
186
205
|
|
187
206
|
def load_bridge
|
@@ -238,7 +257,8 @@ helpers do
|
|
238
257
|
options = load_twitter_user.merge(options).merge({
|
239
258
|
'twitter_name' => session[:twitter_name],
|
240
259
|
'access_token' => session[:access_token],
|
241
|
-
'secret_token' => session[:secret_token]
|
260
|
+
'secret_token' => session[:secret_token],
|
261
|
+
'client_id' => session[:client_id]
|
242
262
|
})
|
243
263
|
JsChat::Storage.driver.save_user(options)
|
244
264
|
end
|
@@ -258,6 +278,10 @@ helpers do
|
|
258
278
|
JsChat::Storage.driver.find_user({ 'twitter_name' => session[:twitter_name] }) || {}
|
259
279
|
end
|
260
280
|
|
281
|
+
def valid_twitter_client_id?
|
282
|
+
session[:client_id] == load_twitter_user['client_id']
|
283
|
+
end
|
284
|
+
|
261
285
|
def load_twitter_user_and_set_bridge_id
|
262
286
|
user = load_twitter_user
|
263
287
|
if user['jschat_id'] and user['jschat_id'].size > 0
|
@@ -268,6 +292,11 @@ helpers do
|
|
268
292
|
def nickname
|
269
293
|
request.cookies['jschat-name']
|
270
294
|
end
|
295
|
+
|
296
|
+
def unique_token
|
297
|
+
chars = ("a".."z").to_a + ("1".."9").to_a
|
298
|
+
Array.new(8, '').collect { chars[rand(chars.size)] }.join
|
299
|
+
end
|
271
300
|
end
|
272
301
|
|
273
302
|
# Identify
|
@@ -311,8 +340,10 @@ get '/messages' do
|
|
311
340
|
else
|
312
341
|
if @bridge.last_error and @bridge.last_error['error']['code'] == 107
|
313
342
|
error 500, [@bridge.last_error].to_json
|
314
|
-
|
343
|
+
elsif @bridge.last_error
|
315
344
|
[@bridge.last_error].to_json
|
345
|
+
else
|
346
|
+
error 500, 'Unknown error'
|
316
347
|
end
|
317
348
|
end
|
318
349
|
end
|
@@ -367,7 +398,7 @@ end
|
|
367
398
|
get '/chat/' do
|
368
399
|
load_bridge
|
369
400
|
if @bridge and @bridge.active?
|
370
|
-
erb
|
401
|
+
erb detected_message_form, :layout => detected_layout
|
371
402
|
else
|
372
403
|
erb :index, :layout => detected_layout
|
373
404
|
end
|
@@ -375,7 +406,7 @@ end
|
|
375
406
|
|
376
407
|
post '/message' do
|
377
408
|
load_bridge
|
378
|
-
save_last_room params['
|
409
|
+
save_last_room params['to']
|
379
410
|
@bridge.send_message params['message'], params['to']
|
380
411
|
'OK'
|
381
412
|
end
|
@@ -424,6 +455,7 @@ get '/twitter_auth' do
|
|
424
455
|
)
|
425
456
|
rescue OAuth::Unauthorized => exception
|
426
457
|
puts exception
|
458
|
+
halt "Unable to login with Twitter: #{exception.class}"
|
427
459
|
end
|
428
460
|
|
429
461
|
if @twitter.authorized?
|
@@ -441,6 +473,7 @@ get '/twitter_auth' do
|
|
441
473
|
end
|
442
474
|
|
443
475
|
session[:jschat_id] = user['jschat_id'] if user['jschat_id'] and !user['jschat_id'].empty?
|
476
|
+
session[:client_id] = unique_token
|
444
477
|
save_nickname name
|
445
478
|
save_twitter_user('twitter_name' => @twitter.info['screen_name'],
|
446
479
|
'jschat_id' => session[:jschat_id],
|
@@ -475,6 +508,15 @@ get '/twitter_auth' do
|
|
475
508
|
end
|
476
509
|
end
|
477
510
|
|
511
|
+
# TODO: This doesn't seem to work with twitter oauth right now
|
512
|
+
post '/tweet' do
|
513
|
+
if twitter_user? and @twitter.authorized?
|
514
|
+
@twitter.update(params['tweet'])
|
515
|
+
else
|
516
|
+
error 500, 'You are not signed in with Twitter'
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
478
520
|
# This serves the JavaScript concat'd by Sprockets
|
479
521
|
# run script/sprocket.rb to cache this
|
480
522
|
get '/javascripts/all.js' do
|
@@ -99,6 +99,8 @@ JsChat.ChatController = Class.create({
|
|
99
99
|
var message = $('message').value;
|
100
100
|
$('message').value = '';
|
101
101
|
|
102
|
+
this.tabCompletion.history.add(message);
|
103
|
+
|
102
104
|
if (message.length > 0) {
|
103
105
|
var command_posted = $H(UserCommands).find(function(command) {
|
104
106
|
var name = command[0];
|
@@ -110,14 +112,12 @@ JsChat.ChatController = Class.create({
|
|
110
112
|
}.bind(this));
|
111
113
|
|
112
114
|
if (!command_posted) {
|
113
|
-
if (message.match(
|
115
|
+
if (message.match(/^\/\s?\//)) {
|
116
|
+
this.postMessage(message.replace(/\//, '').strip());
|
117
|
+
} else if (message.match(/^\//)) {
|
114
118
|
Display.add_message('Error: Command not found. Use /help display commands.', 'error');
|
115
119
|
} else {
|
116
|
-
|
117
|
-
new Ajax.Request('/message', {
|
118
|
-
method: 'post',
|
119
|
-
parameters: { 'message': message, 'to': PageHelper.currentRoom() }
|
120
|
-
});
|
120
|
+
this.postMessage(message);
|
121
121
|
}
|
122
122
|
}
|
123
123
|
}
|
@@ -129,6 +129,21 @@ JsChat.ChatController = Class.create({
|
|
129
129
|
return false;
|
130
130
|
},
|
131
131
|
|
132
|
+
postMessage: function(message) {
|
133
|
+
Display.message({ 'message': message.escapeHTML(), 'user': JsChat.user.name }, new Date());
|
134
|
+
new Ajax.Request('/message', {
|
135
|
+
method: 'post',
|
136
|
+
parameters: { 'message': message, 'to': PageHelper.currentRoom() }
|
137
|
+
});
|
138
|
+
},
|
139
|
+
|
140
|
+
sendTweet: function(message) {
|
141
|
+
new Ajax.Request('/tweet', {
|
142
|
+
method: 'post',
|
143
|
+
parameters: { 'tweet': message }
|
144
|
+
});
|
145
|
+
},
|
146
|
+
|
132
147
|
initDisplay: function() {
|
133
148
|
Display.unread = 0;
|
134
149
|
Display.show_unread = false;
|
@@ -293,7 +308,7 @@ JsChat.ChatController = Class.create({
|
|
293
308
|
},
|
294
309
|
|
295
310
|
updateNames: function() {
|
296
|
-
|
311
|
+
JsChat.Request.get('/names', function(t) { this.displayMessages(t.responseText); }.bind(this));
|
297
312
|
},
|
298
313
|
|
299
314
|
showMessagesResponse: function(transport) {
|
@@ -15,6 +15,15 @@ var LinkHelper = {
|
|
15
15
|
return '<a href="\#{url}" target="_blank"><img class="inline-image" src="\#{image}" /></a>'.interpolate({ url: url, image: url })
|
16
16
|
},
|
17
17
|
|
18
|
+
twitpic_url: function(url) {
|
19
|
+
return url.match(/\bhttp:\/\/twitpic.com\/(show|[^\s]*)\b/i);
|
20
|
+
},
|
21
|
+
|
22
|
+
twitpic: function(url) {
|
23
|
+
var twitpic_id = url.split('/').last();
|
24
|
+
return '<a href="\#{url}" target="_blank"><img class="inline-image" src="http://twitpic.com/show/mini/\#{twitpic_id}" /></a>'.interpolate({ twitpic_id: twitpic_id, url: url })
|
25
|
+
},
|
26
|
+
|
18
27
|
youtube_url: function(url) {
|
19
28
|
return url.match(/youtube\.com/) && url.match(/watch\?v/);
|
20
29
|
},
|
@@ -78,6 +78,8 @@ var TextHelper = {
|
|
78
78
|
result += link.replace(link, LinkHelper.vimeo(link));
|
79
79
|
} else if (LinkHelper.image_url(link) && !JsChat.user.hideImages) {
|
80
80
|
result += link.replace(link, LinkHelper.image(link));
|
81
|
+
} else if (LinkHelper.twitpic_url(link) && !JsChat.user.hideImages) {
|
82
|
+
result += link.replace(link, LinkHelper.twitpic(link));
|
81
83
|
} else if (LinkHelper.url(link)) {
|
82
84
|
result += link.replace(link, LinkHelper.link(link));
|
83
85
|
} else {
|
@@ -58,6 +58,7 @@ var UserCommands = {
|
|
58
58
|
onSuccess: function(response) {
|
59
59
|
this.displayMessages(response.responseText);
|
60
60
|
JsChat.user.setName(name);
|
61
|
+
this.updateNames();
|
61
62
|
}.bind(this),
|
62
63
|
onFailure: function() {
|
63
64
|
Display.add_message("Server error: couldn't access: #{url}".interpolate({ url: url }), 'server');
|
@@ -66,7 +67,7 @@ var UserCommands = {
|
|
66
67
|
},
|
67
68
|
|
68
69
|
'/names': function() {
|
69
|
-
|
70
|
+
this.updateNames();
|
70
71
|
},
|
71
72
|
|
72
73
|
'/toggle images': function() {
|
@@ -88,6 +89,11 @@ var UserCommands = {
|
|
88
89
|
this.partRoom(room);
|
89
90
|
},
|
90
91
|
|
92
|
+
'/tweet\\s+(.*)': function() {
|
93
|
+
var message = arguments[0][1];
|
94
|
+
this.sendTweet(message);
|
95
|
+
},
|
96
|
+
|
91
97
|
'/quit': function() {
|
92
98
|
window.location = '/quit';
|
93
99
|
}
|
@@ -1,3 +1,42 @@
|
|
1
|
+
var History = Class.create({
|
2
|
+
initialize: function() {
|
3
|
+
this.messages = [];
|
4
|
+
this.index = 0;
|
5
|
+
this.limit = 100;
|
6
|
+
},
|
7
|
+
|
8
|
+
prev: function() {
|
9
|
+
this.index = this.index <= 0 ? this.messages.length - 1 : this.index - 1;
|
10
|
+
},
|
11
|
+
|
12
|
+
next: function() {
|
13
|
+
this.index = this.index >= this.messages.length - 1 ? 0 : this.index + 1;
|
14
|
+
},
|
15
|
+
|
16
|
+
reset: function() {
|
17
|
+
this.index = this.messages.length;
|
18
|
+
},
|
19
|
+
|
20
|
+
value: function() {
|
21
|
+
if (this.messages.length == 0) return '';
|
22
|
+
return this.messages[this.index];
|
23
|
+
},
|
24
|
+
|
25
|
+
add: function(value) {
|
26
|
+
if (!value || value.length == 0) return;
|
27
|
+
|
28
|
+
this.messages.push(value);
|
29
|
+
if (this.messages.length > this.limit) {
|
30
|
+
this.messages = this.messages.slice(-this.limit);
|
31
|
+
}
|
32
|
+
this.index = this.messages.length;
|
33
|
+
},
|
34
|
+
|
35
|
+
atTop: function() {
|
36
|
+
return this.index === this.messages.length;
|
37
|
+
}
|
38
|
+
});
|
39
|
+
|
1
40
|
var TabCompletion = Class.create({
|
2
41
|
initialize: function(element) {
|
3
42
|
this.element = $(element);
|
@@ -5,6 +44,7 @@ var TabCompletion = Class.create({
|
|
5
44
|
this.match_offset = 0;
|
6
45
|
this.cycling = false;
|
7
46
|
this.has_focus = true;
|
47
|
+
this.history = new History();
|
8
48
|
|
9
49
|
document.observe('keydown', this.keyboardEvents.bindAsEventListener(this));
|
10
50
|
this.element.observe('focus', this.onFocus.bindAsEventListener(this));
|
@@ -23,7 +63,7 @@ var TabCompletion = Class.create({
|
|
23
63
|
},
|
24
64
|
|
25
65
|
tabSearch: function(input) {
|
26
|
-
var names = $$('#names li').collect(function(element) { return element.innerHTML });
|
66
|
+
var names = $$('#names li').collect(function(element) { return element.innerHTML }).sort();
|
27
67
|
return names.findAll(function(name) { return name.toLowerCase().match(input.toLowerCase()) });
|
28
68
|
},
|
29
69
|
|
@@ -106,6 +146,26 @@ var TabCompletion = Class.create({
|
|
106
146
|
return false;
|
107
147
|
break;
|
108
148
|
|
149
|
+
case Event.KEY_UP:
|
150
|
+
if (this.history.atTop()) {
|
151
|
+
this.history.add(this.element.value);
|
152
|
+
}
|
153
|
+
|
154
|
+
this.history.prev();
|
155
|
+
this.element.value = this.history.value();
|
156
|
+
FormHelpers.setCaretPosition(this.element, this.element.value.length + 1);
|
157
|
+
Event.stop(e);
|
158
|
+
return false;
|
159
|
+
break;
|
160
|
+
|
161
|
+
case Event.KEY_DOWN:
|
162
|
+
this.history.next();
|
163
|
+
this.element.value = this.history.value();
|
164
|
+
FormHelpers.setCaretPosition(this.element, this.element.value.length + 1);
|
165
|
+
Event.stop(e);
|
166
|
+
return false;
|
167
|
+
break;
|
168
|
+
|
109
169
|
default:
|
110
170
|
this.reset();
|
111
171
|
break;
|
@@ -0,0 +1,32 @@
|
|
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 iPad</title>
|
6
|
+
<link rel="icon" href="/favicon.ico" />
|
7
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
8
|
+
<meta name="apple-mobile-web-app-capable" content="yes" />
|
9
|
+
<script src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.3/prototype.js" type="text/javascript"></script>
|
10
|
+
<script src="/javascripts/all.js?<%= Time.now.to_i %>" type="text/javascript"></script>
|
11
|
+
<link href="/stylesheets/screen.css?<%= Time.now.to_i %>" media="screen" rel="stylesheet" type="text/css" />
|
12
|
+
</head>
|
13
|
+
<body class="ipad">
|
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 id="rooms" class="rooms" style="display: none">
|
18
|
+
<li class="join"><a href="#">+</a></li>
|
19
|
+
</ul>
|
20
|
+
<ul class="navigation">
|
21
|
+
<li><a href="/">Home</a></li>
|
22
|
+
<li><a href="http://github.com/alexyoung/jschat">Download</a>
|
23
|
+
<li id="help-nav" style="display: none"><a href="#" id="help-link">Help</a>
|
24
|
+
<li id="quit-nav" style="display: none"><a href="/quit">Quit</a>
|
25
|
+
</ul>
|
26
|
+
</div>
|
27
|
+
<div class="header-shadow"></div>
|
28
|
+
<div class="page">
|
29
|
+
<%= yield %>
|
30
|
+
</div>
|
31
|
+
</body>
|
32
|
+
</html>
|
@@ -5,12 +5,13 @@
|
|
5
5
|
<title>JsChat iPhone</title>
|
6
6
|
<link rel="icon" href="/favicon.ico" />
|
7
7
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
8
|
+
<meta name="apple-mobile-web-app-capable" content="yes" />
|
8
9
|
<script src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.3/prototype.js" type="text/javascript"></script>
|
9
10
|
<script src="/javascripts/all.js?<%= Time.now.to_i %>" type="text/javascript"></script>
|
10
11
|
<link href="/stylesheets/screen.css?<%= Time.now.to_i %>" media="screen" rel="stylesheet" type="text/css" />
|
11
12
|
<link href="/stylesheets/iphone.css?<%= Time.now.to_i %>" media="screen" rel="stylesheet" type="text/css" />
|
12
13
|
</head>
|
13
|
-
<body class="iphone">
|
14
|
+
<body class="iphone" onload="setTimeout(function() { window.scrollTo(0, 1); }, 100);">
|
14
15
|
<div id="loading" style="display: none">Loading...</div>
|
15
16
|
<div class="header">
|
16
17
|
<h1><a href="/"><img src="/images/jschat.gif" alt="JsChat" /></a></h1>
|
@@ -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" autocapitalize="off" />
|
12
|
+
<input name="submit" type="submit" id="send_button" value="Send" />
|
13
|
+
</form>
|
14
|
+
</div>
|
15
|
+
|
data/lib/jschat/server.rb
CHANGED
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 3
|
8
|
-
-
|
9
|
-
version: 0.3.
|
8
|
+
- 5
|
9
|
+
version: 0.3.5
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Alex R. Young
|
@@ -183,7 +183,9 @@ files:
|
|
183
183
|
- lib/jschat/http/tmp/restart.txt
|
184
184
|
- lib/jschat/http/views/form.erb
|
185
185
|
- lib/jschat/http/views/index.erb
|
186
|
+
- lib/jschat/http/views/ipad.erb
|
186
187
|
- lib/jschat/http/views/iphone.erb
|
188
|
+
- lib/jschat/http/views/iphone_message_form.erb
|
187
189
|
- lib/jschat/http/views/layout.erb
|
188
190
|
- lib/jschat/http/views/message_form.erb
|
189
191
|
- lib/jschat/http/views/twitter.erb
|
@@ -198,7 +200,7 @@ files:
|
|
198
200
|
- test/test_helper.rb
|
199
201
|
- README.textile
|
200
202
|
- MIT-LICENSE
|
201
|
-
has_rdoc:
|
203
|
+
has_rdoc: false
|
202
204
|
homepage: http://github.com/alexyoung/jschat
|
203
205
|
licenses: []
|
204
206
|
|