jschat 0.3.6 → 0.3.7

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.
@@ -92,5 +92,6 @@ JsChat was created by "Alex Young":http://alexyoung.org for "Helicoid":http://he
92
92
  * "gabrielg":http://github.com/gabrielg
93
93
  * "Simon Starr":http://github.com/sstarr
94
94
  * Kevin Ford
95
+ * "sekrett":http://github.com/sekrett
95
96
 
96
97
  If you'd like to contribute, send "alexyoung":http://github.com/alexyoung a message on GitHub.
@@ -1,3 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ $:.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
4
+
3
5
  require 'jschat/client'
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ $:.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
4
+
3
5
  require 'logger'
4
6
  require 'jschat/server'
5
7
  require 'jschat/server_options'
@@ -1,7 +1,41 @@
1
1
  #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
4
+
2
5
  require 'rubygems'
3
6
  require 'sinatra'
7
+ require 'getoptlong'
4
8
 
5
9
  set :environment, :production
10
+
11
+ def printusage(error_code)
12
+ print "Usage: jschat-web [options]\n\n"
13
+ print " -b, --bind=ADDRESS IP address\n"
14
+ print " -p, --port=PORT Port number\n"
15
+ print " -H, --help This text\n"
16
+
17
+ exit(error_code)
18
+ end
19
+
20
+ opts = GetoptLong.new(
21
+ [ "--bind", "-b", GetoptLong::REQUIRED_ARGUMENT ],
22
+ [ "--port", "-p", GetoptLong::REQUIRED_ARGUMENT ],
23
+ [ "--help", "-H", GetoptLong::NO_ARGUMENT ]
24
+ )
25
+
26
+ begin
27
+ opts.each do |opt, arg|
28
+ case opt
29
+ when "--bind"
30
+ set :bind, arg
31
+ when "--port"
32
+ set :port, arg
33
+ when "--help"
34
+ printusage(0)
35
+ end
36
+ end
37
+ end
38
+
6
39
  require 'jschat/http/jschat'
7
40
  Sinatra::Application.run!
41
+
@@ -6,8 +6,8 @@ module JsChat
6
6
  end
7
7
 
8
8
  # Note: This shouldn't really include 'display' directives
9
- def to_json
10
- { 'display' => 'error', 'error' => { 'message' => @message, 'code' => @code } }.to_json
9
+ def to_json(*a)
10
+ { 'display' => 'error', 'error' => { 'message' => @message, 'code' => @code } }.to_json(*a)
11
11
  end
12
12
  end
13
13
 
@@ -3,7 +3,6 @@ require 'sinatra'
3
3
  require 'sha1'
4
4
  gem 'json', '>= 1.1.9'
5
5
  require 'json'
6
- require 'sprockets'
7
6
  require 'jschat/init'
8
7
  require 'jschat/http/helpers/url_for'
9
8
 
@@ -522,13 +521,3 @@ post '/tweet' do
522
521
  error 500, 'You are not signed in with Twitter'
523
522
  end
524
523
  end
525
-
526
- # This serves the JavaScript concat'd by Sprockets
527
- # run script/sprocket.rb to cache this
528
- get '/javascripts/all.js' do
529
- root = File.join(File.dirname(File.expand_path(__FILE__)))
530
- sprockets_config = YAML.load(IO.read(File.join(root, 'config', 'sprockets.yml')))
531
- secretary = Sprockets::Secretary.new(sprockets_config.merge(:root => root))
532
- content_type 'text/javascript'
533
- secretary.concatenation.to_s
534
- end
@@ -0,0 +1,1305 @@
1
+ var JsChat = {};
2
+
3
+ document.observe('dom:loaded', function() {
4
+ JsChat.user = new User();
5
+
6
+ if ($('post_message')) {
7
+ var chatController = new JsChat.ChatController();
8
+ }
9
+
10
+ if ($('sign-on')) {
11
+ if (JsChat.user.name) {
12
+ $('name').value = JsChat.user.name;
13
+ }
14
+
15
+ if ($('room') && window.location.hash) {
16
+ $('room').value = window.location.hash;
17
+ }
18
+
19
+ var signOnController = new JsChat.SignOnController();
20
+ }
21
+ });
22
+ var History = Class.create({
23
+ initialize: function() {
24
+ this.messages = [];
25
+ this.index = 0;
26
+ this.limit = 100;
27
+ },
28
+
29
+ prev: function() {
30
+ this.index = this.index <= 0 ? this.messages.length - 1 : this.index - 1;
31
+ },
32
+
33
+ next: function() {
34
+ this.index = this.index >= this.messages.length - 1 ? 0 : this.index + 1;
35
+ },
36
+
37
+ reset: function() {
38
+ this.index = this.messages.length;
39
+ },
40
+
41
+ value: function() {
42
+ if (this.messages.length == 0) return '';
43
+ return this.messages[this.index];
44
+ },
45
+
46
+ add: function(value) {
47
+ if (!value || value.length == 0) return;
48
+
49
+ this.messages.push(value);
50
+ if (this.messages.length > this.limit) {
51
+ this.messages = this.messages.slice(-this.limit);
52
+ }
53
+ this.index = this.messages.length;
54
+ },
55
+
56
+ atTop: function() {
57
+ return this.index === this.messages.length;
58
+ }
59
+ });
60
+
61
+ var TabCompletion = Class.create({
62
+ initialize: function(element) {
63
+ this.element = $(element);
64
+ this.matches = [];
65
+ this.match_offset = 0;
66
+ this.cycling = false;
67
+ this.has_focus = true;
68
+ this.history = new History();
69
+
70
+ document.observe('keydown', this.keyboardEvents.bindAsEventListener(this));
71
+ this.element.observe('focus', this.onFocus.bindAsEventListener(this));
72
+ this.element.observe('blur', this.onBlur.bindAsEventListener(this));
73
+ this.element.observe('click', this.onFocus.bindAsEventListener(this));
74
+ },
75
+
76
+ onBlur: function() {
77
+ this.has_focus = false;
78
+ this.reset();
79
+ },
80
+
81
+ onFocus: function() {
82
+ this.has_focus = true;
83
+ this.reset();
84
+ },
85
+
86
+ tabSearch: function(input) {
87
+ var names = $$('#names li').collect(function(element) { return element.innerHTML }).sort();
88
+ return names.findAll(function(name) { return name.toLowerCase().match(input.toLowerCase()) });
89
+ },
90
+
91
+ textToLeft: function() {
92
+ var text = this.element.value;
93
+ var caret_position = FormHelpers.getCaretPosition(this.element);
94
+ if (caret_position < text.length) {
95
+ text = text.slice(0, caret_position);
96
+ }
97
+
98
+ text = text.split(' ').last();
99
+ return text;
100
+ },
101
+
102
+ elementFocused: function(e) {
103
+ if (typeof document.activeElement == 'undefined') {
104
+ return this.has_focus;
105
+ } else {
106
+ return document.activeElement == this.element;
107
+ }
108
+ },
109
+
110
+ keyboardEvents: function(e) {
111
+ if (this.elementFocused()) {
112
+ switch (e.keyCode) {
113
+ case Event.KEY_TAB:
114
+ var caret_position = FormHelpers.getCaretPosition(this.element);
115
+
116
+ if (this.element.value.length > 0) {
117
+ var search_text = '';
118
+ var search_result = '';
119
+ var replace_inline = false;
120
+ var editedText = this.element.value.match(/[^a-z0-9]/i);
121
+
122
+ if (this.cycling) {
123
+ if (this.element.value == '#{last_result}: '.interpolate({ last_result: this.last_result })) {
124
+ editedText = false;
125
+ } else {
126
+ replace_inline = true;
127
+ }
128
+ search_text = this.last_result;
129
+ } else if (editedText && this.matches.length == 0) {
130
+ search_text = this.textToLeft();
131
+ replace_inline = true;
132
+ } else {
133
+ search_text = this.element.value;
134
+ }
135
+
136
+ if (this.matches.length == 0) {
137
+ this.matches = this.tabSearch(search_text);
138
+ search_result = this.matches.first();
139
+ this.cycling = true;
140
+ } else {
141
+ this.match_offset++;
142
+ if (this.match_offset >= this.matches.length) {
143
+ this.match_offset = 0;
144
+ }
145
+ search_result = this.matches[this.match_offset];
146
+ }
147
+
148
+ if (search_result && search_result.length > 0) {
149
+ if (this.cycling && this.last_result) {
150
+ search_text = this.last_result;
151
+ }
152
+ this.last_result = search_result;
153
+
154
+ if (replace_inline) {
155
+ var slice_start = caret_position - search_text.length;
156
+ if (slice_start > 0) {
157
+ this.element.value = this.element.value.substr(0, slice_start) + search_result + this.element.value.substr(caret_position, this.element.value.length);
158
+ FormHelpers.setCaretPosition(this.element, slice_start + search_result.length);
159
+ }
160
+ } else if (!editedText) {
161
+ this.element.value = '#{search_result}: '.interpolate({ search_result: search_result });
162
+ }
163
+ }
164
+ }
165
+
166
+ Event.stop(e);
167
+ return false;
168
+ break;
169
+
170
+ case Event.KEY_UP:
171
+ if (this.history.atTop()) {
172
+ this.history.add(this.element.value);
173
+ }
174
+
175
+ this.history.prev();
176
+ this.element.value = this.history.value();
177
+ FormHelpers.setCaretPosition(this.element, this.element.value.length + 1);
178
+ Event.stop(e);
179
+ return false;
180
+ break;
181
+
182
+ case Event.KEY_DOWN:
183
+ this.history.next();
184
+ this.element.value = this.history.value();
185
+ FormHelpers.setCaretPosition(this.element, this.element.value.length + 1);
186
+ Event.stop(e);
187
+ return false;
188
+ break;
189
+
190
+ default:
191
+ this.reset();
192
+ break;
193
+ }
194
+ }
195
+ },
196
+
197
+ reset: function() {
198
+ this.matches = [];
199
+ this.match_offset = 0;
200
+ this.last_result = null;
201
+ this.cycling = false;
202
+ }
203
+ });
204
+ var UserCommands = {
205
+ '/emotes': function() {
206
+ var text = '';
207
+ Display.add_message('<strong>Available Emotes</strong> &mdash; Prefix with a : to use', 'help');
208
+ Display.add_message(EmoteHelper.legalEmotes.join(', '), 'help');
209
+ },
210
+
211
+ '/help': function() {
212
+ var help = [];
213
+ Display.add_message('<strong>JsChat Help</strong> &mdash; Type the following commands into the message field:', 'help')
214
+ help.push(['/clear', 'Clears messages']);
215
+ help.push(['/join #room_name', 'Joins a room']);
216
+ help.push(['/part #room_name', 'Leaves a room. Leave room_name blank for the current room']);
217
+ help.push(['/lastlog', 'Shows recent activity']);
218
+ help.push(['/search query', 'Searches the logs for this room']);
219
+ help.push(['/names', 'Refreshes the names list']);
220
+ help.push(['/name new_name', 'Changes your name']);
221
+ help.push(['/toggle images', 'Toggles showing of images and videos']);
222
+ help.push(['/quit', 'Quit']);
223
+ help.push(['/emotes', 'Shows available emotes']);
224
+ $A(help).each(function(options) {
225
+ var help_text = '<span class="command">#{command}</span><span class="command_help">#{text}</span>'.interpolate({ command: options[0], text: options[1]});
226
+ Display.add_message(help_text, 'help');
227
+ });
228
+ },
229
+
230
+ '/clear': function() {
231
+ $('messages').innerHTML = '';
232
+ },
233
+
234
+ '/lastlog': function() {
235
+ this.pausePollers = true;
236
+ $('messages').innerHTML = '';
237
+ JsChat.Request.get('/lastlog', function(transport) {
238
+ this.displayMessages(transport.responseText);
239
+ $('names').innerHTML = '';
240
+ this.updateNames();
241
+ this.pausePollers = false;
242
+ }.bind(this));
243
+ },
244
+
245
+ '/search\\s+(.*)': function(query) {
246
+ query = query[1];
247
+ this.pausePollers = true;
248
+ $('messages').innerHTML = '';
249
+ JsChat.Request.get('/search?q=' + query, function(transport) {
250
+ Display.add_message('Search results:', 'server');
251
+ this.displayMessages(transport.responseText);
252
+ this.pausePollers = false;
253
+ }.bind(this));
254
+ },
255
+
256
+ '/(name|nick)\\s+(.*)': function(name) {
257
+ name = name[2];
258
+ new Ajax.Request('/change-name', {
259
+ method: 'post',
260
+ parameters: { name: name },
261
+ onSuccess: function(response) {
262
+ this.displayMessages(response.responseText);
263
+ JsChat.user.setName(name);
264
+ this.updateNames();
265
+ }.bind(this),
266
+ onFailure: function() {
267
+ Display.add_message("Server error: couldn't access: #{url}".interpolate({ url: url }), 'server');
268
+ }
269
+ });
270
+ },
271
+
272
+ '/names': function() {
273
+ this.updateNames();
274
+ },
275
+
276
+ '/toggle images': function() {
277
+ JsChat.user.setHideImages(!JsChat.user.hideImages);
278
+ Display.add_message("Hide images set to #{hide}".interpolate({ hide: JsChat.user.hideImages }), 'server');
279
+ },
280
+
281
+ '/(join)\\s+(.*)': function() {
282
+ var room = arguments[0][2];
283
+ this.validateAndJoinRoom(room);
284
+ },
285
+
286
+ '/(part|leave)': function() {
287
+ this.partRoom(PageHelper.currentRoom());
288
+ },
289
+
290
+ '/(part|leave)\\s+(.*)': function() {
291
+ var room = arguments[0][2];
292
+ this.partRoom(room);
293
+ },
294
+
295
+ '/tweet\\s+(.*)': function() {
296
+ var message = arguments[0][1];
297
+ this.sendTweet(message);
298
+ },
299
+
300
+ '/quit': function() {
301
+ window.location = '/quit';
302
+ }
303
+ };
304
+ var Display = {
305
+ scrolled: false,
306
+
307
+ add_message: function(text, className, time) {
308
+ var time_html = '<span class="time">\#{time}</span>'.interpolate({ time: TextHelper.dateText(time) });
309
+ $('messages').insert({ bottom: '<li class="' + className + '">' + time_html + ' ' + text + '</li>' });
310
+ this.scrollMessagesToTop();
311
+ },
312
+
313
+ addImageOnLoads: function() {
314
+ $$('#messages li').last().select('img').each(function(element) {
315
+ element.observe('load', this.scrollMessagesToTop);
316
+ }.bind(this));
317
+ },
318
+
319
+ message: function(message, time) {
320
+ var name = JsChat.user.name;
321
+ var user_class = name == message['user'] ? 'user active' : 'user';
322
+ var text = '<span class="\#{user_class}">\#{user}</span> <span class="\#{message_class}">\#{message}</span>';
323
+ var blurred_mention = '';
324
+
325
+ if (message['message'].match(new RegExp(name, 'i')) && name != message['user']) {
326
+ user_class = 'user mentioned';
327
+ blurred_mention = '*';
328
+ }
329
+
330
+ Display.clearIdleState(message['user']);
331
+
332
+ text = text.interpolate({
333
+ user_class: user_class,
334
+ room: message['room'],
335
+ user: TextHelper.truncateName(message['user']),
336
+ message: TextHelper.decorateMessage(message['message']),
337
+ message_class: 'message'
338
+ });
339
+
340
+ this.add_message(text, 'message', time);
341
+ this.addImageOnLoads();
342
+
343
+ if (this.show_unread) {
344
+ this.unread++;
345
+ document.title = 'JsChat: (' + this.unread + blurred_mention + ') new messages';
346
+ }
347
+ },
348
+
349
+ messages: function(messages) {
350
+ $('messages').innerHTML = '';
351
+ this.ignore_notices = true;
352
+
353
+ $A(messages).each(function(json) {
354
+ try {
355
+ if (json['change']) {
356
+ Change[json['change']](json[json['change']]);
357
+ } else {
358
+ this[json['display']](json[json['display']]);
359
+ }
360
+ } catch (exception) {
361
+ }
362
+ }.bind(this));
363
+
364
+ this.ignore_notices = false;
365
+ this.scrollMessagesToTop();
366
+ /* This is assumed to be the point at which displaying /lastlog completes */
367
+ $('loading').hide();
368
+ },
369
+
370
+ scrollMessagesToTop: function() {
371
+ if (!this.scrolled) {
372
+ $('messages').scrollTop = $('messages').scrollHeight;
373
+ }
374
+ },
375
+
376
+ clearIdleState: function(user_name) {
377
+ $$('#names li').each(function(element) {
378
+ if (element.innerHTML == user_name && element.hasClassName('idle')) {
379
+ element.lastIdle = (new Date());
380
+ element.removeClassName('idle');
381
+ }
382
+ });
383
+ },
384
+
385
+ isIdle: function(dateValue) {
386
+ try {
387
+ var d = typeof dateValue == 'string' ? new Date(Date.parse(dateValue)) : dateValue,
388
+ now = new Date();
389
+ if (((now - d) / 1000) > (60 * 5)) {
390
+ return true;
391
+ }
392
+ } catch (exception) {
393
+ console.log(exception);
394
+ }
395
+ return false;
396
+ },
397
+
398
+ names: function(users) {
399
+ $('names').innerHTML = '';
400
+ users.each(function(user) {
401
+ var name = user['name'],
402
+ list_class = this.isIdle(user['last_activity']) ? 'idle' : '',
403
+ element = $(document.createElement('li'));
404
+
405
+ element.addClassName(list_class);
406
+ element.innerHTML = TextHelper.truncateName(name);
407
+ $('names').insert({ bottom: element });
408
+
409
+ try {
410
+ // Record the last idle time so the idle state can be dynamically updated
411
+ element.lastIdle = new Date(Date.parse(user['last_activity']));
412
+ } catch (exception) {
413
+ element.lastIdle = null;
414
+ }
415
+ }.bind(this));
416
+ },
417
+
418
+ join: function(join) {
419
+ $('room-name').innerHTML = TextHelper.truncateRoomName(join['room']);
420
+ $('room-name').title = PageHelper.currentRoom();
421
+ },
422
+
423
+ join_notice: function(join, time) {
424
+ this.add_user(join['user']);
425
+ this.add_message(join['user'] + ' has joined the room', 'server', time);
426
+ },
427
+
428
+ add_user: function(name) {
429
+ if (!this.ignore_notices) {
430
+ $('names').insert({ bottom: '<li>' + TextHelper.truncateName(name) + '</li>' });
431
+ }
432
+ },
433
+
434
+ remove_user: function(name) {
435
+ if (!this.ignore_notices) {
436
+ $$('#names li').each(function(element) { if (element.innerHTML == name) element.remove(); });
437
+ }
438
+ },
439
+
440
+ part_notice: function(part, time) {
441
+ this.remove_user(part['user']);
442
+ this.add_message(part['user'] + ' has left the room', 'server', time);
443
+ },
444
+
445
+ quit_notice: function(quit, time) {
446
+ this.remove_user(quit['user']);
447
+ this.add_message(quit['user'] + ' has quit', 'server', time);
448
+ },
449
+
450
+ notice: function(notice) {
451
+ this.add_message(notice, 'server');
452
+ },
453
+
454
+ error: function(error) {
455
+ this.add_message(error['message'], 'error');
456
+ }
457
+ };
458
+ /* FIXME: Later on this should be a class */
459
+ JsChat.Request = {
460
+ get: function(url, callback) {
461
+ new Ajax.Request(url, {
462
+ method: 'get',
463
+ parameters: { time: new Date().getTime(), room: PageHelper.currentRoom() },
464
+ onFailure: function() {
465
+ Display.add_message("Server error: couldn't access: #{url}".interpolate({ url: url }), 'server');
466
+ },
467
+ onComplete: function(transport) { return callback(transport); }
468
+ });
469
+ }
470
+ };
471
+ var Change = {
472
+ user: function(user, time) {
473
+ if (user['name']) {
474
+ change = $H(user['name']).toArray()[0];
475
+ var old = change[0],
476
+ new_value = change[1];
477
+ if (new_value !== PageHelper.nickname()) {
478
+ Display.add_message("#{old} is now known as #{new_value}".interpolate({ old: old, new_value: new_value }), 'server', time);
479
+ }
480
+ $$('#names li').each(function(element) {
481
+ if (element.innerHTML == old) element.innerHTML = new_value;
482
+ });
483
+ }
484
+ }
485
+ };
486
+ User = function() {
487
+ this.name = Cookie.find('jschat-name');
488
+ this.hideImages = Cookie.find('jschat-hideImages') === '1' ? true : false;
489
+ };
490
+
491
+ User.prototype.setName = function(name) {
492
+ Cookie.create('jschat-name', name, 28, '/');
493
+ this.name = name;
494
+ };
495
+
496
+ User.prototype.setHideImages = function(hideImages) {
497
+ this.hideImages = hideImages;
498
+ Cookie.create('jschat-hideImages', (hideImages ? '1' : '0'), 28, '/');
499
+ };
500
+ Cookie = {
501
+ create: function(name, value, days, path) {
502
+ var expires = '';
503
+ path = typeof path == 'undefined' ? '/' : path;
504
+
505
+ if (days) {
506
+ var date = new Date();
507
+ date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
508
+ expires = "; expires=" + date.toGMTString();
509
+ }
510
+
511
+ if (name && value) {
512
+ document.cookie = name + '=' + escape(value) + expires + ';path=' + path;
513
+ }
514
+ },
515
+
516
+ find: function(name) {
517
+ var matches = document.cookie.match(name + '=([^;]*)');
518
+ if (matches && matches.length == 2) {
519
+ return unescape(matches[1]);
520
+ }
521
+ },
522
+
523
+ destroy: function(name) {
524
+ this.create(name, ' ', -1);
525
+ }
526
+ };
527
+ /*
528
+ Cross-Browser Split 0.3
529
+ By Steven Levithan <http://stevenlevithan.com>
530
+ MIT license
531
+ Provides a consistent cross-browser, ECMA-262 v3 compliant split method
532
+ */
533
+
534
+ String.prototype._$$split = String.prototype._$$split || String.prototype.split;
535
+
536
+ String.prototype.split = function (s /* separator */, limit) {
537
+ // if separator is not a regex, use the native split method
538
+ if (!(s instanceof RegExp))
539
+ return String.prototype._$$split.apply(this, arguments);
540
+
541
+ var flags = (s.global ? "g" : "") + (s.ignoreCase ? "i" : "") + (s.multiline ? "m" : ""),
542
+ s2 = new RegExp("^" + s.source + "$", flags),
543
+ output = [],
544
+ origLastIndex = s.lastIndex,
545
+ lastLastIndex = 0,
546
+ i = 0, match, lastLength;
547
+
548
+ /* behavior for limit: if it's...
549
+ - undefined: no limit
550
+ - NaN or zero: return an empty array
551
+ - a positive number: use limit after dropping any decimal
552
+ - a negative number: no limit
553
+ - other: type-convert, then use the above rules
554
+ */
555
+ if (limit === undefined || +limit < 0) {
556
+ limit = false;
557
+ } else {
558
+ limit = Math.floor(+limit);
559
+ if (!limit)
560
+ return [];
561
+ }
562
+
563
+ if (s.global)
564
+ s.lastIndex = 0;
565
+ else
566
+ s = new RegExp(s.source, "g" + flags);
567
+
568
+ while ((!limit || i++ <= limit) && (match = s.exec(this))) {
569
+ var emptyMatch = !match[0].length;
570
+
571
+ // Fix IE's infinite-loop-resistant but incorrect lastIndex
572
+ if (emptyMatch && s.lastIndex > match.index)
573
+ s.lastIndex--;
574
+
575
+ if (s.lastIndex > lastLastIndex) {
576
+ // Fix browsers whose exec methods don't consistently return undefined for non-participating capturing groups
577
+ if (match.length > 1) {
578
+ match[0].replace(s2, function () {
579
+ for (var j = 1; j < arguments.length - 2; j++) {
580
+ if (arguments[j] === undefined)
581
+ match[j] = undefined;
582
+ }
583
+ });
584
+ }
585
+
586
+ output = output.concat(this.slice(lastLastIndex, match.index));
587
+ if (1 < match.length && match.index < this.length)
588
+ output = output.concat(match.slice(1));
589
+ lastLength = match[0].length; // only needed if s.lastIndex === this.length
590
+ lastLastIndex = s.lastIndex;
591
+ }
592
+
593
+ if (emptyMatch)
594
+ s.lastIndex++; // avoid an infinite loop
595
+ }
596
+
597
+ // since this uses test(), output must be generated before restoring lastIndex
598
+ output = lastLastIndex === this.length ?
599
+ (s.test("") && !lastLength ? output : output.concat("")) :
600
+ (limit ? output : output.concat(this.slice(lastLastIndex)));
601
+ s.lastIndex = origLastIndex; // only needed if s.global, else we're working with a copy of the regex
602
+ return output;
603
+ };
604
+
605
+ var TextHelper = {
606
+ zeroPad: function(value, length) {
607
+ value = value.toString();
608
+ if (value.length >= length) {
609
+ return value;
610
+ } else {
611
+ return this.zeroPad('0' + value, length);
612
+ }
613
+ },
614
+
615
+ dateText: function(time) {
616
+ var d = new Date();
617
+ if (typeof time != 'undefined') {
618
+ d = new Date(Date.parse(time));
619
+ }
620
+ return this.zeroPad(d.getHours(), 2) + ':' + this.zeroPad(d.getMinutes(), 2);
621
+ },
622
+
623
+ truncateName: function(text) {
624
+ return text.truncate(15);
625
+ },
626
+
627
+ truncateRoomName: function(text) {
628
+ return text.truncate(15);
629
+ },
630
+
631
+ decorateMessage: function(text) {
632
+ return EmoteHelper.insertEmotes(this.autoLink(this.textilize(text)));
633
+ },
634
+
635
+ textilize: function(text) {
636
+ function escape_regex(text) { return text.replace(/([\*\?\+\^\?])/g, "\\$1"); }
637
+ function openTag(text) { return '<' + text + '>'; }
638
+ function closeTag(text) { return '</' + text + '>'; }
639
+
640
+ var map = { '_': 'em', '*': 'strong' };
641
+
642
+ $H(map).each(function(mapping) {
643
+ var result = '';
644
+ var m = escape_regex(mapping[0]);
645
+ var mr = new RegExp('(' + m + ')');
646
+ var matcher = new RegExp('(^|\\s+)(' + m + ')([^\\s][^' + mapping[0] + ']*[^\\s])(' + m + ')', 'g');
647
+
648
+ if (text.match(matcher)) {
649
+ var open = false;
650
+ text.split(matcher).each(function(segment) {
651
+ if (segment == mapping[0]) {
652
+ var tag = open ? closeTag(mapping[1]) : openTag(mapping[1]);
653
+ result += segment.replace(mr, tag);
654
+ open = !open;
655
+ } else {
656
+ result += segment;
657
+ }
658
+ });
659
+
660
+ if (open) result += closeTag(mapping[1]);
661
+ text = result;
662
+ }
663
+ });
664
+
665
+ return text;
666
+ },
667
+
668
+ autoLink: function(text) {
669
+ var result = '';
670
+ try {
671
+ if (!LinkHelper.url(text)) {
672
+ return text;
673
+ }
674
+
675
+ $A(text.split(/(https?:\/\/[^\s]*)/gi)).each(function(link) {
676
+ if (link.match(/href="/)) {
677
+ result += link;
678
+ } else {
679
+ if (LinkHelper.youtube_url(link) && !JsChat.user.hideImages) {
680
+ result += link.replace(link, LinkHelper.youtube(link));
681
+ } else if (LinkHelper.vimeo_url(link) && !JsChat.user.hideImages) {
682
+ result += link.replace(link, LinkHelper.vimeo(link));
683
+ } else if (LinkHelper.image_url(link) && !JsChat.user.hideImages) {
684
+ result += link.replace(link, LinkHelper.image(link));
685
+ } else if (LinkHelper.twitpic_url(link) && !JsChat.user.hideImages) {
686
+ result += link.replace(link, LinkHelper.twitpic(link));
687
+ } else if (LinkHelper.url(link)) {
688
+ result += link.replace(link, LinkHelper.link(link));
689
+ } else {
690
+ result += link;
691
+ }
692
+ }
693
+ });
694
+ } catch (exception) {
695
+ }
696
+ return result;
697
+ }
698
+ };
699
+ var PageHelper = {
700
+ currentRoom: function() {
701
+ return window.location.hash;
702
+ },
703
+
704
+ setCurrentRoomName: function(roomName) {
705
+ window.location.hash = roomName;
706
+ $('room-name').innerHTML = TextHelper.truncateRoomName(PageHelper.currentRoom());
707
+ $('room-name').title = PageHelper.currentRoom();
708
+ document.title = PageHelper.title();
709
+ },
710
+
711
+ allRoomNames: function() {
712
+ return $$('#rooms li a').collect(function(link) {
713
+ return link.innerHTML;
714
+ });
715
+ },
716
+
717
+ nickname: function() {
718
+ return JsChat.user.name;
719
+ },
720
+
721
+ title: function() {
722
+ if (PageHelper.currentRoom()) {
723
+ return 'JsChat: ' + PageHelper.currentRoom();
724
+ } else {
725
+ return 'JsChat';
726
+ }
727
+ },
728
+
729
+ device: function() {
730
+ if ($$('body.iphone').length > 0) {
731
+ return 'iphone';
732
+ } else if ($$('body.ipad').length > 0) {
733
+ return 'ipad';
734
+ }
735
+ },
736
+
737
+ isDevice: function(device) {
738
+ return PageHelper.device() == device;
739
+ }
740
+ };
741
+ var LinkHelper = {
742
+ url: function(url) {
743
+ return url.match(/(https?:\/\/[^\s]*)/gi);
744
+ },
745
+
746
+ link: function(url) {
747
+ return '<a href="\#{url}" target="_blank">\#{link_name}</a>'.interpolate({ url: url, link_name: url});
748
+ },
749
+
750
+ image_url: function(url) {
751
+ return url.match(/\.(jpe?g|png|gif)/i);
752
+ },
753
+
754
+ image: function(url) {
755
+ return '<a href="\#{url}" target="_blank"><img class="inline-image" src="\#{image}" /></a>'.interpolate({ url: url, image: url })
756
+ },
757
+
758
+ twitpic_url: function(url) {
759
+ return url.match(/\bhttp:\/\/twitpic.com\/(show|[^\s]*)\b/i);
760
+ },
761
+
762
+ twitpic: function(url) {
763
+ var twitpic_id = url.split('/').last();
764
+ 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 })
765
+ },
766
+
767
+ youtube_url: function(url) {
768
+ return url.match(/youtube\.com/) && url.match(/watch\?v/);
769
+ },
770
+
771
+ youtube: function(url) {
772
+ var youtube_url_id = url.match(/\?v=([^&\s]*)/);
773
+ if (youtube_url_id && youtube_url_id[1]) {
774
+ var youtube_url = 'http://www.youtube.com/v/' + youtube_url_id[1];
775
+ var youtube_html = '<object width="480" height="295"><param name="movie" value="#{movie_url}"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="#{url}" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="480" height="295"></embed></object>';
776
+ return youtube_html.interpolate({ movie_url: youtube_url, url: youtube_url });
777
+ } else {
778
+ return this.link(url);
779
+ }
780
+ },
781
+
782
+ vimeo_url: function(url) {
783
+ return url.match(/vimeo\.com/) && url.match(/\/\d+/);
784
+ },
785
+
786
+ vimeo: function(url) {
787
+ var vimeo_url_id = url.match(/\d+/);
788
+ if (vimeo_url_id) {
789
+ var vimeo_url = 'http://vimeo.com/' + vimeo_url_id;
790
+ var vimeo_html = '<object width="560" height="315"><param name="allowfullscreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id=' + vimeo_url_id + '&amp;server=vimeo.com&amp;show_title=1&amp;show_byline=0&amp;show_portrait=0&amp;color=969696&amp;fullscreen=1" /><embed src="http://vimeo.com/moogaloop.swf?clip_id=' + vimeo_url_id + '&amp;server=vimeo.com&amp;show_title=1&amp;show_byline=0&amp;show_portrait=0&amp;color=969696&amp;fullscreen=1" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="560" height="315"></embed></object>';
791
+ return vimeo_html.interpolate({ movie_url: vimeo_url, url: vimeo_url });
792
+ } else {
793
+ return this.link(url);
794
+ }
795
+ }
796
+ };
797
+ var FormHelpers = {
798
+ getCaretPosition: function(element) {
799
+ if (element.setSelectionRange) {
800
+ return element.selectionStart;
801
+ } else if (element.createTextRange) {
802
+ try {
803
+ // The current selection
804
+ var range = document.selection.createRange();
805
+ // We'll use this as a 'dummy'
806
+ var stored_range = range.duplicate();
807
+ // Select all text
808
+ stored_range.moveToElementText(element);
809
+ // Now move 'dummy' end point to end point of original range
810
+ stored_range.setEndPoint('EndToEnd', range);
811
+
812
+ return stored_range.text.length - range.text.length;
813
+ } catch (exception) {
814
+ // IE is being mental. TODO: Figure out what IE's issue is
815
+ return 0;
816
+ }
817
+ }
818
+ },
819
+
820
+ setCaretPosition: function(element, pos) {
821
+ if (element.setSelectionRange) {
822
+ element.focus()
823
+ element.setSelectionRange(pos, pos)
824
+ } else if (element.createTextRange) {
825
+ var range = element.createTextRange()
826
+
827
+ range.collapse(true)
828
+ range.moveEnd('character', pos)
829
+ range.moveStart('character', pos)
830
+ range.select()
831
+ }
832
+ }
833
+ };
834
+ var EmoteHelper = {
835
+ legalEmotes: ['angry', 'arr', 'blink', 'blush', 'brucelee', 'btw', 'chuckle', 'clap', 'cool', 'drool', 'drunk', 'dry', 'eek', 'flex', 'happy', 'holmes', 'huh', 'laugh', 'lol', 'mad', 'mellow', 'noclue', 'oh', 'ohmy', 'panic', 'ph34r', 'pimp', 'punch', 'realmad', 'rock', 'rofl', 'rolleyes', 'sad', 'scratch', 'shifty', 'shock', 'shrug', 'sleep', 'sleeping', 'smile', 'suicide', 'sweat', 'thumbs', 'tongue', 'unsure', 'w00t', 'wacko', 'whistling', 'wink', 'worship', 'yucky'],
836
+
837
+ emoteToImage: function(emote) {
838
+ var result = emote;
839
+ emote = emote.replace(/^:/, '').toLowerCase();
840
+ if (EmoteHelper.legalEmotes.find(function(v) { return v == emote })) {
841
+ result = '<img src="/images/emoticons/#{emote}.gif" alt="#{description}" />'.interpolate({ emote: emote, description: emote });
842
+ }
843
+ return result;
844
+ },
845
+
846
+ insertEmotes: function(text) {
847
+ var result = '';
848
+ $A(text.split(/(:[^ ]*)/)).each(function(segment) {
849
+ if (segment && segment.match(/^:/)) {
850
+ segment = EmoteHelper.emoteToImage(segment);
851
+ }
852
+ result += segment;
853
+ });
854
+ return result;
855
+ }
856
+ };
857
+ JsChat.SignOnController = Class.create({
858
+ initialize: function() {
859
+ this.retries = 0;
860
+ setTimeout(function() { $('name').activate(); }, 500);
861
+ $('sign-on').observe('submit', this.submitEvent.bindAsEventListener(this));
862
+ },
863
+
864
+ submitEvent: function(e) {
865
+ this.signOn();
866
+ Event.stop(e);
867
+ return false;
868
+ },
869
+
870
+ showError: function(message) {
871
+ $('feedback').innerHTML = '<div class="error">#{message}</div>'.interpolate({ message: message });
872
+ $('feedback').show();
873
+ $('sign-on-submit').enable();
874
+ },
875
+
876
+ signOn: function() {
877
+ $('loading').show();
878
+ $('sign-on-submit').disable();
879
+ this.retries += 1;
880
+
881
+ new Ajax.Request('/identify', {
882
+ parameters: $('sign-on').serialize(true),
883
+ onSuccess: function(transport) {
884
+ try {
885
+ var json = transport.responseText.evalJSON(true);
886
+ if (json['action'] == 'reload' && this.retries < 4) {
887
+ setTimeout(function() { this.signOn() }.bind(this), 50);
888
+ } else if (json['action'] == 'redirect') {
889
+ if (window.location.toString().match(new RegExp(json['to'] + '$'))) {
890
+ window.location.reload();
891
+ } else {
892
+ window.location = json['to'];
893
+ }
894
+ } else if (json['error']) {
895
+ this.showError(json['error']['message']);
896
+ $('loading').hide();
897
+ } else {
898
+ this.showError('Connection error');
899
+ }
900
+ } catch (exception) {
901
+ this.showError('Connection error: #{error}'.interpolate({ error: exception }));
902
+ }
903
+ }.bind(this),
904
+ onFailure: function() {
905
+ this.showError('Connection error');
906
+ }.bind(this),
907
+ onComplete: function() {
908
+ $('loading').hide();
909
+ }
910
+ });
911
+ }
912
+ });
913
+ JsChat.ChatController = Class.create({
914
+ initialize: function() {
915
+ $('loading').show();
916
+
917
+ this.resizeEvent();
918
+ setTimeout(this.initDisplay.bind(this), 50);
919
+ this.tabCompletion = new TabCompletion('message');
920
+
921
+ Event.observe(window, 'focus', this.focusEvent.bindAsEventListener(this));
922
+ Event.observe(window, 'blur', this.blurEvent.bindAsEventListener(this));
923
+ Event.observe(window, 'resize', this.resizeEvent.bindAsEventListener(this));
924
+
925
+ $('post_message').observe('submit', this.postMessageFormEvent.bindAsEventListener(this));
926
+ $('messages').observe('scroll', this.messagesScrolled.bindAsEventListener(this));
927
+ $$('#rooms li.join a').first().observe('click', this.joinRoomClicked.bindAsEventListener(this));
928
+ Event.observe(document, 'click', this.roomTabClick.bindAsEventListener(this));
929
+ this.allRecentMessages();
930
+ },
931
+
932
+ allRecentMessages: function() {
933
+ new Ajax.Request('/room_update_times', {
934
+ method: 'get',
935
+ onComplete: function(request) {
936
+ var times = request.responseText.evalJSON();
937
+ $H(this.lastUpdateTimes).each(function(data) {
938
+ var room = data[0],
939
+ time = data[1];
940
+ if (Date.parse(time) < Date.parse(times[room])) {
941
+ this.roomTabAlert(room);
942
+ }
943
+ }.bind(this));
944
+ this.lastUpdateTimes = times;
945
+ }.bind(this)
946
+ });
947
+ },
948
+
949
+ roomTabAlert: function(room) {
950
+ if (room === PageHelper.currentRoom()) return;
951
+
952
+ $$('ul#rooms li a').each(function(roomLink) {
953
+ if (roomLink.innerHTML === room) {
954
+ roomLink.addClassName('new');
955
+ }
956
+ });
957
+ },
958
+
959
+ clearRoomTabAlert: function(room) {
960
+ $$('ul#rooms li a').each(function(roomLink) {
961
+ if (roomLink.innerHTML === room) {
962
+ roomLink.removeClassName('new');
963
+ }
964
+ });
965
+ },
966
+
967
+ joinRoomClicked: function(e) {
968
+ this.addRoomPrompt(e);
969
+ Event.stop(e);
970
+ return false;
971
+ },
972
+
973
+ roomTabClick: function(e) {
974
+ var element = Event.element(e);
975
+
976
+ if (element.tagName == 'A' && element.up('#rooms') && !element.up('li').hasClassName('join')) {
977
+ this.switchRoom(element.innerHTML);
978
+ Event.stop(e);
979
+ return false;
980
+ }
981
+ },
982
+
983
+ messagesScrolled: function() {
984
+ Display.scrolled = (($('messages').scrollHeight - $('messages').scrollTop) > $('messages').getHeight());
985
+ },
986
+
987
+ focusEvent: function() {
988
+ Display.unread = 0;
989
+ Display.show_unread = false;
990
+ document.title = PageHelper.title();
991
+ },
992
+
993
+ blurEvent: function() {
994
+ Display.show_unread = true;
995
+ },
996
+
997
+ resizeEvent: function() {
998
+ var messageInset = PageHelper.isDevice('iphone') ? 390 : 290,
999
+ heightInset = PageHelper.isDevice('iphone') ? 200 : 100,
1000
+ windowSize = document.viewport.getDimensions();
1001
+
1002
+ if (PageHelper.isDevice('ipad')) {
1003
+ messageInset = 330;
1004
+ heightInset = 130;
1005
+ }
1006
+
1007
+ $('messages').setStyle({ width: windowSize.width - 220 + 'px' });
1008
+ $('messages').setStyle({ height: windowSize.height - heightInset + 'px' });
1009
+ $('message').setStyle({ width: windowSize.width - messageInset + 'px' });
1010
+ $('names').setStyle({ height: windowSize.height - 200 + 'px' });
1011
+ Display.scrollMessagesToTop();
1012
+ },
1013
+
1014
+ postMessageFormEvent: function(e) {
1015
+ try {
1016
+ var element = Event.element(e);
1017
+ var message = $('message').value;
1018
+ $('message').value = '';
1019
+
1020
+ this.tabCompletion.history.add(message);
1021
+
1022
+ if (message.length > 0) {
1023
+ var command_posted = $H(UserCommands).find(function(command) {
1024
+ var name = command[0];
1025
+ var matches = message.match(new RegExp('^' + name + '$'));
1026
+ if (matches) {
1027
+ command[1].bind(this)(matches);
1028
+ return true;
1029
+ }
1030
+ }.bind(this));
1031
+
1032
+ if (!command_posted) {
1033
+ if (message.match(/^\/\s?\//)) {
1034
+ this.postMessage(message.replace(/\//, '').strip());
1035
+ } else if (message.match(/^\//)) {
1036
+ Display.add_message('Error: Command not found. Use /help display commands.', 'error');
1037
+ } else {
1038
+ this.postMessage(message);
1039
+ }
1040
+ }
1041
+ }
1042
+ } catch (exception) {
1043
+ console.log(exception);
1044
+ }
1045
+
1046
+ Event.stop(e);
1047
+ return false;
1048
+ },
1049
+
1050
+ postMessage: function(message) {
1051
+ Display.message({ 'message': message.escapeHTML(), 'user': JsChat.user.name }, new Date());
1052
+ new Ajax.Request('/message', {
1053
+ method: 'post',
1054
+ parameters: { 'message': message, 'to': PageHelper.currentRoom() }
1055
+ });
1056
+ },
1057
+
1058
+ sendTweet: function(message) {
1059
+ new Ajax.Request('/tweet', {
1060
+ method: 'post',
1061
+ parameters: { 'tweet': message }
1062
+ });
1063
+ },
1064
+
1065
+ initDisplay: function() {
1066
+ Display.unread = 0;
1067
+ Display.show_unread = false;
1068
+ Display.ignore_notices = false;
1069
+
1070
+ PageHelper.setCurrentRoomName(window.location.hash);
1071
+ $('message').activate();
1072
+ $$('.header .navigation li').invoke('hide');
1073
+ $('quit-nav').show();
1074
+ $('help-nav').show();
1075
+
1076
+ $('help-link').observe('click', function(e) {
1077
+ UserCommands['/help']();
1078
+ $('message').activate();
1079
+ Event.stop(e);
1080
+ return false;
1081
+ });
1082
+
1083
+ this.createPollers();
1084
+ this.getRoomList(this.addRoomAndCheckSelected);
1085
+ this.joinRoom(PageHelper.currentRoom());
1086
+ },
1087
+
1088
+ getRoomList: function(callback) {
1089
+ new Ajax.Request('/rooms', {
1090
+ method: 'get',
1091
+ parameters: { time: new Date().getTime() },
1092
+ onComplete: function(response) {
1093
+ response.responseText.evalJSON().sort().each(function(roomName) {
1094
+ try {
1095
+ callback.apply(this, [roomName]);
1096
+ } catch (exception) {
1097
+ console.log(exception);
1098
+ }
1099
+ }.bind(this));
1100
+ }.bind(this)
1101
+ });
1102
+ },
1103
+
1104
+ joinRoom: function(roomName) {
1105
+ new Ajax.Request('/join', {
1106
+ method: 'post',
1107
+ parameters: { time: new Date().getTime(), room: roomName },
1108
+ onFailure: function() {
1109
+ Display.add_message("Error: Couldn't join channel", 'server');
1110
+ $('loading').hide();
1111
+ },
1112
+ onComplete: function() {
1113
+ // Make the server update the last polled time
1114
+ JsChat.Request.get('/messages', function() {});
1115
+ document.title = PageHelper.title();
1116
+ UserCommands['/lastlog'].apply(this);
1117
+ $('loading').hide();
1118
+ $('rooms').show();
1119
+ this.addRoomToNav(roomName, true);
1120
+ }.bind(this)
1121
+ });
1122
+ },
1123
+
1124
+ isValidRoom: function(roomName) {
1125
+ if (PageHelper.allRoomNames().include(roomName)) {
1126
+ return false;
1127
+ }
1128
+ return true;
1129
+ },
1130
+
1131
+ validateAndJoinRoom: function(roomName) {
1132
+ if (roomName === null || roomName.length == 0) {
1133
+ return;
1134
+ }
1135
+
1136
+ if (!roomName.match(/^#/)) {
1137
+ roomName = '#' + roomName;
1138
+ }
1139
+
1140
+ if (this.isValidRoom(roomName)) {
1141
+ this.joinRoomInTab(roomName);
1142
+ }
1143
+ },
1144
+
1145
+ addRoomPrompt: function() {
1146
+ var roomName = prompt('Enter a room name:');
1147
+ this.validateAndJoinRoom(roomName);
1148
+ },
1149
+
1150
+ addRoomToNav: function(roomName, selected) {
1151
+ if (PageHelper.allRoomNames().include(roomName)) return;
1152
+
1153
+ var classAttribute = selected ? ' class="selected"' : '';
1154
+ $('rooms').insert({ bottom: '<li#{classAttribute}><a href="#{roomName}">#{roomName}</a></li>'.interpolate({ classAttribute: classAttribute, roomName: roomName }) });
1155
+ },
1156
+
1157
+ addRoomAndCheckSelected: function(roomName) {
1158
+ this.addRoomToNav(roomName, PageHelper.currentRoom() == roomName);
1159
+ },
1160
+
1161
+ removeSelectedTab: function() {
1162
+ $$('#rooms .selected').invoke('removeClassName', 'selected');
1163
+ },
1164
+
1165
+ selectRoomTab: function(roomName) {
1166
+ $$('#rooms a').each(function(a) {
1167
+ if (a.innerHTML == roomName) {
1168
+ a.up('li').addClassName('selected');
1169
+ }
1170
+ });
1171
+ },
1172
+
1173
+ joinRoomInTab: function(roomName) {
1174
+ this.removeSelectedTab();
1175
+ PageHelper.setCurrentRoomName(roomName);
1176
+ this.joinRoom(roomName);
1177
+ $('message').focus();
1178
+ },
1179
+
1180
+ switchRoom: function(roomName) {
1181
+ if (PageHelper.currentRoom() == roomName) {
1182
+ return;
1183
+ }
1184
+
1185
+ this.removeSelectedTab();
1186
+ this.selectRoomTab(roomName);
1187
+ PageHelper.setCurrentRoomName(roomName);
1188
+ UserCommands['/lastlog'].apply(this);
1189
+ this.clearRoomTabAlert(roomName);
1190
+ $('message').focus();
1191
+ },
1192
+
1193
+ rooms: function() {
1194
+ return $$('#rooms li a').slice(1).collect(function(element) {
1195
+ return element.innerHTML;
1196
+ });
1197
+ },
1198
+
1199
+ partRoom: function(roomName) {
1200
+ if (this.rooms().length == 1) {
1201
+ return UserCommands['/quit']();
1202
+ }
1203
+
1204
+ new Ajax.Request('/part', {
1205
+ method: 'get',
1206
+ parameters: { room: roomName },
1207
+ onSuccess: function(request) {
1208
+ this.removeTab(roomName);
1209
+ }.bind(this),
1210
+ onFailure: function(request) {
1211
+ Display.add_message('Error: ' + request.responseText, 'server');
1212
+ }
1213
+ });
1214
+ },
1215
+
1216
+ removeTab: function(roomName) {
1217
+ $$('#rooms li').each(function(element) {
1218
+ if (element.down('a').innerHTML == roomName) {
1219
+ element.remove();
1220
+
1221
+ if (roomName == PageHelper.currentRoom()) {
1222
+ this.switchRoom($$('#rooms li a')[1].innerHTML);
1223
+ }
1224
+ }
1225
+ }.bind(this));
1226
+ },
1227
+
1228
+ updateNames: function() {
1229
+ JsChat.Request.get('/names', function(t) { this.displayMessages(t.responseText); }.bind(this));
1230
+ },
1231
+
1232
+ showMessagesResponse: function(transport) {
1233
+ try {
1234
+ this.displayMessages(transport.responseText);
1235
+
1236
+ if ($$('#messages li').length > 1000) {
1237
+ $$('#messages li').slice(0, 500).invoke('remove');
1238
+ }
1239
+ } catch (exception) {
1240
+ console.log(transport.responseText);
1241
+ console.log(exception);
1242
+ }
1243
+ },
1244
+
1245
+ updateMessages: function() {
1246
+ if (this.pausePollers) {
1247
+ return;
1248
+ }
1249
+
1250
+ new Ajax.Request('/messages', {
1251
+ method: 'get',
1252
+ parameters: { time: new Date().getTime(), room: PageHelper.currentRoom() },
1253
+ onSuccess: function(transport) {
1254
+ this.showMessagesResponse(transport);
1255
+ }.bind(this),
1256
+ onFailure: function(request) {
1257
+ this.stopPolling();
1258
+ Display.add_message('Server error: <a href="/#{room}">please reconnect</a>'.interpolate({ room: PageHelper.currentRoom() }), 'server');
1259
+ }.bind(this)
1260
+ });
1261
+ },
1262
+
1263
+ displayMessages: function(text, successCallback) {
1264
+ var json_set = text.evalJSON(true);
1265
+ if (json_set.length == 0) {
1266
+ return;
1267
+ }
1268
+ json_set.each(function(json) {
1269
+ try {
1270
+ if (json['change']) {
1271
+ Change[json['change']](json[json['change']], json['time']);
1272
+ } else {
1273
+ Display[json['display']](json[json['display']], json['time']);
1274
+ if (json['display'] !== 'error' && typeof successCallback !== 'undefined') {
1275
+ successCallback();
1276
+ }
1277
+ }
1278
+ } catch (exception) {
1279
+ }
1280
+ });
1281
+ },
1282
+
1283
+ checkIdleNames: function() {
1284
+ $$('#names li').each(function(element) {
1285
+ if (Display.isIdle(element.lastIdle)) {
1286
+ element.addClassName('idle');
1287
+ }
1288
+ });
1289
+ },
1290
+
1291
+ stopPolling: function() {
1292
+ this.pollers.invoke('stop');
1293
+ },
1294
+
1295
+ firePollers: function() {
1296
+ this.pollers.invoke('execute');
1297
+ },
1298
+
1299
+ createPollers: function() {
1300
+ this.pollers = $A();
1301
+ this.pollers.push(new PeriodicalExecuter(this.updateMessages.bind(this), 3));
1302
+ this.pollers.push(new PeriodicalExecuter(this.checkIdleNames.bind(this), 5));
1303
+ this.pollers.push(new PeriodicalExecuter(this.allRecentMessages.bind(this), 10));
1304
+ }
1305
+ });