jschat 0.3.3 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|