├── README
├── index.html
├── style.css
├── redis.js
├── client.js
├── server.js
├── fu.js
└── jquery-1.3.2.min.js
/README:
--------------------------------------------------------------------------------
1 |
2 | Demo for node.js http://tinyclouds.org/node
3 |
4 | with a simple redis integration using http://github.com/sma/redis-node-client.
5 | this client really works!
6 |
7 | to run
8 | $ /usr/local/bin/node server.js
9 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
26 |
27 |
28 |
29 |
30 |
31 | - keule
32 | - eule
33 | - ksdeule
34 |
35 |
36 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 0;
3 | margin: 0;
4 | }
5 | body, #entry {
6 | background: #22252a;
7 | color: #eee;
8 | }
9 | body, table {
10 | font-family: DejaVu Sans Mono, fixed;
11 | font-size: 14pt;
12 | line-height: 150%;
13 | }
14 | tr,td,table {
15 | padding: 0;
16 | margin: 0;
17 | outline-width: 0;
18 | border-width: 0;
19 | }
20 | table {
21 | border-collapse: collapse;
22 | border-spacing: 0
23 | }
24 |
25 |
26 | a { text-decoration: none; color: #888; }
27 | a:hover { text-decoration: underline; color: #aaa; }
28 |
29 | #loading, #connect {
30 | background: #22252a;
31 | position: fixed;
32 | top: 0;
33 | left: 0;
34 | width: 100%;
35 | height: 100%;
36 | padding: 1em;
37 | }
38 |
39 | #loading { z-index: 10000000; }
40 | #connect { z-index: 10000001; }
41 |
42 | #loading p, #connect fieldset {
43 | font-family: DejaVu Sans Mono, fixed;
44 | width: 30em;
45 | border: 0;
46 | }
47 |
48 | #toolbar {
49 | position: fixed;
50 | color: #fff;
51 | width: 100%;
52 | bottom: 0;
53 | background: #733;
54 | }
55 |
56 | #entry {
57 | width: 100%;
58 | font-size: inherit;
59 | padding: 1em;
60 | margin: 0;
61 | border-width: 0;
62 | outline-width: 0;
63 | clear: both;
64 | }
65 |
66 | #log {
67 | display: block;
68 | /* enough padding to clear the toolbar.. is there a better way to do this? */
69 | padding-bottom: 5.1em;
70 | background: inherit;
71 | overflow: hidden;
72 | float: left;
73 | width: 75%;
74 | }
75 |
76 | #users {
77 | background: #733;
78 | margin-top: 1em;
79 | padding: 0em;
80 | width: 15%;
81 | margin-left: 80%;
82 | position: fixed;
83 | }
84 |
85 |
86 | /* not very important info */
87 | .notice td, .join td, .part td, .message .date { color: #555; }
88 | .error td { color: #933; }
89 | .personal .nick, .notice .nick { color: #cd5; }
90 |
91 | .message {
92 | margin: 0.1em 0;
93 | }
94 |
95 | .message td {
96 | vertical-align: top;
97 | }
98 |
99 | .nick {
100 | font-weight: bold;
101 | padding: 0 1em 0 0.5em;
102 | }
103 |
104 | .nick a {
105 | color: inherit;
106 | }
107 |
108 | #toolbar ul {
109 | margin: 0;
110 | padding: 0;
111 | list-style: none;
112 | }
113 |
114 | #toolbar li {
115 | display: block;
116 | float: left;
117 | margin: 0 2em 0 0;
118 | }
119 |
120 | #usersLink {
121 | color: inherit;
122 | }
123 |
--------------------------------------------------------------------------------
/redis.js:
--------------------------------------------------------------------------------
1 | var sys = require("sys"),
2 | tcp = require("tcp");
3 |
4 | var CRLF = "\r\n";
5 |
6 | function dbg(s) {
7 | if (GLOBAL.DEBUG) {
8 | sys.debug(s);
9 | }
10 | }
11 |
12 | /**
13 | * Constructs a new Redis client, calling "callback" if successfully connected.
14 | */
15 | function Redis(callback, port, host) {
16 | this.callbacks = [];
17 |
18 | this.conn = tcp.createConnection(port || 6379, host || '127.0.0.1');
19 | this.conn.setEncoding("binary");
20 | this.conn.setTimeout(0);
21 | if (callback) {
22 | var redis = this;
23 | this.conn.addListener("connect", function() {
24 | callback(redis);
25 | });
26 | }
27 |
28 | var redis = this, buffer = "", count = 0, n = 0, result = null;
29 |
30 | this.conn.addListener("receive", function(data) {
31 | function reply(obj) {
32 | if (n > 0) {
33 | dbg("storing " + obj);
34 | result.push(obj);
35 | obj = result;
36 | n -= 1;
37 | }
38 | if (n == 0) {
39 | dbg("returning " + obj);
40 | redis.callbacks.shift().emitSuccess(obj);
41 | }
42 | }
43 |
44 | dbg("receive: " + data);
45 | buffer += data;
46 | while (true) {
47 | if (count > 0) {
48 | dbg("waiting for " + count + " bytes...")
49 | if (buffer.length < count) {
50 | dbg("not enough, only " + buffer.length);
51 | return;
52 | }
53 | var chunk = buffer.substring(0, count - 2);
54 | buffer = buffer.substring(count);
55 | count = 0;
56 | dbg("got chunk " + chunk);
57 | reply(chunk);
58 | } else {
59 | dbg("waiting for line")
60 | var end = buffer.indexOf(CRLF);
61 | if (end == -1) {
62 | dbg("no CRLF");
63 | return;
64 | }
65 | var command = buffer[0];
66 | var line = buffer.substring(1, end);
67 | buffer = buffer.substring(end + 2);
68 | switch (command) {
69 | case '+':
70 | dbg("got line: " + line);
71 | reply(line);
72 | break;
73 | case ':':
74 | dbg("got line: " + line);
75 | reply(parseInt(line, 10));
76 | break;
77 | case '$':
78 | var c = parseInt(line, 10);
79 | dbg("bulk reply detected " + c)
80 | if (c == -1) {
81 | dbg("got null");
82 | reply(null);
83 | } else {
84 | count = c + 2;
85 | continue;
86 | }
87 | break;
88 | case '*':
89 | n = parseInt(line, 10);
90 | if (n == -1) {
91 | n = 0;
92 | dbg("got null");
93 | reply(null);
94 | } else {
95 | dbg("multi bulk reply " + n);
96 | result = [];
97 | continue;
98 | }
99 | break;
100 | case '-':
101 | dbg("error " + line);
102 | redis.callbacks.shift().emitError(line);
103 | break;
104 | default:
105 | dbg("unexpected " + command + line);
106 | }
107 | }
108 | }
109 | });
110 | }
111 |
112 | Redis.prototype._send = function(command) {
113 | var promise = new process.Promise;
114 | this.callbacks.push(promise);
115 | this.conn.send(command + CRLF, "binary");
116 | return promise;
117 | };
118 |
119 | Redis.prototype.close = function() {
120 | this._send("QUIT").addCallback(function() {
121 | this.conn.close();
122 | });
123 | };
124 |
125 | Redis.prototype.ping = function(callback) {
126 | return this._send("PING");
127 | };
128 |
129 | // commands operating on string values
130 |
131 | Redis.prototype.set = function(key, value) {
132 | return this._send("SET " + key + " " + value.length + CRLF + value);
133 | };
134 |
135 | Redis.prototype.get = function(key) {
136 | return this._send("GET " + key);
137 | };
138 |
139 | Redis.prototype.getset = function(key, value) {
140 | return this._send("GETSET " + key + " " + value.length + CRLF + value);
141 | };
142 |
143 | Redis.prototype.mget = function(/*keys*/) {
144 | var keys = [];
145 | for (var i = 0; i < arguments.length; i++) {
146 | keys.push(arguments[i]);
147 | }
148 | return this._send("MGET " + keys.join(" "));
149 | };
150 |
151 | Redis.prototype.setnx = function(key, value) {
152 | return this._send("SETNX " + key + " " + value.length + CRLF + value);
153 | };
154 |
155 | Redis.prototype.incr = function(key, by) {
156 | if (by) {
157 | return this._send("INCRBY " + key + " " + by);
158 | }
159 | return this._send("INCR " + key);
160 | };
161 |
162 | Redis.prototype.decr = function(key, by) {
163 | if (by) {
164 | return this._send("DECRBY " + key + " " + by);
165 | }
166 | return this._send("DECR " + key);
167 | };
168 |
169 | Redis.prototype.del = function(key) {
170 | return this._send("DEL " + key);
171 | };
172 |
173 | Redis.prototype.type = function(key) {
174 | return this._send("TYPE " + key);
175 | };
176 |
177 | // commands operating on the key space
178 |
179 | Redis.prototype.randomkey = function() {
180 | return this._send("RANDOMKEY");
181 | };
182 |
183 | Redis.prototype.dbsize = function() {
184 | return this._send("DBSIZE");
185 | };
186 |
187 | // commands operating on lists
188 |
189 | Redis.prototype.rpush = function(key, value) {
190 | return this._send("RPUSH " + key + " " + value.length + CRLF + value);
191 | };
192 |
193 | Redis.prototype.lpush = function(key, value) {
194 | return this._send("LPUSH " + key + " " + value.length + CRLF + value);
195 | };
196 |
197 | Redis.prototype.llen = function(key) {
198 | return this._send("LLEN " + key);
199 | };
200 |
201 | Redis.prototype.lrange = function(key, from, to) {
202 | return this._send("LRANGE " + key + " " + from + " " + to);
203 | };
204 |
205 | // commands operating on sets
206 | // commands operating on sorted sets
207 |
208 | // multiple databases handling commmands
209 |
210 | Redis.prototype.select = function(index) {
211 | return this._send("SELECT " + index);
212 | };
213 |
214 | Redis.prototype.move = function(key, index) {
215 | return this._send("MOVE " + key + " " + index);
216 | };
217 |
218 | Redis.prototype.flushdb = function() {
219 | return this._send("FLUSHDB");
220 | };
221 |
222 | Redis.prototype.flushall = function() {
223 | return this._send("FLUSHALL");
224 | };
225 |
226 | exports.Redis = Redis;
227 |
--------------------------------------------------------------------------------
/client.js:
--------------------------------------------------------------------------------
1 |
2 | var CONFIG = { debug: false
3 | , nick: "#" // set in onConnect
4 | , id: null // set in onConnect
5 | , last_message_index: 0
6 | };
7 |
8 | var nicks = [];
9 |
10 | function updateUsersLink ( ) {
11 | var t = nicks.length.toString() + " user";
12 | if (nicks.length != 1) t += "s";
13 | $("#usersLink").text(t);
14 | }
15 |
16 | function userJoin(nick, timestamp, text) {
17 | addMessage(nick, text || "joined", timestamp, "join");
18 | for (var i = 0; i < nicks.length; i++)
19 | if (nicks[i] == nick) return;
20 | nicks.push(nick);
21 | updateUsersDisplay();
22 | }
23 |
24 | function updateUsersDisplay() {
25 | var newList = $("
");
26 | for (var i = 0; i < nicks.length; i++) {
27 | nick = nicks[i];
28 | newList.append("
" + nick + "");
29 | }
30 | $("#users ul").replaceWith(newList);
31 | updateUsersLink();
32 | }
33 |
34 | function userPart(nick, timestamp, text) {
35 | addMessage(nick, text || "left", timestamp, "part");
36 | for (var i = 0; i < nicks.length; i++) {
37 | if (nicks[i] == nick) {
38 | nicks.splice(i,1)
39 | break;
40 | }
41 | }
42 | updateUsersDisplay();
43 | }
44 |
45 | // utility functions
46 |
47 | util = {
48 | urlRE: /https?:\/\/([-\w\.]+)+(:\d+)?(\/([^\s]*(\?\S+)?)?)?/g,
49 |
50 | // html sanitizer
51 | toStaticHTML: function(inputHtml) {
52 | return inputHtml.replace(/&/g, "&")
53 | .replace(//g, ">");
55 | },
56 |
57 | zeroPad: function (digits, n) {
58 | n = n.toString();
59 | while (n.length < digits)
60 | n = '0' + n;
61 | return n;
62 | },
63 |
64 | timeString: function (date) {
65 | var minutes = date.getMinutes().toString();
66 | var hours = date.getHours().toString();
67 | return this.zeroPad(2, hours) + ":" + this.zeroPad(2, minutes);
68 | },
69 |
70 | isBlank: function(text) {
71 | var blank = /^\s*$/;
72 | return (text.match(blank) !== null);
73 | }
74 | };
75 |
76 | function scrollDown () {
77 | window.scrollBy(0, 100000000000000000);
78 | $("#entry").focus();
79 | }
80 |
81 | function addMessage (from, text, time, _class) {
82 | if (text === null)
83 | return;
84 |
85 | if (time == null) {
86 | // if the time is null or undefined, use the current time.
87 | time = new Date();
88 | } else if ((time instanceof Date) === false) {
89 | // if it's a timestamp, interpret it
90 | time = new Date(time);
91 | }
92 |
93 | var messageElement = $(document.createElement("table"));
94 |
95 | messageElement.addClass("message");
96 | if (_class)
97 | messageElement.addClass(_class);
98 |
99 | // sanitize
100 | text = util.toStaticHTML(text);
101 |
102 | // See if it matches our nick?
103 | var nick_re = new RegExp(CONFIG.nick);
104 | if (nick_re.exec(text))
105 | messageElement.addClass("personal");
106 |
107 | // replace URLs with links
108 | text = text.replace(util.urlRE, '
$&');
109 |
110 | var content = '
'
111 | + ' | ' + util.timeString(time) + ' | '
112 | + ' ' + util.toStaticHTML(from) + ' | '
113 | + ' ' + text + ' | '
114 | + '
'
115 | ;
116 | messageElement.html(content);
117 |
118 | $("#log").append(messageElement);
119 | scrollDown();
120 | }
121 |
122 | var transmission_errors = 0;
123 | var first_poll = true;
124 |
125 | function longPoll (data) {
126 | if (transmission_errors > 2) {
127 | showConnect();
128 | return;
129 | }
130 |
131 | if (data && data.messages) {
132 | for (var i = 0; i < data.messages.length; i++) {
133 | var message = data.messages[i];
134 |
135 | if (message.index > CONFIG.last_message_index)
136 | CONFIG.last_message_index = message.index;
137 |
138 | switch (message.type) {
139 | case "msg":
140 | addMessage(message.nick, message.text, message.timestamp);
141 | break;
142 |
143 | case "join":
144 | userJoin(message.nick, message.timestamp, message.text);
145 | break;
146 |
147 | case "part":
148 | userPart(message.nick, message.timestamp, message.text);
149 | break;
150 | }
151 | }
152 | if (first_poll) {
153 | first_poll = false;
154 | who();
155 | }
156 | }
157 |
158 | $.ajax({ cache: false
159 | , type: "GET"
160 | , url: "/recv"
161 | , dataType: "json"
162 | , data: { since: CONFIG.last_message_index, id: CONFIG.id }
163 | , error: function () {
164 | addMessage("", "long poll error. trying again...", new Date(), "error");
165 | transmission_errors += 1;
166 | setTimeout(longPoll, 10*1000);
167 | }
168 | , success: function (data) {
169 | transmission_errors = 0;
170 | longPoll(data);
171 | }
172 | });
173 | }
174 |
175 | function send(msg) {
176 | if (CONFIG.debug === false) {
177 | // XXX should be POST
178 | jQuery.get("/send", {id: CONFIG.id, text: msg}, function (data) { }, "json");
179 | }
180 | }
181 |
182 | function showConnect () {
183 | $("#connect").show();
184 | $("#loading").hide();
185 | $("#toolbar").hide();
186 | $("#nickInput").focus();
187 | }
188 |
189 | function showLoad () {
190 | $("#connect").hide();
191 | $("#loading").show();
192 | $("#toolbar").hide();
193 | }
194 |
195 | function showChat (nick) {
196 | $("#toolbar").show();
197 | $("#entry").focus();
198 |
199 | $("#connect").hide();
200 | $("#loading").hide();
201 |
202 | scrollDown();
203 | }
204 |
205 | function onConnect (session) {
206 | if (session.error) {
207 | alert("error connecting: " + session.error);
208 | showConnect();
209 | return;
210 | }
211 |
212 | CONFIG.nick = session.nick;
213 | CONFIG.id = session.id;
214 |
215 | transmission_errors = 0;
216 | first_poll = true;
217 | longPoll();
218 |
219 | showChat(CONFIG.nick);
220 | }
221 |
222 | function outputUsers () {
223 | var nick_string = nicks.length > 0 ? nicks.join(", ") : "(none)";
224 | addMessage("users:", nick_string, new Date(), "notice");
225 | return false;
226 | }
227 |
228 | function who () {
229 | jQuery.get("/who", {}, function (data, status) {
230 | if (status != "success") return;
231 | nicks = data.nicks;
232 | outputUsers();
233 | }, "json");
234 | }
235 |
236 | $(document).ready(function() {
237 |
238 | $("#entry").keypress(function (e) {
239 | if (e.keyCode != 13 /* Return */) return;
240 | var msg = $("#entry").attr("value").replace("\n", "");
241 | if (!util.isBlank(msg)) send(msg);
242 | $("#entry").attr("value", ""); // clear the entry field.
243 | });
244 |
245 | $("#usersLink").click(outputUsers);
246 |
247 | $("#connectButton").click(function () {
248 | showLoad();
249 | var nick = $("#nickInput").attr("value");
250 |
251 | if (nick.length > 50) {
252 | alert("Nick too long. 50 character max.");
253 | showConnect();
254 | return false;
255 | }
256 |
257 | if (/[^\w_\-^!]/.exec(nick)) {
258 | alert("Bad character in nick. Can only have letters, numbers, and '_', '-', '^', '!'");
259 | showConnect();
260 | return false;
261 | }
262 |
263 | $.ajax({ cache: false
264 | , type: "GET" // XXX should be POST
265 | , dataType: "json"
266 | , url: "/join"
267 | , data: { nick: nick }
268 | , error: function () {
269 | alert("error connecting to server");
270 | showConnect();
271 | }
272 | , success: onConnect
273 | });
274 | return false;
275 | });
276 |
277 | // update the clock every second
278 | setInterval(function () {
279 | var now = new Date();
280 | $("#currentTime").text(util.timeString(now));
281 | }, 1000);
282 |
283 | if (CONFIG.debug) {
284 | $("#loading").hide();
285 | $("#connect").hide();
286 | scrollDown();
287 | return;
288 | }
289 |
290 | // remove fixtures
291 | $("#log table").remove();
292 |
293 | showConnect();
294 | });
295 |
296 | $(window).unload(function () {
297 | jQuery.get("/part", {id: CONFIG.id}, function (data) { }, "json");
298 | });
299 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | HOST = null; // localhost
2 | PORT = 8001;
3 | GLOBAL.DEBUG = true;
4 |
5 | var fu = require("./fu");
6 | var sys = require("sys");
7 | var http = require("http");
8 | var redis = require("./redis");
9 |
10 | var MESSAGE_BACKLOG = 200;
11 | var CALLBACK_TIMEOUT = 30 * 1000;
12 | var SESSION_TIMEOUT = 2 * CALLBACK_TIMEOUT;
13 | var CHAT_DB_NUMBER = 7;
14 | var DEFAULT_CHANNEL = "default";
15 |
16 | var rclient = new redis.Redis(function(r) {
17 | r.select(CHAT_DB_NUMBER);
18 | });
19 |
20 | var channels = {};
21 |
22 | function createChannel(name) {
23 | var channel = new function () {
24 | var members = {};
25 | var nMembers = 0;
26 |
27 | this.name = name;
28 |
29 | this.join = function (session, text) {
30 | if (!members[session.id]) {
31 | sys.puts("channel.join(" + session.nick + ", " + this.name + ")");
32 | members[session.id] = { timestamp: new Date(), session: session };
33 | nMembers++;
34 | this.appendMessage(session.nick, "join", text);
35 | }
36 | };
37 |
38 | this.leave = function (session, text) {
39 | if (members[session.id]) {
40 | sys.puts("channel.leave(" + session.nick + ", " + this.name + ")");
41 | this.appendMessage(session.nick, "part", text)
42 | delete members[session.id];
43 | nMembers--;
44 | }
45 | };
46 |
47 | this.getNumberOfMembers = function() {
48 | return nMembers;
49 | };
50 |
51 | this.getMembers = function() {
52 | var nicks = [];
53 | for (sessionId in members) {
54 | if (!members.hasOwnProperty(sessionId)) continue;
55 | nicks.push(members[sessionId].session.nick);
56 | }
57 | return nicks;
58 | };
59 |
60 | this.appendMessage = function (nick, type, text) {
61 | rclient.llen(name).addCallback(function (value) {
62 | var m = { index: value
63 | , nick: nick
64 | , type: type // "msg", "join", "part"
65 | , text: text
66 | , timestamp: (new Date()).getTime()
67 | };
68 | rclient.rpush(name, JSON.stringify(m));
69 |
70 | for (var sessionId in members) {
71 | if (!members.hasOwnProperty(sessionId)) continue;
72 | members[sessionId].session.deliver([m]);
73 | }
74 | });
75 | };
76 |
77 | this.query = function (since, callback) {
78 | rclient.llen(name).addCallback( function(value) {
79 | if(since < value-1) {
80 | rclient.lrange(name, since, -1).addCallback( function(values) {
81 | var matching = [];
82 | if (values) {
83 | for(var i = 0; i < values.length; i++) {
84 | var message = JSON.parse(values[i]);
85 | matching.push(message);
86 | }
87 | }
88 | callback(matching);
89 | });
90 | } else {
91 | callback([]);
92 | }
93 | });
94 | };
95 | };
96 |
97 | channels[name] = channel;
98 | return channel;
99 | }
100 |
101 | createChannel(DEFAULT_CHANNEL);
102 |
103 | var sessions = {};
104 |
105 | function createSession (nick) {
106 | if (nick.length > 50) return null;
107 | if (/[^\w_\-^!]/.exec(nick)) return null;
108 |
109 | for (var i in sessions) {
110 | var session = sessions[i];
111 | if (session && session.nick === nick) return null;
112 | }
113 |
114 | var session = new function() {
115 | this.nick = nick;
116 |
117 | this.id = Math.floor(Math.random()*99999999999).toString();
118 |
119 | this.channel = channels[DEFAULT_CHANNEL];
120 |
121 | this.timestamp = new Date();
122 |
123 | // function (messages)
124 | this.callback = null;
125 |
126 | // private messages are transient and will be delivered in-band to this session
127 | this.systemMessages = [];
128 |
129 | this.poke = function () {
130 | this.timestamp = new Date();
131 | };
132 |
133 | this.destroy = function () {
134 | this.channel.leave(this, this.nick + " parted");
135 | delete sessions[this.id];
136 | };
137 |
138 | this.switchTo = function (channelName) {
139 | if (this.channel.name !== channelName) {
140 | this.channel.leave(this, "left " + this.channel.name);
141 | this.channel = channels[channelName] || createChannel(channelName);
142 | this.channel.join(this, "enters " + this.channel.name);
143 | }
144 | };
145 |
146 | this.query = function (since, callback) {
147 | if (this.systemMessages.length > 0) {
148 | callback(this.systemMessages);
149 | this.systemMessages.clear();
150 | } else {
151 | var s = this;
152 | this.channel.query(since, function(messages) {
153 | if (messages.length > 0) {
154 | callback(messages);
155 | } else {
156 | s.callback = { timestamp: new Date(), callback: callback };
157 | }
158 | });
159 | }
160 | };
161 |
162 | this.deliver = function (messages) {
163 | if (this.callback) {
164 | this.callback.callback(messages || []);
165 | this.callback = null;
166 | }
167 | };
168 |
169 | this.sendSystemMessage = function (text) {
170 | var message = {
171 | nick: "system",
172 | type: "msg",
173 | text: text,
174 | timestamp: (new Date()).getTime()
175 | };
176 |
177 | if (this.callback) {
178 | this.deliver([message]);
179 | } else {
180 | this.systemMessages.push(message);
181 | }
182 | };
183 |
184 | this.validate = function() {
185 | var now = new Date();
186 | if (now - this.timestamp > SESSION_TIMEOUT) {
187 | this.destroy();
188 | } else if (this.callback && (now - this.callback.timestamp > CALLBACK_TIMEOUT)) {
189 | this.deliver();
190 | }
191 | };
192 | };
193 |
194 | sessions[session.id] = session;
195 | session.channel.join(session, session.nick + " joined");
196 | return session;
197 | }
198 |
199 | // interval to kill off old sessions
200 | setInterval(function () {
201 | var now = new Date();
202 | for (var id in sessions) {
203 | if (!sessions.hasOwnProperty(id)) continue;
204 | sessions[id].validate();
205 | }
206 | }, 1000);
207 |
208 | fu.listen(PORT, HOST);
209 |
210 | fu.get("/", fu.staticHandler("index.html"));
211 | fu.get("/style.css", fu.staticHandler("style.css"));
212 | fu.get("/client.js", fu.staticHandler("client.js"));
213 | fu.get("/jquery-1.3.2.min.js", fu.staticHandler("jquery-1.3.2.min.js"));
214 |
215 |
216 | fu.get("/who", function (req, res) {
217 | var nicks = [];
218 | for (var id in sessions) {
219 | if (!sessions.hasOwnProperty(id)) continue;
220 | var session = sessions[id];
221 | nicks.push(session.nick);
222 | }
223 | res.simpleJSON(200, { nicks: nicks });
224 | });
225 |
226 | fu.get("/join", function (req, res) {
227 | var nick = req.uri.params["nick"];
228 | if (nick == null || nick.length == 0) {
229 | res.simpleJSON(400, {error: "Bad nick."});
230 | return;
231 | }
232 | var session = createSession(nick);
233 | if (session == null) {
234 | res.simpleJSON(400, {error: "Nick in use"});
235 | return;
236 | }
237 |
238 | //sys.puts("connection: " + nick + "@" + res.connection.remoteAddress);
239 |
240 | res.simpleJSON(200, { id: session.id, nick: session.nick, channel: session.channel.name, nicks: session.channel.getMembers() });
241 | });
242 |
243 | fu.get("/part", function (req, res) {
244 | var id = req.uri.params.id;
245 | var session;
246 | if (id && sessions[id]) {
247 | session = sessions[id];
248 | session.destroy();
249 | }
250 | res.simpleJSON(200, { });
251 | });
252 |
253 | fu.get("/recv", function (req, res) {
254 | if (!req.uri.params.since) {
255 | res.simpleJSON(400, { error: "Must supply since parameter" });
256 | return;
257 | }
258 | var id = req.uri.params.id;
259 | var session;
260 | if (id && sessions[id]) {
261 | session = sessions[id];
262 | session.poke();
263 |
264 | var since = parseInt(req.uri.params.since, 10);
265 | session.query(since, function(messages) {
266 | session.poke();
267 | res.simpleJSON(200, { messages: messages });
268 | });
269 | }
270 | });
271 |
272 | var commands = {
273 | "join": function(session, args) { session.switchTo(args[0]); },
274 | "leave": function(session) { session.switchTo(DEFAULT_CHANNEL); },
275 | "whoami": function(session) { session.sendSystemMessage("You are " + session.nick); },
276 | "where": function(session) { session.sendSystemMessage("You are in channel '" + session.channel.name + "'."); },
277 | "channels": function(session) {
278 | var names = [];
279 | for (var name in channels) {
280 | if (!channels.hasOwnProperty(name)) continue;
281 | var label = "'" + name + "' (" + channels[name].getNumberOfMembers() + ")" + (name == session.channel.name ? "*" : "");
282 | names.push(label);
283 | }
284 | session.sendSystemMessage("Available channels are " + names.join(", "));
285 | },
286 | "who": function(session) {
287 | var allNicks = session.channel.getMembers();
288 |
289 | // remove own name
290 | var nicks = [];
291 | for (var i in allNicks) {
292 | var nick = allNicks[i];
293 | if (nick !== session.nick)
294 | nicks.push(nick);
295 | }
296 |
297 | var text = (nicks.length > 0) ? (nicks.join(", ") + (nicks.length == 1 ? " is" : " are") + " here with you.") :
298 | "You are all alone. Try /channels to find channels with someone to talk to.";
299 | session.sendSystemMessage(text);
300 | },
301 | "flush": function(session) {
302 | rclient.flushdb();
303 | },
304 | "help": function(session) {
305 | var cmdNames = [];
306 | for (cmd in commands) {
307 | if (!commands.hasOwnProperty(cmd)) continue;
308 | cmdNames.push("/"+cmd);
309 | }
310 | session.sendSystemMessage("Available commands: " + cmdNames.join(", "));
311 | }
312 | };
313 |
314 | fu.get("/send", function (req, res) {
315 | var id = req.uri.params.id;
316 | var text = req.uri.params.text;
317 |
318 | var session = sessions[id];
319 | if (!session || !text) {
320 | res.simpleJSON(400, { error: "No such session id" });
321 | return;
322 | }
323 |
324 | session.poke();
325 |
326 | var match = text.match(/^\/(\S+)\s*(.+)?$/);
327 | var response = {};
328 | if (match) {
329 | sys.puts(match.length + " " + match)
330 | var command = commands[match[1]];
331 | if (command) {
332 | command(session, match[2] ? match[2].split(/\s/) : []);
333 | response["channel"] = session.channel.name;
334 | response["nicks"] = session.channel.getMembers();
335 | }
336 | } else {
337 | session.channel.appendMessage(session.nick, "msg", text);
338 | }
339 | res.simpleJSON(200, response);
340 | });
341 |
--------------------------------------------------------------------------------
/fu.js:
--------------------------------------------------------------------------------
1 | var createServer = require("http").createServer;
2 | var process = require("posix");
3 | var sys = require("sys");
4 | DEBUG = false;
5 |
6 | var fu = exports;
7 |
8 | var NOT_FOUND = "Not Found\n";
9 |
10 | function notFound(req, res) {
11 | res.sendHeader(404, [ ["Content-Type", "text/plain"]
12 | , ["Content-Length", NOT_FOUND.length]
13 | ]);
14 | res.sendBody(NOT_FOUND);
15 | res.finish();
16 | }
17 |
18 | var getMap = {};
19 |
20 | fu.get = function (path, handler) {
21 | getMap[path] = handler;
22 | };
23 |
24 | var server = createServer(function (req, res) {
25 | if (req.method === "GET" || req.method === "HEAD") {
26 | var handler = getMap[req.uri.path] || notFound;
27 |
28 | res.simpleText = function (code, body) {
29 | res.sendHeader(code, [ ["Content-Type", "text/plain"]
30 | , ["Content-Length", body.length]
31 | ]);
32 | res.sendBody(body);
33 | res.finish();
34 | };
35 |
36 | res.simpleJSON = function (code, obj) {
37 | var body = JSON.stringify(obj);
38 | res.sendHeader(code, [ ["Content-Type", "text/json"]
39 | , ["Content-Length", body.length]
40 | ]);
41 | res.sendBody(body);
42 | res.finish();
43 | };
44 |
45 | handler(req, res);
46 | }
47 | });
48 |
49 | fu.listen = function (port, host) {
50 | server.listen(port, host);
51 | sys.puts("Server at http://" + (host || "127.0.0.1") + ":" + port.toString() + "/");
52 | };
53 |
54 | fu.close = function () { server.close(); };
55 |
56 | function extname (path) {
57 | var index = path.lastIndexOf(".");
58 | return index < 0 ? "" : path.substring(index);
59 | }
60 |
61 | fu.staticHandler = function (filename) {
62 | var body, headers;
63 | var content_type = fu.mime.lookupExtension(extname(filename));
64 | var encoding = (content_type.slice(0,4) === "text" ? "utf8" : "binary");
65 |
66 | function loadResponseData(callback) {
67 | if (body && headers && !DEBUG) {
68 | callback();
69 | return;
70 | }
71 |
72 | sys.puts("loading " + filename + "...");
73 | var promise = process.cat(filename, encoding);
74 |
75 | promise.addCallback(function (data) {
76 | body = data;
77 | headers = [ [ "Content-Type" , content_type ]
78 | , [ "Content-Length" , body.length ]
79 | ];
80 | if (!DEBUG)
81 | headers.push(["Cache-Control", "public"]);
82 |
83 | sys.puts("static file " + filename + " loaded");
84 | callback();
85 | });
86 |
87 | promise.addErrback(function () {
88 | sys.puts("Error loading " + filename);
89 | });
90 | }
91 |
92 | return function (req, res) {
93 | loadResponseData(function () {
94 | res.sendHeader(200, headers);
95 | res.sendBody(body, encoding);
96 | res.finish();
97 | });
98 | }
99 | };
100 |
101 | // stolen from jack- thanks
102 | fu.mime = {
103 | // returns MIME type for extension, or fallback, or octet-steam
104 | lookupExtension : function(ext, fallback) {
105 | return fu.mime.TYPES[ext.toLowerCase()] || fallback || 'application/octet-stream';
106 | },
107 |
108 | // List of most common mime-types, stolen from Rack.
109 | TYPES : { ".3gp" : "video/3gpp"
110 | , ".a" : "application/octet-stream"
111 | , ".ai" : "application/postscript"
112 | , ".aif" : "audio/x-aiff"
113 | , ".aiff" : "audio/x-aiff"
114 | , ".asc" : "application/pgp-signature"
115 | , ".asf" : "video/x-ms-asf"
116 | , ".asm" : "text/x-asm"
117 | , ".asx" : "video/x-ms-asf"
118 | , ".atom" : "application/atom+xml"
119 | , ".au" : "audio/basic"
120 | , ".avi" : "video/x-msvideo"
121 | , ".bat" : "application/x-msdownload"
122 | , ".bin" : "application/octet-stream"
123 | , ".bmp" : "image/bmp"
124 | , ".bz2" : "application/x-bzip2"
125 | , ".c" : "text/x-c"
126 | , ".cab" : "application/vnd.ms-cab-compressed"
127 | , ".cc" : "text/x-c"
128 | , ".chm" : "application/vnd.ms-htmlhelp"
129 | , ".class" : "application/octet-stream"
130 | , ".com" : "application/x-msdownload"
131 | , ".conf" : "text/plain"
132 | , ".cpp" : "text/x-c"
133 | , ".crt" : "application/x-x509-ca-cert"
134 | , ".css" : "text/css"
135 | , ".csv" : "text/csv"
136 | , ".cxx" : "text/x-c"
137 | , ".deb" : "application/x-debian-package"
138 | , ".der" : "application/x-x509-ca-cert"
139 | , ".diff" : "text/x-diff"
140 | , ".djv" : "image/vnd.djvu"
141 | , ".djvu" : "image/vnd.djvu"
142 | , ".dll" : "application/x-msdownload"
143 | , ".dmg" : "application/octet-stream"
144 | , ".doc" : "application/msword"
145 | , ".dot" : "application/msword"
146 | , ".dtd" : "application/xml-dtd"
147 | , ".dvi" : "application/x-dvi"
148 | , ".ear" : "application/java-archive"
149 | , ".eml" : "message/rfc822"
150 | , ".eps" : "application/postscript"
151 | , ".exe" : "application/x-msdownload"
152 | , ".f" : "text/x-fortran"
153 | , ".f77" : "text/x-fortran"
154 | , ".f90" : "text/x-fortran"
155 | , ".flv" : "video/x-flv"
156 | , ".for" : "text/x-fortran"
157 | , ".gem" : "application/octet-stream"
158 | , ".gemspec" : "text/x-script.ruby"
159 | , ".gif" : "image/gif"
160 | , ".gz" : "application/x-gzip"
161 | , ".h" : "text/x-c"
162 | , ".hh" : "text/x-c"
163 | , ".htm" : "text/html"
164 | , ".html" : "text/html"
165 | , ".ico" : "image/vnd.microsoft.icon"
166 | , ".ics" : "text/calendar"
167 | , ".ifb" : "text/calendar"
168 | , ".iso" : "application/octet-stream"
169 | , ".jar" : "application/java-archive"
170 | , ".java" : "text/x-java-source"
171 | , ".jnlp" : "application/x-java-jnlp-file"
172 | , ".jpeg" : "image/jpeg"
173 | , ".jpg" : "image/jpeg"
174 | , ".js" : "application/javascript"
175 | , ".json" : "application/json"
176 | , ".log" : "text/plain"
177 | , ".m3u" : "audio/x-mpegurl"
178 | , ".m4v" : "video/mp4"
179 | , ".man" : "text/troff"
180 | , ".mathml" : "application/mathml+xml"
181 | , ".mbox" : "application/mbox"
182 | , ".mdoc" : "text/troff"
183 | , ".me" : "text/troff"
184 | , ".mid" : "audio/midi"
185 | , ".midi" : "audio/midi"
186 | , ".mime" : "message/rfc822"
187 | , ".mml" : "application/mathml+xml"
188 | , ".mng" : "video/x-mng"
189 | , ".mov" : "video/quicktime"
190 | , ".mp3" : "audio/mpeg"
191 | , ".mp4" : "video/mp4"
192 | , ".mp4v" : "video/mp4"
193 | , ".mpeg" : "video/mpeg"
194 | , ".mpg" : "video/mpeg"
195 | , ".ms" : "text/troff"
196 | , ".msi" : "application/x-msdownload"
197 | , ".odp" : "application/vnd.oasis.opendocument.presentation"
198 | , ".ods" : "application/vnd.oasis.opendocument.spreadsheet"
199 | , ".odt" : "application/vnd.oasis.opendocument.text"
200 | , ".ogg" : "application/ogg"
201 | , ".p" : "text/x-pascal"
202 | , ".pas" : "text/x-pascal"
203 | , ".pbm" : "image/x-portable-bitmap"
204 | , ".pdf" : "application/pdf"
205 | , ".pem" : "application/x-x509-ca-cert"
206 | , ".pgm" : "image/x-portable-graymap"
207 | , ".pgp" : "application/pgp-encrypted"
208 | , ".pkg" : "application/octet-stream"
209 | , ".pl" : "text/x-script.perl"
210 | , ".pm" : "text/x-script.perl-module"
211 | , ".png" : "image/png"
212 | , ".pnm" : "image/x-portable-anymap"
213 | , ".ppm" : "image/x-portable-pixmap"
214 | , ".pps" : "application/vnd.ms-powerpoint"
215 | , ".ppt" : "application/vnd.ms-powerpoint"
216 | , ".ps" : "application/postscript"
217 | , ".psd" : "image/vnd.adobe.photoshop"
218 | , ".py" : "text/x-script.python"
219 | , ".qt" : "video/quicktime"
220 | , ".ra" : "audio/x-pn-realaudio"
221 | , ".rake" : "text/x-script.ruby"
222 | , ".ram" : "audio/x-pn-realaudio"
223 | , ".rar" : "application/x-rar-compressed"
224 | , ".rb" : "text/x-script.ruby"
225 | , ".rdf" : "application/rdf+xml"
226 | , ".roff" : "text/troff"
227 | , ".rpm" : "application/x-redhat-package-manager"
228 | , ".rss" : "application/rss+xml"
229 | , ".rtf" : "application/rtf"
230 | , ".ru" : "text/x-script.ruby"
231 | , ".s" : "text/x-asm"
232 | , ".sgm" : "text/sgml"
233 | , ".sgml" : "text/sgml"
234 | , ".sh" : "application/x-sh"
235 | , ".sig" : "application/pgp-signature"
236 | , ".snd" : "audio/basic"
237 | , ".so" : "application/octet-stream"
238 | , ".svg" : "image/svg+xml"
239 | , ".svgz" : "image/svg+xml"
240 | , ".swf" : "application/x-shockwave-flash"
241 | , ".t" : "text/troff"
242 | , ".tar" : "application/x-tar"
243 | , ".tbz" : "application/x-bzip-compressed-tar"
244 | , ".tcl" : "application/x-tcl"
245 | , ".tex" : "application/x-tex"
246 | , ".texi" : "application/x-texinfo"
247 | , ".texinfo" : "application/x-texinfo"
248 | , ".text" : "text/plain"
249 | , ".tif" : "image/tiff"
250 | , ".tiff" : "image/tiff"
251 | , ".torrent" : "application/x-bittorrent"
252 | , ".tr" : "text/troff"
253 | , ".txt" : "text/plain"
254 | , ".vcf" : "text/x-vcard"
255 | , ".vcs" : "text/x-vcalendar"
256 | , ".vrml" : "model/vrml"
257 | , ".war" : "application/java-archive"
258 | , ".wav" : "audio/x-wav"
259 | , ".wma" : "audio/x-ms-wma"
260 | , ".wmv" : "video/x-ms-wmv"
261 | , ".wmx" : "video/x-ms-wmx"
262 | , ".wrl" : "model/vrml"
263 | , ".wsdl" : "application/wsdl+xml"
264 | , ".xbm" : "image/x-xbitmap"
265 | , ".xhtml" : "application/xhtml+xml"
266 | , ".xls" : "application/vnd.ms-excel"
267 | , ".xml" : "application/xml"
268 | , ".xpm" : "image/x-xpixmap"
269 | , ".xsl" : "application/xml"
270 | , ".xslt" : "application/xslt+xml"
271 | , ".yaml" : "text/yaml"
272 | , ".yml" : "text/yaml"
273 | , ".zip" : "application/zip"
274 | }
275 | };
276 |
--------------------------------------------------------------------------------
/jquery-1.3.2.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery JavaScript Library v1.3.2
3 | * http://jquery.com/
4 | *
5 | * Copyright (c) 2009 John Resig
6 | * Dual licensed under the MIT and GPL licenses.
7 | * http://docs.jquery.com/License
8 | *
9 | * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009)
10 | * Revision: 6246
11 | */
12 | (function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F
=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+">"+T+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("",""]||!O.indexOf("",""]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,""]||!O.indexOf("
",""]||(!O.indexOf(" | ","
"]||!O.indexOf("",""]||!o.support.htmlSerialize&&[1,"div","
"]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}});
13 | /*
14 | * Sizzle CSS Selector Engine - v0.9.3
15 | * Copyright 2009, The Dojo Foundation
16 | * Released under the MIT, BSD, and GPL Licenses.
17 | * More information: http://sizzlejs.com/
18 | */
19 | (function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return UT[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(Fa';var H=K.getElementsByTagName("*"),E=K.getElementsByTagName("a")[0];if(!H||!H.length||!E){return}o.support={leadingWhitespace:K.firstChild.nodeType==3,tbody:!K.getElementsByTagName("tbody").length,objectAll:!!K.getElementsByTagName("object")[0].getElementsByTagName("*").length,htmlSerialize:!!K.getElementsByTagName("link").length,style:/red/.test(E.getAttribute("style")),hrefNormalized:E.getAttribute("href")==="/a",opacity:E.style.opacity==="0.5",cssFloat:!!E.style.cssFloat,scriptEval:false,noCloneEvent:true,boxModel:null};G.type="text/javascript";try{G.appendChild(document.createTextNode("window."+J+"=1;"))}catch(I){}F.insertBefore(G,F.firstChild);if(l[J]){o.support.scriptEval=true;delete l[J]}F.removeChild(G);if(K.attachEvent&&K.fireEvent){K.attachEvent("onclick",function(){o.support.noCloneEvent=false;K.detachEvent("onclick",arguments.callee)});K.cloneNode(true).fireEvent("onclick")}o(function(){var L=document.createElement("div");L.style.width=L.style.paddingLeft="1px";document.body.appendChild(L);o.boxModel=o.support.boxModel=L.offsetWidth===2;document.body.removeChild(L).style.display="none"})})();var w=o.support.cssFloat?"cssFloat":"styleFloat";o.props={"for":"htmlFor","class":"className","float":w,cssFloat:w,styleFloat:w,readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",tabindex:"tabIndex"};o.fn.extend({_load:o.fn.load,load:function(G,J,K){if(typeof G!=="string"){return this._load(G)}var I=G.indexOf(" ");if(I>=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("").append(M.responseText.replace(/