jschat 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. data/MIT-LICENSE +23 -0
  2. data/README.textile +71 -0
  3. data/bin/jschat-client +3 -0
  4. data/bin/jschat-server +18 -0
  5. data/bin/jschat-web +7 -0
  6. data/lib/jschat/client.rb +745 -0
  7. data/lib/jschat/errors.rb +41 -0
  8. data/lib/jschat/flood_protection.rb +39 -0
  9. data/lib/jschat/http/config/sprockets.yml +7 -0
  10. data/lib/jschat/http/config.ru +12 -0
  11. data/lib/jschat/http/jschat.rb +264 -0
  12. data/lib/jschat/http/public/favicon.ico +0 -0
  13. data/lib/jschat/http/public/images/emoticons/angry.gif +0 -0
  14. data/lib/jschat/http/public/images/emoticons/arr.gif +0 -0
  15. data/lib/jschat/http/public/images/emoticons/blink.gif +0 -0
  16. data/lib/jschat/http/public/images/emoticons/blush.gif +0 -0
  17. data/lib/jschat/http/public/images/emoticons/brucelee.gif +0 -0
  18. data/lib/jschat/http/public/images/emoticons/btw.gif +0 -0
  19. data/lib/jschat/http/public/images/emoticons/chuckle.gif +0 -0
  20. data/lib/jschat/http/public/images/emoticons/clap.gif +0 -0
  21. data/lib/jschat/http/public/images/emoticons/cool.gif +0 -0
  22. data/lib/jschat/http/public/images/emoticons/drool.gif +0 -0
  23. data/lib/jschat/http/public/images/emoticons/drunk.gif +0 -0
  24. data/lib/jschat/http/public/images/emoticons/dry.gif +0 -0
  25. data/lib/jschat/http/public/images/emoticons/eek.gif +0 -0
  26. data/lib/jschat/http/public/images/emoticons/flex.gif +0 -0
  27. data/lib/jschat/http/public/images/emoticons/happy.gif +0 -0
  28. data/lib/jschat/http/public/images/emoticons/holmes.gif +0 -0
  29. data/lib/jschat/http/public/images/emoticons/huh.gif +0 -0
  30. data/lib/jschat/http/public/images/emoticons/laugh.gif +0 -0
  31. data/lib/jschat/http/public/images/emoticons/lol.gif +0 -0
  32. data/lib/jschat/http/public/images/emoticons/mad.gif +0 -0
  33. data/lib/jschat/http/public/images/emoticons/mellow.gif +0 -0
  34. data/lib/jschat/http/public/images/emoticons/noclue.gif +0 -0
  35. data/lib/jschat/http/public/images/emoticons/oh.gif +0 -0
  36. data/lib/jschat/http/public/images/emoticons/ohmy.gif +0 -0
  37. data/lib/jschat/http/public/images/emoticons/ph34r.gif +0 -0
  38. data/lib/jschat/http/public/images/emoticons/pimp.gif +0 -0
  39. data/lib/jschat/http/public/images/emoticons/punch.gif +0 -0
  40. data/lib/jschat/http/public/images/emoticons/realmad.gif +0 -0
  41. data/lib/jschat/http/public/images/emoticons/rock.gif +0 -0
  42. data/lib/jschat/http/public/images/emoticons/rofl.gif +0 -0
  43. data/lib/jschat/http/public/images/emoticons/rolleyes.gif +0 -0
  44. data/lib/jschat/http/public/images/emoticons/sad.gif +0 -0
  45. data/lib/jschat/http/public/images/emoticons/scratch.gif +0 -0
  46. data/lib/jschat/http/public/images/emoticons/shifty.gif +0 -0
  47. data/lib/jschat/http/public/images/emoticons/shock.gif +0 -0
  48. data/lib/jschat/http/public/images/emoticons/shrug.gif +0 -0
  49. data/lib/jschat/http/public/images/emoticons/sleep.gif +0 -0
  50. data/lib/jschat/http/public/images/emoticons/sleeping.gif +0 -0
  51. data/lib/jschat/http/public/images/emoticons/smile.gif +0 -0
  52. data/lib/jschat/http/public/images/emoticons/suicide.gif +0 -0
  53. data/lib/jschat/http/public/images/emoticons/sweat.gif +0 -0
  54. data/lib/jschat/http/public/images/emoticons/thumbs.gif +0 -0
  55. data/lib/jschat/http/public/images/emoticons/tongue.gif +0 -0
  56. data/lib/jschat/http/public/images/emoticons/unsure.gif +0 -0
  57. data/lib/jschat/http/public/images/emoticons/w00t.gif +0 -0
  58. data/lib/jschat/http/public/images/emoticons/wacko.gif +0 -0
  59. data/lib/jschat/http/public/images/emoticons/whistling.gif +0 -0
  60. data/lib/jschat/http/public/images/emoticons/wink.gif +0 -0
  61. data/lib/jschat/http/public/images/emoticons/worship.gif +0 -0
  62. data/lib/jschat/http/public/images/emoticons/yucky.gif +0 -0
  63. data/lib/jschat/http/public/images/jschat.gif +0 -0
  64. data/lib/jschat/http/public/images/shadow.png +0 -0
  65. data/lib/jschat/http/public/javascripts/app/controllers/chat_controller.js +191 -0
  66. data/lib/jschat/http/public/javascripts/app/controllers/signon_controller.js +56 -0
  67. data/lib/jschat/http/public/javascripts/app/helpers/emote_helper.js +23 -0
  68. data/lib/jschat/http/public/javascripts/app/helpers/form_helpers.js +37 -0
  69. data/lib/jschat/http/public/javascripts/app/helpers/link_helper.js +47 -0
  70. data/lib/jschat/http/public/javascripts/app/helpers/page_helper.js +27 -0
  71. data/lib/jschat/http/public/javascripts/app/helpers/text_helper.js +92 -0
  72. data/lib/jschat/http/public/javascripts/app/lib/split.js +78 -0
  73. data/lib/jschat/http/public/javascripts/app/models/cookie.js +27 -0
  74. data/lib/jschat/http/public/javascripts/app/protocol/change.js +15 -0
  75. data/lib/jschat/http/public/javascripts/app/protocol/chat_request.js +13 -0
  76. data/lib/jschat/http/public/javascripts/app/protocol/display.js +147 -0
  77. data/lib/jschat/http/public/javascripts/app/ui/commands.js +55 -0
  78. data/lib/jschat/http/public/javascripts/app/ui/tab_completion.js +122 -0
  79. data/lib/jschat/http/public/javascripts/init.js +19 -0
  80. data/lib/jschat/http/public/stylesheets/iphone.css +3 -0
  81. data/lib/jschat/http/public/stylesheets/screen.css +68 -0
  82. data/lib/jschat/http/script/sprockets.rb +14 -0
  83. data/lib/jschat/http/tmp/restart.txt +0 -0
  84. data/lib/jschat/http/views/index.erb +23 -0
  85. data/lib/jschat/http/views/iphone.erb +29 -0
  86. data/lib/jschat/http/views/layout.erb +29 -0
  87. data/lib/jschat/http/views/message_form.erb +15 -0
  88. data/lib/jschat/server.rb +503 -0
  89. data/test/server_test.rb +175 -0
  90. data/test/stateless_test.rb +33 -0
  91. data/test/test_helper.rb +61 -0
  92. metadata +223 -0
@@ -0,0 +1,191 @@
1
+ JsChat.ChatController = Class.create({
2
+ initialize: function() {
3
+ $('loading').show();
4
+
5
+ this.resizeEvent();
6
+ setTimeout(this.initDisplay.bind(this), 50);
7
+ this.tabCompletion = new TabCompletion('message');
8
+
9
+ Event.observe(window, 'focus', this.focusEvent.bindAsEventListener(this));
10
+ Event.observe(window, 'blur', this.blurEvent.bindAsEventListener(this));
11
+ Event.observe(window, 'resize', this.resizeEvent.bindAsEventListener(this));
12
+
13
+ $('post_message').observe('submit', this.postMessageFormEvent.bindAsEventListener(this));
14
+ $('messages').observe('scroll', this.messagesScrolled.bindAsEventListener(this));
15
+ },
16
+
17
+ messagesScrolled: function() {
18
+ Display.scrolled = (($('messages').scrollHeight - $('messages').scrollTop) > $('messages').getHeight());
19
+ },
20
+
21
+ focusEvent: function() {
22
+ Display.unread = 0;
23
+ Display.show_unread = false;
24
+ document.title = PageHelper.title();
25
+ },
26
+
27
+ blurEvent: function() {
28
+ Display.show_unread = true;
29
+ },
30
+
31
+ resizeEvent: function() {
32
+ var messageInset = PageHelper.isDevice('iphone') ? 390 : 290,
33
+ heightInset = PageHelper.isDevice('iphone') ? 200 : 100,
34
+ windowSize = document.viewport.getDimensions();
35
+ $('messages').setStyle({ width: windowSize.width - 220 + 'px' });
36
+ $('messages').setStyle({ height: windowSize.height - heightInset + 'px' });
37
+ $('message').setStyle({ width: windowSize.width - messageInset + 'px' });
38
+ $('names').setStyle({ height: windowSize.height - 200 + 'px' });
39
+ Display.scrollMessagesToTop();
40
+ },
41
+
42
+ postMessageFormEvent: function(e) {
43
+ try {
44
+ var element = Event.element(e);
45
+ var message = $('message').value;
46
+ $('message').value = '';
47
+
48
+ if (message.length > 0) {
49
+ var command_posted = $H(UserCommands).find(function(command) {
50
+ var name = command[0];
51
+ var matches = message.match(new RegExp('^' + name + '$'));
52
+ if (matches) {
53
+ command[1].bind(this)(matches);
54
+ return true;
55
+ }
56
+ }.bind(this));
57
+
58
+ if (!command_posted) {
59
+ if (message.match(/^\//)) {
60
+ Display.add_message('Error: Command not found. Use /help display commands.', 'error');
61
+ } else {
62
+ Display.message({ 'message': message.escapeHTML(), 'user': $('name').innerHTML }, true);
63
+ new Ajax.Request('/message', {
64
+ method: 'post',
65
+ parameters: { 'message': message, 'to': PageHelper.currentRoom() }
66
+ });
67
+ }
68
+ }
69
+ }
70
+ } catch (exception) {
71
+ console.log(exception);
72
+ }
73
+
74
+ Event.stop(e);
75
+ return false;
76
+ },
77
+
78
+ initDisplay: function() {
79
+ Display.unread = 0;
80
+ Display.show_unread = false;
81
+ Display.ignore_notices = false;
82
+
83
+ $('room-name').innerHTML = TextHelper.truncateRoomName(PageHelper.currentRoom());
84
+ $('room-name').title = PageHelper.currentRoom();
85
+ $('message').activate();
86
+ $$('.header .navigation li').invoke('hide');
87
+ $('quit-nav').show();
88
+ $('help-nav').show();
89
+
90
+ $('help-link').observe('click', function(e) {
91
+ UserCommands['/help']();
92
+ $('message').activate();
93
+ Event.stop(e);
94
+ return false;
95
+ });
96
+
97
+ this.createPollers();
98
+ this.joinRoom();
99
+ },
100
+
101
+ joinRoom: function() {
102
+ new Ajax.Request('/join', {
103
+ method: 'post',
104
+ parameters: { time: new Date().getTime(), room: PageHelper.currentRoom() },
105
+ onFailure: function() {
106
+ Display.add_message("Error: Couldn't join channel", 'server');
107
+ $('loading').hide();
108
+ },
109
+ onComplete: function() {
110
+ // Make the server update the last polled time
111
+ JsChat.Request.get('/messages');
112
+ document.title = PageHelper.title();
113
+ UserCommands['/lastlog'].apply(this);
114
+ $('loading').hide();
115
+ }.bind(this)
116
+ });
117
+ },
118
+
119
+ updateNames: function() {
120
+ UserCommands['/names'].apply(this);
121
+ },
122
+
123
+ showMessagesResponse: function(transport) {
124
+ try {
125
+ this.displayMessages(transport.responseText);
126
+
127
+ if ($$('#messages li').length > 1000) {
128
+ $$('#messages li').slice(0, 500).invoke('remove');
129
+ }
130
+ } catch (exception) {
131
+ console.log(transport.responseText);
132
+ console.log(exception);
133
+ }
134
+ },
135
+
136
+ updateMessages: function() {
137
+ new Ajax.Request('/messages', {
138
+ method: 'get',
139
+ parameters: { time: new Date().getTime(), room: PageHelper.currentRoom() },
140
+ onSuccess: function(transport) {
141
+ this.showMessagesResponse(transport);
142
+ }.bind(this),
143
+ onFailure: function(request) {
144
+ this.stopPolling();
145
+ Display.add_message('Server error: <a href="/#{room}">please reconnect</a>'.interpolate({ room: PageHelper.currentRoom() }), 'server');
146
+ }.bind(this)
147
+ });
148
+ },
149
+
150
+ displayMessages: function(text, successCallback) {
151
+ var json_set = text.evalJSON(true);
152
+ if (json_set.length == 0) {
153
+ return;
154
+ }
155
+ json_set.each(function(json) {
156
+ try {
157
+ if (json['change']) {
158
+ Change[json['change']](json[json['change']]);
159
+ } else {
160
+ Display[json['display']](json[json['display']]);
161
+ if (json['display'] !== 'error' && typeof successCallback !== 'undefined') {
162
+ successCallback();
163
+ }
164
+ }
165
+ } catch (exception) {
166
+ }
167
+ });
168
+ },
169
+
170
+ checkIdleNames: function() {
171
+ $$('#names li').each(function(element) {
172
+ if (Display.isIdle(element.lastIdle)) {
173
+ element.addClassName('idle');
174
+ }
175
+ });
176
+ },
177
+
178
+ stopPolling: function() {
179
+ this.pollers.invoke('stop');
180
+ },
181
+
182
+ firePollers: function() {
183
+ this.pollers.invoke('execute');
184
+ },
185
+
186
+ createPollers: function() {
187
+ this.pollers = $A();
188
+ this.pollers.push(new PeriodicalExecuter(this.updateMessages.bind(this), 3));
189
+ this.pollers.push(new PeriodicalExecuter(this.checkIdleNames.bind(this), 5));
190
+ }
191
+ });
@@ -0,0 +1,56 @@
1
+ JsChat.SignOnController = Class.create({
2
+ initialize: function() {
3
+ this.retries = 0;
4
+ setTimeout(function() { $('name').activate() }, 500);
5
+ $('sign-on').observe('submit', this.submitEvent.bindAsEventListener(this));
6
+ },
7
+
8
+ submitEvent: function(e) {
9
+ this.signOn();
10
+ Event.stop(e);
11
+ return false;
12
+ },
13
+
14
+ showError: function(message) {
15
+ $('feedback').innerHTML = '<div class="error">#{message}</div>'.interpolate({ message: message });
16
+ $('feedback').show();
17
+ $('sign-on-submit').enable();
18
+ },
19
+
20
+ signOn: function() {
21
+ $('loading').show();
22
+ $('sign-on-submit').disable();
23
+ this.retries += 1;
24
+
25
+ new Ajax.Request('/identify', {
26
+ parameters: $('sign-on').serialize(true),
27
+ onSuccess: function(transport) {
28
+ try {
29
+ var json = transport.responseText.evalJSON(true);
30
+ if (json['action'] == 'reload' && this.retries < 4) {
31
+ setTimeout(function() { this.signOn() }.bind(this), 50);
32
+ } else if (json['action'] == 'redirect') {
33
+ if (window.location.toString().match(new RegExp(json['to'] + '$'))) {
34
+ window.location.reload();
35
+ } else {
36
+ window.location = json['to'];
37
+ }
38
+ } else if (json['error']) {
39
+ this.showError(json['error']['message']);
40
+ $('loading').hide();
41
+ } else {
42
+ this.showError('Connection error');
43
+ }
44
+ } catch (exception) {
45
+ this.showError('Connection error: #{error}'.interpolate({ error: exception }));
46
+ }
47
+ }.bind(this),
48
+ onFailure: function() {
49
+ this.showError('Connection error');
50
+ }.bind(this),
51
+ onComplete: function() {
52
+ $('loading').hide();
53
+ }
54
+ });
55
+ }
56
+ });
@@ -0,0 +1,23 @@
1
+ var EmoteHelper = {
2
+ 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', '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'],
3
+
4
+ emoteToImage: function(emote) {
5
+ var result = emote;
6
+ emote = emote.replace(/^:/, '').toLowerCase();
7
+ if (EmoteHelper.legalEmotes.find(function(v) { return v == emote })) {
8
+ result = '<img src="/images/emoticons/#{emote}.gif" alt="#{description}" />'.interpolate({ emote: emote, description: emote });
9
+ }
10
+ return result;
11
+ },
12
+
13
+ insertEmotes: function(text) {
14
+ var result = '';
15
+ $A(text.split(/(:[^ ]*)/)).each(function(segment) {
16
+ if (segment && segment.match(/^:/)) {
17
+ segment = EmoteHelper.emoteToImage(segment);
18
+ }
19
+ result += segment;
20
+ });
21
+ return result;
22
+ }
23
+ };
@@ -0,0 +1,37 @@
1
+ var FormHelpers = {
2
+ getCaretPosition: function(element) {
3
+ if (element.setSelectionRange) {
4
+ return element.selectionStart;
5
+ } else if (element.createTextRange) {
6
+ try {
7
+ // The current selection
8
+ var range = document.selection.createRange();
9
+ // We'll use this as a 'dummy'
10
+ var stored_range = range.duplicate();
11
+ // Select all text
12
+ stored_range.moveToElementText(element);
13
+ // Now move 'dummy' end point to end point of original range
14
+ stored_range.setEndPoint('EndToEnd', range);
15
+
16
+ return stored_range.text.length - range.text.length;
17
+ } catch (exception) {
18
+ // IE is being mental. TODO: Figure out what IE's issue is
19
+ return 0;
20
+ }
21
+ }
22
+ },
23
+
24
+ setCaretPosition: function(element, pos) {
25
+ if (element.setSelectionRange) {
26
+ element.focus()
27
+ element.setSelectionRange(pos, pos)
28
+ } else if (element.createTextRange) {
29
+ var range = element.createTextRange()
30
+
31
+ range.collapse(true)
32
+ range.moveEnd('character', pos)
33
+ range.moveStart('character', pos)
34
+ range.select()
35
+ }
36
+ }
37
+ };
@@ -0,0 +1,47 @@
1
+ var LinkHelper = {
2
+ url: function(url) {
3
+ return url.match(/(https?:\/\/[^\s]*)/gi);
4
+ },
5
+
6
+ link: function(url) {
7
+ return '<a href="\#{url}" target="_blank">\#{link_name}</a>'.interpolate({ url: url, link_name: url});
8
+ },
9
+
10
+ image_url: function(url) {
11
+ return url.match(/\.(jpe?g|png|gif)/i);
12
+ },
13
+
14
+ image: function(url) {
15
+ return '<a href="\#{url}" target="_blank"><img class="inline-image" src="\#{image}" /></a>'.interpolate({ url: url, image: url })
16
+ },
17
+
18
+ youtube_url: function(url) {
19
+ return url.match(/youtube\.com/) && url.match(/watch\?v/);
20
+ },
21
+
22
+ youtube: function(url) {
23
+ var youtube_url_id = url.match(/\?v=([^&\s]*)/);
24
+ if (youtube_url_id && youtube_url_id[1]) {
25
+ var youtube_url = 'http://www.youtube.com/v/' + youtube_url_id[1];
26
+ 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>';
27
+ return youtube_html.interpolate({ movie_url: youtube_url, url: youtube_url });
28
+ } else {
29
+ return this.link(url);
30
+ }
31
+ },
32
+
33
+ vimeo_url: function(url) {
34
+ return url.match(/vimeo\.com/) && url.match(/\/\d+/);
35
+ },
36
+
37
+ vimeo: function(url) {
38
+ var vimeo_url_id = url.match(/\d+/);
39
+ if (vimeo_url_id) {
40
+ var vimeo_url = 'http://vimeo.com/' + vimeo_url_id;
41
+ 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>';
42
+ return vimeo_html.interpolate({ movie_url: vimeo_url, url: vimeo_url });
43
+ } else {
44
+ return this.link(url);
45
+ }
46
+ }
47
+ };
@@ -0,0 +1,27 @@
1
+ var PageHelper = {
2
+ currentRoom: function() {
3
+ return window.location.hash;
4
+ },
5
+
6
+ nickname: function() {
7
+ return Cookie.find('jschat-name');
8
+ },
9
+
10
+ title: function() {
11
+ if (PageHelper.currentRoom()) {
12
+ return 'JsChat: ' + PageHelper.currentRoom();
13
+ } else {
14
+ return 'JsChat';
15
+ }
16
+ },
17
+
18
+ device: function() {
19
+ if ($$('body.iphone').length > 0) {
20
+ return 'iphone';
21
+ }
22
+ },
23
+
24
+ isDevice: function(device) {
25
+ return PageHelper.device() == device;
26
+ }
27
+ };
@@ -0,0 +1,92 @@
1
+ var TextHelper = {
2
+ zeroPad: function(value, length) {
3
+ value = value.toString();
4
+ if (value.length >= length) {
5
+ return value;
6
+ } else {
7
+ return this.zeroPad('0' + value, length);
8
+ }
9
+ },
10
+
11
+ dateText: function(time) {
12
+ var d = new Date();
13
+ if (typeof time != 'undefined') {
14
+ d = new Date(Date.parse(time));
15
+ }
16
+ return this.zeroPad(d.getHours(), 2) + ':' + this.zeroPad(d.getMinutes(), 2);
17
+ },
18
+
19
+ truncateName: function(text) {
20
+ return text.truncate(15);
21
+ },
22
+
23
+ truncateRoomName: function(text) {
24
+ return text.truncate(15);
25
+ },
26
+
27
+ decorateMessage: function(text) {
28
+ return EmoteHelper.insertEmotes(this.autoLink(this.textilize(text)));
29
+ },
30
+
31
+ textilize: function(text) {
32
+ function escape_regex(text) { return text.replace(/([\*\?\+\^\?])/g, "\\$1"); }
33
+ function openTag(text) { return '<' + text + '>'; }
34
+ function closeTag(text) { return '</' + text + '>'; }
35
+
36
+ var map = { '_': 'em', '*': 'strong' };
37
+
38
+ $H(map).each(function(mapping) {
39
+ var result = '';
40
+ var m = escape_regex(mapping[0]);
41
+ var mr = new RegExp('(' + m + ')');
42
+ var matcher = new RegExp('(^|\\s+)(' + m + ')([^\\s][^' + mapping[0] + ']*[^\\s])(' + m + ')', 'g');
43
+
44
+ if (text.match(matcher)) {
45
+ var open = false;
46
+ text.split(matcher).each(function(segment) {
47
+ if (segment == mapping[0]) {
48
+ var tag = open ? closeTag(mapping[1]) : openTag(mapping[1]);
49
+ result += segment.replace(mr, tag);
50
+ open = !open;
51
+ } else {
52
+ result += segment;
53
+ }
54
+ });
55
+
56
+ if (open) result += closeTag(mapping[1]);
57
+ text = result;
58
+ }
59
+ });
60
+
61
+ return text;
62
+ },
63
+
64
+ autoLink: function(text) {
65
+ var result = '';
66
+ try {
67
+ if (!LinkHelper.url(text)) {
68
+ return text;
69
+ }
70
+
71
+ $A(text.split(/(https?:\/\/[^\s]*)/gi)).each(function(link) {
72
+ if (link.match(/href="/)) {
73
+ result += link;
74
+ } else {
75
+ if (LinkHelper.youtube_url(link)) {
76
+ result += link.replace(link, LinkHelper.youtube(link));
77
+ } else if (LinkHelper.vimeo_url(link)) {
78
+ result += link.replace(link, LinkHelper.vimeo(link));
79
+ } else if (LinkHelper.image_url(link)) {
80
+ result += link.replace(link, LinkHelper.image(link));
81
+ } else if (LinkHelper.url(link)) {
82
+ result += link.replace(link, LinkHelper.link(link));
83
+ } else {
84
+ result += link;
85
+ }
86
+ }
87
+ });
88
+ } catch (exception) {
89
+ }
90
+ return result;
91
+ }
92
+ };
@@ -0,0 +1,78 @@
1
+ /*
2
+ Cross-Browser Split 0.3
3
+ By Steven Levithan <http://stevenlevithan.com>
4
+ MIT license
5
+ Provides a consistent cross-browser, ECMA-262 v3 compliant split method
6
+ */
7
+
8
+ String.prototype._$$split = String.prototype._$$split || String.prototype.split;
9
+
10
+ String.prototype.split = function (s /* separator */, limit) {
11
+ // if separator is not a regex, use the native split method
12
+ if (!(s instanceof RegExp))
13
+ return String.prototype._$$split.apply(this, arguments);
14
+
15
+ var flags = (s.global ? "g" : "") + (s.ignoreCase ? "i" : "") + (s.multiline ? "m" : ""),
16
+ s2 = new RegExp("^" + s.source + "$", flags),
17
+ output = [],
18
+ origLastIndex = s.lastIndex,
19
+ lastLastIndex = 0,
20
+ i = 0, match, lastLength;
21
+
22
+ /* behavior for limit: if it's...
23
+ - undefined: no limit
24
+ - NaN or zero: return an empty array
25
+ - a positive number: use limit after dropping any decimal
26
+ - a negative number: no limit
27
+ - other: type-convert, then use the above rules
28
+ */
29
+ if (limit === undefined || +limit < 0) {
30
+ limit = false;
31
+ } else {
32
+ limit = Math.floor(+limit);
33
+ if (!limit)
34
+ return [];
35
+ }
36
+
37
+ if (s.global)
38
+ s.lastIndex = 0;
39
+ else
40
+ s = new RegExp(s.source, "g" + flags);
41
+
42
+ while ((!limit || i++ <= limit) && (match = s.exec(this))) {
43
+ var emptyMatch = !match[0].length;
44
+
45
+ // Fix IE's infinite-loop-resistant but incorrect lastIndex
46
+ if (emptyMatch && s.lastIndex > match.index)
47
+ s.lastIndex--;
48
+
49
+ if (s.lastIndex > lastLastIndex) {
50
+ // Fix browsers whose exec methods don't consistently return undefined for non-participating capturing groups
51
+ if (match.length > 1) {
52
+ match[0].replace(s2, function () {
53
+ for (var j = 1; j < arguments.length - 2; j++) {
54
+ if (arguments[j] === undefined)
55
+ match[j] = undefined;
56
+ }
57
+ });
58
+ }
59
+
60
+ output = output.concat(this.slice(lastLastIndex, match.index));
61
+ if (1 < match.length && match.index < this.length)
62
+ output = output.concat(match.slice(1));
63
+ lastLength = match[0].length; // only needed if s.lastIndex === this.length
64
+ lastLastIndex = s.lastIndex;
65
+ }
66
+
67
+ if (emptyMatch)
68
+ s.lastIndex++; // avoid an infinite loop
69
+ }
70
+
71
+ // since this uses test(), output must be generated before restoring lastIndex
72
+ output = lastLastIndex === this.length ?
73
+ (s.test("") && !lastLength ? output : output.concat("")) :
74
+ (limit ? output : output.concat(this.slice(lastLastIndex)));
75
+ s.lastIndex = origLastIndex; // only needed if s.global, else we're working with a copy of the regex
76
+ return output;
77
+ };
78
+
@@ -0,0 +1,27 @@
1
+ Cookie = {
2
+ create: function(name, value, days, path) {
3
+ var expires = '';
4
+ path = typeof path == 'undefined' ? '/' : path;
5
+
6
+ if (days) {
7
+ var date = new Date();
8
+ date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
9
+ expires = "; expires=" + date.toGMTString();
10
+ }
11
+
12
+ if (name && value) {
13
+ document.cookie = name + '=' + escape(value) + expires + ';path=' + path;
14
+ }
15
+ },
16
+
17
+ find: function(name) {
18
+ var matches = document.cookie.match(name + '=([^;]*)');
19
+ if (matches && matches.length == 2) {
20
+ return unescape(matches[1]);
21
+ }
22
+ },
23
+
24
+ destroy: function(name) {
25
+ this.create(name, ' ', -1);
26
+ }
27
+ };
@@ -0,0 +1,15 @@
1
+ var Change = {
2
+ user: function(user) {
3
+ if (user['name']) {
4
+ change = $H(user['name']).toArray()[0];
5
+ var old = change[0],
6
+ new_value = change[1];
7
+ if (new_value !== PageHelper.nickname()) {
8
+ Display.add_message("#{old} is now known as #{new_value}".interpolate({ old: old, new_value: new_value }), 'server', user['time']);
9
+ }
10
+ $$('#names li').each(function(element) {
11
+ if (element.innerHTML == old) element.innerHTML = new_value;
12
+ });
13
+ }
14
+ }
15
+ };
@@ -0,0 +1,13 @@
1
+ /* FIXME: Later on this should be a class */
2
+ JsChat.Request = {
3
+ get: function(url, callback) {
4
+ new Ajax.Request(url, {
5
+ method: 'get',
6
+ parameters: { time: new Date().getTime(), room: PageHelper.currentRoom() },
7
+ onFailure: function() {
8
+ Display.add_message("Server error: couldn't access: #{url}".interpolate({ url: url }), 'server');
9
+ },
10
+ onComplete: function(transport) { return callback(transport); }
11
+ });
12
+ }
13
+ };