├── 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 | 4 | 5 | 6 | node chat 7 | 8 | 9 |
10 |
11 |
12 |
13 |

14 | This is a chat room. Both the client-side and server-side are 15 | written in javascript. The source code is 16 | here. 17 |

18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |
26 |

loading

27 |
28 |
29 |
30 | 35 |
36 |
37 | 41 | 42 |
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+">"});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(F=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(//g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='
    ';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})(); --------------------------------------------------------------------------------