jschat 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +23 -0
- data/README.textile +71 -0
- data/bin/jschat-client +3 -0
- data/bin/jschat-server +18 -0
- data/bin/jschat-web +7 -0
- data/lib/jschat/client.rb +745 -0
- data/lib/jschat/errors.rb +41 -0
- data/lib/jschat/flood_protection.rb +39 -0
- data/lib/jschat/http/config/sprockets.yml +7 -0
- data/lib/jschat/http/config.ru +12 -0
- data/lib/jschat/http/jschat.rb +264 -0
- data/lib/jschat/http/public/favicon.ico +0 -0
- data/lib/jschat/http/public/images/emoticons/angry.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/arr.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/blink.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/blush.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/brucelee.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/btw.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/chuckle.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/clap.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/cool.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/drool.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/drunk.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/dry.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/eek.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/flex.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/happy.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/holmes.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/huh.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/laugh.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/lol.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/mad.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/mellow.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/noclue.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/oh.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/ohmy.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/ph34r.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/pimp.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/punch.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/realmad.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/rock.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/rofl.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/rolleyes.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/sad.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/scratch.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/shifty.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/shock.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/shrug.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/sleep.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/sleeping.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/smile.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/suicide.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/sweat.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/thumbs.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/tongue.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/unsure.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/w00t.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/wacko.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/whistling.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/wink.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/worship.gif +0 -0
- data/lib/jschat/http/public/images/emoticons/yucky.gif +0 -0
- data/lib/jschat/http/public/images/jschat.gif +0 -0
- data/lib/jschat/http/public/images/shadow.png +0 -0
- data/lib/jschat/http/public/javascripts/app/controllers/chat_controller.js +191 -0
- data/lib/jschat/http/public/javascripts/app/controllers/signon_controller.js +56 -0
- data/lib/jschat/http/public/javascripts/app/helpers/emote_helper.js +23 -0
- data/lib/jschat/http/public/javascripts/app/helpers/form_helpers.js +37 -0
- data/lib/jschat/http/public/javascripts/app/helpers/link_helper.js +47 -0
- data/lib/jschat/http/public/javascripts/app/helpers/page_helper.js +27 -0
- data/lib/jschat/http/public/javascripts/app/helpers/text_helper.js +92 -0
- data/lib/jschat/http/public/javascripts/app/lib/split.js +78 -0
- data/lib/jschat/http/public/javascripts/app/models/cookie.js +27 -0
- data/lib/jschat/http/public/javascripts/app/protocol/change.js +15 -0
- data/lib/jschat/http/public/javascripts/app/protocol/chat_request.js +13 -0
- data/lib/jschat/http/public/javascripts/app/protocol/display.js +147 -0
- data/lib/jschat/http/public/javascripts/app/ui/commands.js +55 -0
- data/lib/jschat/http/public/javascripts/app/ui/tab_completion.js +122 -0
- data/lib/jschat/http/public/javascripts/init.js +19 -0
- data/lib/jschat/http/public/stylesheets/iphone.css +3 -0
- data/lib/jschat/http/public/stylesheets/screen.css +68 -0
- data/lib/jschat/http/script/sprockets.rb +14 -0
- data/lib/jschat/http/tmp/restart.txt +0 -0
- data/lib/jschat/http/views/index.erb +23 -0
- data/lib/jschat/http/views/iphone.erb +29 -0
- data/lib/jschat/http/views/layout.erb +29 -0
- data/lib/jschat/http/views/message_form.erb +15 -0
- data/lib/jschat/server.rb +503 -0
- data/test/server_test.rb +175 -0
- data/test/stateless_test.rb +33 -0
- data/test/test_helper.rb +61 -0
- 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 + '&server=vimeo.com&show_title=1&show_byline=0&show_portrait=0&color=969696&fullscreen=1" /><embed src="http://vimeo.com/moogaloop.swf?clip_id=' + vimeo_url_id + '&server=vimeo.com&show_title=1&show_byline=0&show_portrait=0&color=969696&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
|
+
};
|