jschat 0.3.6 → 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ });