jschat 0.1.1

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.
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
+ };