User:WaterDemonBaku/monobook.js

/************** Torus chat client ************** * A Wikia chat client that isn't Special:Chat * * --- * *   Written and maintained by Monchoman45    * *   https://github.com/Monchoman45/Torus     * ***********************************************/ if(window.Torus) {throw new Error('Torus already loaded');} window.Torus = { init: false, local: '', version: 241, pretty_version: '2.4.1', chats: {}, listeners: { window: { load: [], unload: [], },		chat: { 'new': [], open: [], connected: [], close: [], reopen: [], update_user: [], remove_user: [], initial: [], send_message: [], send_me: [], setstatus: [], logout: [], givechatmod: [], kick: [], ban: [], unban: [], //FIXME: never called openprivate: [], },		io: { initial: [], message: [], alert: [], me: [], join: [], rejoin: [], part: [], ghost: [], logout: [], update_user: [], ctcp: [], mod: [], kick: [], ban: [], unban: [], open_private: [], force_reconnect: [], force_disconnect: [], },		ext: { 'new': [], load_options: [], //FIXME: find a better place for this after_load_options: [], //FIXME: find a better place for this save_options: [], //FIXME: find a better place for this }	},	io: { transports: {}, jsonp_callbacks: [], },	classes: {}, util: {}, data: { domains: {}, ids: {}, blocked: [], blockedBy: [], titleflash: document.title, pinginterval: 0, history: [], histindex: 0, tabtext: '', tabindex: 0, tabpos: 0, fullscreen: false, },	options: {}, } //Function for adding an event listener //Accepts the event name and the listener function Torus.add_listener = function(type, event, func) { if(!this.listeners[type]) { //FIXME: the error is probably better but it causes problems with ui events //throw new Error('Event type `' + type + '` doesn\'t exist'); this.listeners[type] = {}; }	if(!this.listeners[type][event]) {this.listeners[type][event] = [];} this.listeners[type][event].push(func); return true; } //Function for removing an event listener //Accepts the event name and the listener function //Returns true if the listener is removed, otherwise false Torus.remove_listener = function(type, event, func) { if(!this.listeners[type]) {throw new Error('Event type `' + type + '` doesn\'t exist');} if(!this.listeners[type][event]) {return false;} for(var i = 0; i < this.listeners[type][event].length; i++) { if(this.listeners[type][event][i] == func) {this.listeners[type][event].splice(i, 1); return true;} }	return false; } //Function for calling listeners for an event //Accepts the event name //Returns false if the type is invalid, otherwise true Torus.call_listeners = function(event) { if(!event.type || !event.event) {throw new Error('Event doesn\'t have `.type` or `.event`: ' + JSON.stringify(event));} if(!this.listeners[event.type]) {throw new Error('Event type `' + event.type + '` doesn\'t exist');} if(this.listeners[event.type][event.event]) { for(var i = 0; i < this.listeners[event.type][event.event].length; i++) { this.listeners[event.type][event.event][i].call(this, event); }	}	if(event.room && !(this instanceof Torus.classes.Chat) && !(this instanceof Torus.classes.Extension)) {event.room.call_listeners(event);} return true; } Torus.open = function(domain, parent, users) { if(Torus.chats[domain]) {var chat = Torus.chats[domain];} else {var chat = new Torus.classes.Chat(domain, parent, users);} if(!chat.connecting && !chat.connected) {chat.connect;} return chat; } Torus.logout = function { for(var i in Torus.chats) { if(i > 0) { var chat = Torus.chats[i]; chat.send_command('logout'); Torus.call_listeners(new Torus.classes.ChatEvent('logout', chat)); chat.disconnect('logout'); }	} } Torus.alert = function(text, room) { if(!room) {room = Torus.chats[0];} text = text.trim; if(text.indexOf('\n') != -1) { var spl = text.split('\n'); for(var i = 0; i < spl.length; i++) { var event = new Torus.classes.IOEvent('alert', room); event.text = spl[i]; Torus.call_listeners(event); }	}	else { var event = new Torus.classes.IOEvent('alert', room); event.text = text; Torus.call_listeners(event); } } Torus.onload = function { Torus.load_options; Torus.call_listeners(new Torus.classes.WindowEvent('load')); //Torus.alert('Initialized.'); Torus.init = true; } Torus.unload = function { Torus.logout; Torus.save_options; Torus.call_listeners(new Torus.classes.WindowEvent('unload')); } Torus.save_options = function { var save = {version: Torus.version, data: Torus.options}; var event = new Torus.classes.ExtEvent('save_options'); event.options = save; Torus.call_listeners(event); window.localStorage.setItem('torus-options', JSON.stringify(save)); return save; } Torus.load_options = function { var load = JSON.parse(window.localStorage.getItem('torus-options')); if(load) { if(load.version < 231) { window.localStorage.removeItem('torus-options'); load.data = {}; }		for(var i in load.data) {Torus.options[i] = load.data[i];} }	Torus.call_listeners(new Torus.classes.ExtEvent('load_options')); return Torus.options; } Torus.io.ajax = function(method, post, callback) { var str = ''; for(var i in post) {str += '&' + i + '=' + encodeURIComponent(post[i]);} str = str.substring(1); var xhr = new XMLHttpRequest; xhr.addEventListener('loadend', function {		if(this.status == 200) {			if(typeof callback == 'function') {callback.call(Torus, this.response);}		}		else {throw new Error('Request returned response ' + this.status + '. (io.ajax)');}	}); xhr.open('POST', '/index.php?action=ajax&rs=ChatAjax&method=' + method, true); xhr.responseType = 'json'; xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.setRequestHeader('Api-Client', 'Torus/' + Torus.version); xhr.send(str); } Torus.io.getPrivateId = function(users, callback) { Torus.io.ajax('getPrivateRoomId', {users: JSON.stringify(users)}, function(data) {		if(typeof callback == 'function') {callback.call(Torus, data.id);}	}); } Torus.io.getBlockedPrivate = function(callback) { Torus.io.ajax('getListOfBlockedPrivate', {}, function(data) {		Torus.data.blockedBy = data.blockedByChatUsers;		Torus.data.blocked = data.blockedChatUsers;		if(typeof callback == 'function') {callback.call(Torus, data);}	}); } Torus.io.block = function(user, callback) { Torus.io.ajax('blockOrBanChat', {userToBan: user, dir: 'add'}, function(data) {		Torus.data.blocked.push(user);		if(typeof callback == 'function') {callback.call(Torus, data);}	}); } Torus.io.unblock = function(user, callback) { Torus.io.ajax('blockOrBanChat', {userToBan: user, dir: 'remove'}, function(data) {		for(var i = 0; i < Torus.data.blocked.length; i++) {			if(Torus.data.blocked[i] == user) {Torus.data.blocked.splice(i, 1); break;}		}		if(typeof callback == 'function') {callback.call(Torus, data);}	}); } Torus.io.key = function(callback) { var xhr = new XMLHttpRequest; xhr.addEventListener('loadend', function {		if(this.status == 200) {			if(typeof callback == 'function') {				if(typeof this.response.chatkey == 'string') {callback.call(Torus, this.response.chatkey);}				else {callback.call(Torus, false);}			}		}		else if(this.status == 404) { //wiki doesn't have chat			if(typeof callback == 'function') {callback.call(Torus, false);}		}		else {			if(typeof callback == 'function') {callback.call(Torus, {error: 'http', code: this.status});}			throw new Error('io.key: request returned HTTP ' + this.status + '.');		}	}); xhr.open('GET', '/wikia.php?controller=Chat&format=json', true); xhr.responseType = 'json'; xhr.setRequestHeader('Api-Client', 'Torus/' + Torus.version); xhr.send; } Torus.io.spider = function(domain, callback) { if(Torus.cache.data[domain]) { if(typeof callback == 'function') {callback.call(Torus, Torus.cache.data[domain]);} return; }	var xhr = new XMLHttpRequest; xhr.addEventListener('loadend', function {		if(this.status == 200) {			if(!this.response.error) {Torus.cache.update(domain, this.response);}			if(typeof callback == 'function') {callback.call(Torus, this.response);}		}		else {			if(typeof callback == 'function') {callback.call(Torus, {error: 'http', code: this.status});}			throw new Error('io.spider: CORS proxy returned HTTP ' + this.status + '.');		}	}); xhr.open('GET', 'http://cis-linux2.temple.edu/~tuf23151/torus.php?domain=' + domain, true); xhr.responseType = 'json'; xhr.setRequestHeader('Api-Client', 'Torus/' + Torus.version); xhr.send; } Torus.io.jsonp = function(url, callback) { var id = Torus.io.jsonp_callbacks.length var script = document.createElement('script'); script.src = url + '&callback=Torus.io.jsonp_callbacks[' + id + ']'; script.type = 'text/javascript'; script.setAttribute('data-id', '' + id); script.onload = Torus.io.jsonp_cleanup; document.head.appendChild(script); Torus.io.jsonp_callbacks.push(callback); } Torus.io.jsonp_cleanup = function { this.parentNode.removeChild(this); Torus.io.jsonp_callbacks[this.getAttribute('data-id') * 1] = null; for(var i = 0; i < Torus.io.jsonp_callbacks.length; i++) { if(Torus.io.jsonp_callbacks[i] != null) {return;} }	Torus.io.jsonp_callbacks = []; } Torus.io.transports.polling = function(domain, info) { if(!(this instanceof Torus.io.transports.polling)) {throw new Error('Must create transport with `new`.');} this.open = false; this.domain = domain; this.host = info.host; this.port = info.port; this.server = info.server; this.room = info.room; this.key = info.key; this.session = ''; this.url = ''; this.xhr = null; this.ping_interval = 0; this.iid = 0; this.listeners = { 'io': {}, };	this.add_listener('io', 'disconnect', this.close); if(!this.host || !this.port || !this.server || !this.room) { var sock = this; //FIXME: this forces a closure scope Torus.io.spider(this.domain, function(data) {			if(data.error == 'nochat') {				sock.call_listeners({ type: 'io', event: 'disconnect', message: 'no chat', sock: sock });				return;			}			else if(data.error) {throw new Error('transport: CORS proxy returned error `' + data.error + '`');}			if(!sock.host) {sock.host = data.host;}			if(!sock.port) {sock.port = data.port;}			if(!sock.server) {sock.server = data.server;}			if(!sock.room) {sock.room = data.room;}			if(sock.host && sock.port && sock.server && sock.room && sock.key && !sock.open) {sock.poll;} //FIXME: long		}); }	if(!this.key) { var sock = this; //FIXME: this forces a closure scope Torus.io.key(function(key) {			if(!key) {throw new Error('transport: not logged in');}			sock.key = key;			if(sock.host && sock.port && sock.server && sock.room && sock.key && !sock.open) {sock.poll;} //FIXME: long		}); }	if(this.host && this.port && this.server && this.room && this.key && !this.open) {this.poll;} //FIXME: long } Torus.io.transports.polling.prototype.poll = function { this.url = 'http://' + this.host + ':' + this.port + '/socket.io/?EIO=2&transport=polling&name=' + encodeURIComponent(wgUserName) + '&key=' + this.key + '&roomId=' + this.room + '&serverId=' + this.server; if(this.session) {this.url += '&sid=' + this.session;} this.open = true; this.xhr = new XMLHttpRequest; this.xhr.sock = this; this.xhr.addEventListener('loadend', function { //FIXME: hardcoded function		if(this.sock.xhr != this) {console.log('xhr returned and found itself orphaned:', this.sock);}		if(this.status == 200) {			//As far as I know all messages begin with a null byte (to tell socket.io that they are strings)			//after this is the length, encoded in the single most ridiculously stupid format ever created			//the decimal representation of the length is encoded in binary, terminated by \ufffd: for example,			//if the message length is 30 bytes, then the length is encoded as \x03\x00\ufffd			//yes that's right, rather than use the actual number, they decided to take the same amount of space			//to write the number in a format that provides no advantages and is literally always harder to parse			//following the asinine length is the number 4 (which means message)			//following that is the message type, and then immediately thereafter is the actual message content //I swear to god socket.io must have been high when they designed this var data = this.responseText; while(data.length > 0) { //for(var ufffd = this.responseText.indexOf('\ufffd'); ufffd != -1; ufffd = this.responseText.indexOf('\ufffd', ufffd + 1)) { var ufffd = data.indexOf('\ufffd'); var end = 1 + ufffd + Torus.util.stupid_to_int(data.substring(1, ufffd)); var text = data.substring(1 + ufffd, end); data = data.substring(end); var packet_type = text.charAt(0) * 1; text = text.substring(1); var sock = this.sock; switch(packet_type) { case 0: //connect //we should only reach this once, hopefully var data = JSON.parse(text); sock.session = data.sid; sock.ping_interval = Math.floor(data.pingTimeout * 3 / 4); //pingTimeout is the longest we can go without disconnecting if(sock.iid) {clearInterval(sock.iid);} sock.iid = setInterval(function {sock.ping;}, sock.ping_interval); //FIXME: this forces a closure scope break; case 1: //disconnect sock.call_listeners({							type: 'io',							event: 'disconnect',							message: 'Server closed the connection',							sock: sock						}); return; case 2: //ping sock.ping; break; case 4: //message var message_type = text.charAt(0) * 1; text = text.substring(1); //Torus.logs.socket[sock.room].push({id: (new Date).getTime, type: message_type, message: text}); //FIXME: ui						switch(message_type) { //yep, there are two of these case 0: //connect sock.call_listeners({									type: 'io',									event: 'connect',									sock: sock								}); break; case 1: //disconnect sock.call_listeners({									type: 'io',									event: 'disconnect',									message: 'Server closed the connection',									sock: sock								}); return; case 2: //event sock.call_listeners({									type: 'io',									event: 'message',									message: JSON.parse(text)[1],									sock: sock								}); break; case 4: //error sock.call_listeners({									type: 'io',									event: 'disconnect',									message: 'Protocol error: ' + JSON.parse(text),									sock: sock								}); return; case 3: //ack case 5: //binary event case 6: //binary ack console.log('Unimplemented data type: ' + this.responseText); sock.call_listeners({									type: 'io',									event: 'disconnect',									message: 'Protocol error: Received unimplemented data type `' + message_type + '`',									sock: sock								}); return; }						break; case 3: //pong case 6: //noop break; case 5: //upgrade default: console.log('Unimplemented data type: ' + this.responseText); sock.call_listeners({							type: 'io',							event: 'disconnect',							message: 'Protocol error: Received unimplemented data type `' + packet_type + '`',							sock: sock						}); return; }			}			this.sock.poll; } //status == 200 else if(this.status == 400 || this.status == 404) { this.sock.session = ''; this.sock.poll; }		else if(this.status != 0) { this.sock.call_listeners({				type: 'io',				event: 'disconnect',				message: 'Socket error (polling): HTTP status ' + this.status,				sock: this.sock			}); }		else {console.log('HTTP status 0: ', this.sock);} });	this.xhr.open('GET', this.url, true);	//this.xhr.setRequestHeader('Api-Client', 'Torus/' + Torus.version);	this.xhr.send; } Torus.io.transports.polling.prototype.send = function(message) {	var data = '42["message",' + Torus.util.utf8ify(JSON.stringify(message)) + ']';	var xhr = new XMLHttpRequest;	xhr.open('POST', this.url, true);	xhr.setRequestHeader('Content-Type', 'text/plain;charset=utf-8');	//xhr.setRequestHeader('Api-Client', 'Torus/' + Torus.version);	xhr.send('' + data.length + ':' + data); } Torus.io.transports.polling.prototype.ping = function {	var xhr = new XMLHttpRequest;	xhr.open('POST', this.url, true);	xhr.setRequestHeader('Content-Type', 'text/plain;charset=utf-8');	//xhr.setRequestHeader('Api-Client', 'Torus/' + Torus.version);	xhr.send('1:2'); } Torus.io.transports.polling.prototype.close = function {	this.open = false;	if(this.xhr) {		this.xhr.abort;		this.xhr = null; }	if(this.iid) { clearInterval(this.iid); this.iid = 0; } } Torus.io.transports.polling.prototype.add_listener = Torus.add_listener; Torus.io.transports.polling.prototype.remove_listener = Torus.remove_listener; Torus.io.transports.polling.prototype.call_listeners = Torus.call_listeners; Torus.classes.Chat = function(domain, parent, users) { if(!(this instanceof Torus.classes.Chat)) {throw new Error('Must call Torus.classes.Chat with `new`.');} if(!domain && domain !== 0) {throw new Error('Torus.classes.Chat: Tried to create room with no domain.');} if(Torus.chats[domain]) {throw new Error('Torus.classes.Chat: Tried to create room `' + name + '` but it already exists.');} this.domain = domain; Torus.chats[domain] = this; if(domain) { //this is a normal room if(parent) { //PM this.id = domain * 1; this.parent = parent; this.priv_users = users; if(this.priv_users.length == 2) { for(var i = 0; i < this.priv_users.length; i++) { if(this.priv_users[i] != wgUserName) {this.name = 'PM: ' + this.priv_users[i]; break;} }			}			else {this.name = this.domain;} }		else { //public this.id = 1; //this'll get overwriten later this.parent = false; this.name = this.domain; }		//this.away_timeout = 0; this.connected = false; this.connecting = false; this.socket = false; this.users = 0; this.userlist = {}; this.listeners = { chat: {}, io: {}, };	}	else { //this is the status room this.id = 0; this.name = '#status' this.listeners = { chat: {}, io: {}, };	}	var event = new Torus.classes.ChatEvent('new', this); Torus.call_listeners(event); } Torus.classes.Chat.socket_connect = function(event) { event.sock.chat.connecting = false; event.sock.chat.connected = true; event.sock.chat.send_command('initquery'); Torus.alert('Connected.', event.sock.chat); Torus.io.getBlockedPrivate; Torus.call_listeners(new Torus.classes.ChatEvent('connected', event.sock.chat)); } Torus.classes.Chat.socket_disconnect = function(event) {event.sock.chat.disconnect(event.message);} Torus.classes.Chat.socket_message = function(event) { if(event.message.data) {data = JSON.parse(event.message.data);} else {data = {};} //disableReconnect and probably forceReconnect do this var e = event.sock.chat['event_' + event.message.event](data); Torus.call_listeners(e); } Torus.classes.Chat.prototype.connect = function(transport) { if(this.connected || this.connecting) {throw new Error('Tried to open ' + this.domain + ' which is already open. (Chat.connect)');} if(!transport) {transport = 'polling';} this.connecting = true; if(this.parent) { var info = { host: this.parent.socket.host, port: this.parent.socket.port, server: this.parent.socket.server, room: this.id, key: this.parent.socket.key, };	}	else {var info = {};} this.socket = new Torus.io.transports[transport](this.domain, info); this.socket.chat = this; this.socket.add_listener('io', 'connect', Torus.classes.Chat.socket_connect); this.socket.add_listener('io', 'disconnect', Torus.classes.Chat.socket_disconnect); this.socket.add_listener('io', 'message', Torus.classes.Chat.socket_message); Torus.call_listeners(new Torus.classes.ChatEvent('open', this)); } Torus.classes.Chat.prototype.reconnect = function { this.socket.close; this.connected = false; this.connecting = false; this.connect(this.socket.transport); Torus.call_listeners(new Torus.classes.ChatEvent('reopen', this)); } Torus.classes.Chat.prototype.disconnect = function(message) { this.socket.close; this.socket = false; Torus.alert('Disconnected from {' + this.name + '}: ' + message); this.connecting = false; this.connected = false; var event = new Torus.classes.ChatEvent('close', this); event.message = message; Torus.call_listeners(event); this.users = 0; this.userlist = {}; } Torus.classes.Chat.prototype.send_message = function(text) { if(!this.connected) {throw new Error('Tried to send a message to room ' + this.domain + ' before it finished connecting. (Chat.send_message)');} text += ''; message = {attrs: {msgType: 'chat', text: text, name: wgUserName}}; if(text.indexOf('/me') == 0) {var event = new Torus.classes.ChatEvent('send_me', this);} else {var event = new Torus.classes.ChatEvent('send_message', this);} event.message = message; Torus.call_listeners(event); if(this.parent) {this.parent.send_command('openprivate', {roomId: this.id, users: this.users});} this.socket.send(JSON.stringify(message)); } Torus.classes.Chat.prototype.send_command = function(command, args) { if(!this.connected) {throw new Error('Tried to send a command to room ' + this.domain + ' before it finished connecting. (Chat.send_command)');} if(!args) {args = {};} var message = {attrs: {msgType: 'command', command: command}}; for(var i in args) {message.attrs[i] = args[i];} var event = new Torus.classes.ChatEvent(command, this); event.message = message; Torus.call_listeners(event); this.socket.send(JSON.stringify(message)); } Torus.classes.Chat.prototype.set_status = function(state, message) { var user = this.userlist[wgUserName]; if(!state) {state = user.status_state;} if(!message) {message = user.status_message;} user.old_state = user.status_state; user.old_message = user.status_message; this.send_command('setstatus', {statusState: state, statusMessage: message}); } Torus.classes.Chat.prototype.ctcp = function(target, proto, data) { if(!target) {target = '';} //everyone if(!proto) {proto = 'version';} if(data === undefined) {data = '';} var state = this.userlist[wgUserName].status_state; var message = this.userlist[wgUserName].status_message; this.send_command('setstatus', {statusState: 'CTCP|' + target + '|' + proto, statusMessage: data}); this.send_command('setstatus', {statusState: state, statusMessage: message}); } Torus.classes.Chat.prototype.mod = function(user) {this.send_command('givechatmod', {userToPromote: user});} Torus.classes.Chat.prototype.kick = function(user) {this.send_command('kick', {userToKick: user});} Torus.classes.Chat.prototype.ban = function(user, expiry, reason) { if(typeof expiry == 'string') {expiry = Torus.util.expiry_to_seconds(expiry);} if(!reason) {reason = 'Misbehaving in chat';} //is a ban //FIXME: ?action=query&meta=allmessages this.send_command('ban', {userToBan: user, reason: reason, time: expiry}); } Torus.classes.Chat.prototype.unban = function(user, reason) { if(!reason) {reason = 'undo';} //FIXME: ?action=query&meta=allmessages return this.ban(user, 0, reason); } Torus.classes.Chat.prototype.open_private = function(users, callback, id) { if(users.indexOf(wgUserName) == -1) {users.push(wgUserName);} if(!id) { var c = this; //FIXME: this forces a closure scope Torus.io.getPrivateId(users, function(id) {return c.open_private(users, callback, id);}); return; }	var pm = Torus.open(id * 1, this, users); if(typeof callback == 'function') {callback.call(pm, new Torus.classes.ChatEvent('open', pm));} } Torus.classes.Chat.prototype.event_initial = function(data) { var event = new Torus.classes.IOEvent('initial', this); this.users = 0; this.userlist = {}; //clear current userlist, this list is 100% accurate and ours might not be	event.users = []; for(var i = 0; i < data.collections.users.models.length; i++) {event.users.push(this.event_updateUser(data.collections.users.models[i]));} event.messages = []; for(var i = 0; i < data.collections.chats.models.length; i++) {event.messages.push(this['event_chat:add'](data.collections.chats.models[i]));} if(this.parent) { event.parent = this.parent; }	//this.awayTimeout = setTimeout('Torus.io.setStatus(' + Torus.ui.active + ', \'away\', \'\'); Torus.chats[' + Torus.ui.active + '].autoAway = true;', 5 * 60 * 1000); return event; } Torus.classes.Chat.prototype['event_chat:add'] = function(data) { var event = new Torus.classes.IOEvent('chat:add', this); if(!data.attrs.isInlineAlert) { if(data.attrs.text.indexOf('/me') == 0) { event.event = 'me'; event.text = data.attrs.text.substring(3).trim; }		else { event.event = 'message'; event.text = data.attrs.text; }		event.user = data.attrs.name; event.id = data.attrs.timeStamp; event.time = data.attrs.timeStamp; }	else if(data.attrs.wfMsg) { switch(data.attrs.wfMsg) { case 'chat-inlinealert-a-made-b-chatmod': event.event = 'mod'; event.performer = data.attrs.msgParams[0]; event.target = data.attrs.msgParams[1]; break; case 'chat-err-connected-from-another-browser': //TODO: make this its own event event.event = 'alert'; event.text = 'You are connected to ' + this.name + ' from another window.'; break; case 'chat-kick-cant-kick-moderator': //TODO: figure out who we tried to kick event.event = 'alert'; event.text = 'Can\'t kick moderators.'; break; default: console.log(event); break; }	}	else { event.event = 'alert'; event.text = data.attrs.text; }	return event; } Torus.classes.Chat.prototype.event_join = function(data) { if(this.userlist[data.attrs.name]) {var rejoin = true;} else {var rejoin = false;} var event = this.event_updateUser(data); if(rejoin) {event.event = 'rejoin';} else {event.event = 'join';} return event; } Torus.classes.Chat.prototype.event_updateUser = function(data) { var event = new Torus.classes.IOEvent('update_user', this); event.user = data.attrs.name; event.data = { avatar: data.attrs.avatarSrc.replace('28px', '100px'), mod: data.attrs.isModerator, staff: data.attrs.isStaff, givemod: data.attrs.isCanGiveChatMod, status_state: data.attrs.statusState, status_message: data.attrs.statusMessage, edits: data.attrs.editCount, };	if(event.data.status_state.indexOf('CTCP|') == 0) { var split = event.data.status_state.split('|'); var target = split[1].trim; var proto = split[2].trim.toLowerCase; if(event.user == wgUserName || target == wgUserName || target === '') { event.event = 'ctcp'; event.target = target; event.proto = proto; event.data = event.data.status_message; if(event.user != wgUserName && proto == 'version' && event.data == '') {this.ctcp(event.user, 'version', 'Torus/' + Torus.version);} return event; }	}	if(!this.userlist[data.attrs.name]) { this.users++; this.userlist[data.attrs.name] = event.data; }	else { for(var i in event.data) {this.userlist[data.attrs.name][i] = event.data[i];} }	return event; } Torus.classes.Chat.prototype.event_part = function(data) { var event = new Torus.classes.IOEvent('part', this); event.user = data.attrs.name; if(this.userlist[data.attrs.name]) { this.users--; delete this.userlist[data.attrs.name]; }	else {event.event = 'ghost';} //ghost part return event; } Torus.classes.Chat.prototype.event_logout = function(data) { var event = this.event_part(data); event.event = 'logout'; return event; } Torus.classes.Chat.prototype.event_ban = function(data) { var event = this.event_kick(data); if(data.attrs.time == 0) {event.event = 'unban';} else { event.event = 'ban'; event.seconds = data.attrs.time; event.expiry = Torus.util.seconds_to_expiry(data.attrs.time); }	return event; } Torus.classes.Chat.prototype.event_kick = function(data) { var event = new Torus.classes.IOEvent('kick', this); event.target = data.attrs.kickedUserName; event.performer = data.attrs.moderatorName; return event; } Torus.classes.Chat.prototype.event_openPrivateRoom = function(data) { var event = new Torus.classes.IOEvent('open_private', this); var blocked = false; for(var i = 0; i < data.attrs.users.length; i++) { if(Torus.data.blocked.indexOf(data.attrs.users[i]) != -1) {blocked = true; break;} }	if(blocked) {event.private = false;} else {event.private = Torus.open(data.attrs.roomId * 1, this, data.attrs.users);} event.users = data.attrs.users; return event; } Torus.classes.Chat.prototype.event_forceReconnect = function(data) { this.reconnect; return new Torus.classes.IOEvent('force_reconnect', this); } Torus.classes.Chat.prototype.event_disableReconnect = function(data) { var event = new Torus.classes.IOEvent('force_disconnect', this); Torus.call_listeners(event); return event; } Torus.classes.Chat.prototype.add_listener = Torus.add_listener; Torus.classes.Chat.prototype.remove_listener = Torus.remove_listener; Torus.classes.Chat.prototype.call_listeners = Torus.call_listeners; Torus.classes.Event = function(type, event, room) { //this is really just so you can this instanceof Torus.classes.Event if(!(this instanceof Torus.classes.Event)) {throw new Error('Must call Torus.classes.Event with `new`.');} this.type = type; this.event = event; if(room !== undefined) {this.room = room;} } Torus.classes.WindowEvent = function(event) { if(!(this instanceof Torus.classes.WindowEvent)) {throw new Error('Must call Torus.classes.WindowEvent with `new`.');} Torus.classes.Event.call(this, 'window', event); } Torus.classes.WindowEvent.prototype = Object.create(Torus.classes.Event.prototype); Torus.classes.ChatEvent = function(event, room) { if(!(this instanceof Torus.classes.ChatEvent)) {throw new Error('Must call Torus.classes.ChatEvent with `new`.');} Torus.classes.Event.call(this, 'chat', event, room); } Torus.classes.ChatEvent.prototype = Object.create(Torus.classes.Event.prototype); Torus.classes.IOEvent = function(event, room) { if(!(this instanceof Torus.classes.IOEvent)) {throw new Error('Must call Torus.classes.IOEvent with `new`.');} Torus.classes.Event.call(this, 'io', event, room); var t = (new Date).getTime; this.id = t;	this.time = t; } Torus.classes.IOEvent.prototype = Object.create(Torus.classes.Event.prototype); Torus.ext = {}; Torus.classes.Extension = function(name, id) { if(!(this instanceof Torus.classes.Extension)) {throw new Error('Must call Torus.classes.Extension with `new`.');} if(!name) {throw new Error('Extensions must be named.');} if(Torus.ext[name]) {throw new Error('Tried to register new extension `' + name + '` but it already exists.');} Torus.ext[name] = this; this.id = id; this.name = name; this.listeners = { chat: {}, ext: {} };	Torus.call_listeners(new Torus.classes.ExtEvent('new', this)); } Torus.classes.Extension.prototype.add_listener = Torus.add_listener; Torus.classes.Extension.prototype.remove_listener = Torus.remove_listener; Torus.classes.Extension.prototype.call_listeners = Torus.call_listeners; Torus.classes.ExtEvent = function(event, ext) { if(!(this instanceof Torus.classes.ExtEvent)) {throw new Error('Must call Torus.classes.ExtEvent with `new`.');} Torus.classes.Event.call(this, 'ext', event, ext); } Torus.classes.ExtEvent.prototype = Object.create(Torus.classes.Event.prototype); Torus.util = {}; Torus.util.debug = function {console.log.apply(console, arguments);} Torus.util.null = function {} Torus.util.compare_strings = function(str1, str2) { for(var i = 0; i < str1.length && i < str2.length; i++) { if(str1.charAt(i) == str2.charAt(i)) {continue;} else {return str1.charCodeAt(i) - str2.charCodeAt(i);} }	return str1.length - str2.length; } Torus.util.cap = function(str) {return str.charAt(0).toUpperCase + str.substring(1);} Torus.util.timestamp = function(time) { var date = new Date; if(time) {date.setTime(time);} date.setUTCHours(date.getUTCHours + Torus.options['messages-general-timezone']); var hours = date.getUTCHours; if(hours < 10) {hours = '0' + hours;} var minutes = date.getUTCMinutes; if(minutes < 10) {minutes = '0' + minutes;} var seconds = date.getUTCSeconds; if(seconds < 10) {seconds = '0' + seconds;} return hours + ':' + minutes + ':' + seconds; } Torus.util.expiry_to_seconds = function(expiry) { if(!expiry) {throw new Error('Not enough parameters. (util.expiry_to_seconds)');} expiry = expiry.trim; if(expiry == 'infinite' || expiry == 'indefinite') {return 60 * 60 * 24 * 365 * 1000;} //the server recognizes 1000 years as infinite if(expiry == 'unban' || expiry == 'undo') {return 0;} var split = expiry.split(','); for(var i = 0; i < split.length; i++) { var ex = split[i].trim; var quant = ex.substring(0, ex.indexOf(' ')); var unit = ex.substring(ex.indexOf(' ') + 1); if(quant == 'a' || quant == 'an') {quant = 1;} else if(isNaN(quant * 1)) {return false;} if(unit.charAt(unit.length - 1) == 's') {unit = unit.substring(0, unit.length - 1);} switch(unit) { case 'second': return quant * 1; case 'minute': return quant * 60; case 'hour': return quant * 60 * 60; case 'day': return quant * 60 * 60 * 24; case 'week': return quant * 60 * 60 * 24 * 7; case 'month': return quant * 60 * 60 * 24 * 30; case 'year': return quant * 60 * 60 * 24 * 365; }	} } Torus.util.seconds_to_expiry = function(seconds) { if(!seconds && seconds !== 0) {throw new Error('Not enough parameters. (util.seconds_to_expiry)');} if(seconds == 60 * 60 * 24 * 365 * 1000 || seconds == Infinity) {return 'infinite';} var time = [60 * 60 * 24 * 365, 60 * 60 * 24 * 30 , 60 * 60 * 24 * 7 , 60 * 60 * 24 , 60 * 60 ,   60    ,     1   ]; var unit = [    'year'       ,      'month'      ,      'week'      ,    'day'     , 'hour'  , 'minute' , 'second']; var str = ''; for(var i = 0; i < time.length; i++) { //long division is fun var num = Math.floor(seconds / time[i]); if(num > 0) { if(num == 1) {str += '1 ' + unit[i] + ', ';} else {str += num + ' ' + unit[i] + 's, ';} seconds -= num * time[i]; }	}	return str.substring(0, str.length - 2); } Torus.util.parse_regex = function(regex) { if(!regex || regex.charAt(0) != '/') {return false;} var pattern = regex.substring(1, regex.lastIndexOf('/')); var mode = regex.substring(regex.lastIndexOf('/') + 1); try {return new RegExp(pattern, mode);} catch(err) {return false;} } Torus.util.int_to_stupid = function(num) { //i still cannot believe they thought this was a good idea var b_stupid = ''; //build backwards for(num; num != 0; num = Math.floor(num / 10)) {b_stupid += String.fromCharCode(num % 10);} var stupid = ''; for(var i = b_stupid.length - 1; i >= 0; i--) {stupid += b_stupid.charAt(i);} //reverse return stupid; } Torus.util.stupid_to_int = function(stupid) { var num = 0; for(var i = 0; i < stupid.length; i++) {num += stupid.charCodeAt(stupid.length - i - 1) * Math.pow(10, i);} return num; } Torus.util.utf8ify = function(str) { str = encodeURIComponent(str); for(var i = str.indexOf('%'); i != -1; i = str.indexOf('%', i + 1)) { str = str.substring(0, i) + String.fromCharCode(parseInt(str.substring(i + 1, i + 3), 16)) + str.substring(i + 3); }	return str; } Torus.util.load_js = function(url) { var js = document.createElement('script'); js.className = 'torus-js'; js.src = url; js.type = 'text/javascript'; document.head.appendChild(js); return js; } Torus.util.load_css = function(url) { var css = document.createElement('link'); css.className = 'torus-css'; css.href = url; css.rel = 'stylesheet'; css.type = 'text/css'; css.media = 'screen'; document.head.appendChild(css); return css; } Torus.cache = { //auto generated, see client: https://github.com/Monchoman45/Torus/blob/master/db.py	//               and server: https://github.com/Monchoman45/Torus/blob/master/torus.php cid: 1428397359, data: { 'callofduty': {"_cid": 1428397359, "server": 6, "port": "80", "host": "chat.wikia-services.com", "room": 83}, 'camphalfbloodroleplay': {"_cid": 1428397359, "server": 11, "port": "80", "host": "chat.wikia-services.com", "room": 154}, 'c': {"_cid": 1428397359, "server": 18, "port": "80", "host": "chat.wikia-services.com", "room": 26}, 'clubpenguin': {"_cid": 1428397359, "server": 16, "port": "80", "host": "chat.wikia-services.com", "room": 56}, 'cod': {"_cid": 1428397359, "server": 6, "port": "80", "host": "chat.wikia-services.com", "room": 83}, 'community': {"_cid": 1428397359, "server": 18, "port": "80", "host": "chat.wikia-services.com", "room": 26}, 'elderscrolls': {"_cid": 1428397359, "server": 7, "port": "80", "host": "chat.wikia-services.com", "room": 160}, 'gta': {"_cid": 1428397359, "server": 2, "port": "80", "host": "chat.wikia-services.com", "room": 1458}, 'mlp': {"_cid": 1428397359, "server": 9, "port": "80", "host": "chat.wikia-services.com", "room": 108}, 'monchbox': {"_cid": 1428397359, "server": 2, "port": "80", "host": "chat.wikia-services.com", "room": 753}, 'runescape': {"_cid": 1428397359, "server": 5, "port": "80", "host": "chat.wikia-services.com", "room": 144}, 'sactage': {"_cid": 1428397359, "server": 15, "port": "80", "host": "chat.wikia-services.com", "room": 5146}, 'tes': {"_cid": 1428397359, "server": 7, "port": "80", "host": "chat.wikia-services.com", "room": 160}, 'thehungergames': {"_cid": 1428397359, "server": 12, "port": "80", "host": "chat.wikia-services.com", "room": 39}, }, }; Torus.cache.save = function { window.localStorage.setItem('torus-cache', JSON.stringify(Torus.cache)); } Torus.cache.load = function { var cache = window.localStorage.getItem('torus-cache'); if(!cache) {return;} cache = JSON.parse(cache); if(cache.cid < Torus.cache.cid) { window.localStorage.removeItem('torus-cache'); return; }	for(var i in cache) { if(i == 'data') { for(var j in cache[i]) {Torus.cache[i][j] = cache[i][j];} }		else {Torus.cache[i] = cache[i];} } } Torus.cache.update = function(domain, entry) { if(entry._cid > Torus.cache.cid) { for(var i in Torus.cache.data) {delete Torus.cache.data[i];} //TODO: some kind of "clear your browser cache" message }	Torus.cache.data[domain] = entry; Torus.cache.save; } Torus.add_listener('window', 'load', Torus.cache.load); Torus.add_listener('window', 'unload', Torus.cache.save); new Torus.classes.Chat(0); new Torus.classes.Extension('ui', -1); Torus.ui = { window: document.createElement('div'), ids: {}, active: Torus.chats[0], viewing: [], popup_timeout: 0, } Torus.listeners.ui = { render: [], activate: [], deactivate: [], show: [], unshow: [], render_popup: [], unrender_popup: [], ping: [], fullscreen: [], }; Torus.logs = { messages: {}, plain: {}, socket: {}, }; Torus.ui.fullscreen = function { //FIXME: move if(Torus.data.fullscreen) { document.body.removeChild(Torus.ui.window); Torus.data.old_parent.appendChild(Torus.ui.window); Torus.data.old_parent = null; Torus.ui.window.classList.remove('fullscreen'); Torus.data.fullscreen = false; Torus.call_listeners(new Torus.classes.UIEvent('fullscreen')); //FIXME: unfullscreen }	else { Torus.data.old_parent = Torus.ui.window.parentNode; Torus.ui.window.parentNode.removeChild(Torus.ui.window); document.body.appendChild(Torus.ui.window); Torus.ui.window.classList.add('fullscreen'); Torus.data.fullscreen = true; Torus.call_listeners(new Torus.classes.UIEvent('fullscreen')); } } Torus.ui.onload = function { var domain = window.location.hostname.substring(0, document.location.hostname.indexOf('.wikia.com')); if(domain.indexOf('preview.') == 0) {domain = domain.substring(8);} if(!domain) {domain = 'localhost';} Torus.local = domain; if(wgCanonicalNamespace == 'Special' && wgTitle == 'Torus') { document.title = Torus.i18n.text('title', wgSiteName); if(window.skin == 'oasis') { var body = 'WikiaArticle'; if(document.getElementById('WikiaPageHeader')) { document.getElementById('WikiaPageHeader').getElementsByTagName('h1')[0].innerHTML = 'Torus'; document.getElementById('WikiaPageHeader').getElementsByTagName('h2')[0].innerHTML = 'It\'s a donut'; //FIXME: i18n }		}		else { var body = 'bodyContent'; document.getElementById('firstHeading').innerHTML = 'Torus'; }		document.getElementById(body).innerHTML = (document.getElementById('AdminDashboardHeader') ? ' Torus ' : ''); document.getElementById(body).appendChild(Torus.ui.window); if(wgUserName == null) { Torus.alert(Torus.i18n.text('error-login')); return; }		if(Torus.options['misc-connection-local']) {Torus.open(Torus.local);} if(Torus.options['misc-connection-default_rooms']) { var rooms = Torus.options['misc-connection-default_rooms'].split('\n'); for(var i = 0; i < rooms.length; i++) { if(!Torus.chats[rooms[i]]) {Torus.open(rooms[i]);} //could be Torus.local }		}	} } Torus.classes.UIEvent = function(event, room) { if(!(this instanceof Torus.classes.UIEvent)) {throw new Error('Must call Torus.classes.UIEvent with `new`.');} Torus.classes.Event.call(this, 'ui', event, room); } Torus.classes.UIEvent.prototype = Object.create(Torus.classes.Event.prototype); Torus.ui.new_extension = function(event) { event.room.listeners.ui = {}; } Torus.add_listener('ext', 'new', Torus.ui.new_extension); Torus.ui.activate = function(room) { if(Torus.ui.active.id >= 0) {Torus.ui.ids['tab-' + Torus.ui.active.domain].classList.remove('torus-tab-active');} else {Torus.ui.ids['tab--1'].classList.remove('torus-tab-active');} if(Torus.ui.active.id >= 0) {Torus.ui.active.last_viewed = (new Date).getTime;} for(var i = 0; i < Torus.ui.viewing.length; i++) {Torus.ui.viewing[i].last_viewed = (new Date).getTime;} Torus.util.empty(Torus.ui.ids['info']); var event = new Torus.classes.UIEvent('deactivate', Torus.ui.active); event.old_window = Torus.util.empty(Torus.ui.ids['window']); event.old_sidebar = Torus.util.empty(Torus.ui.ids['sidebar']); Torus.call_listeners(event); Torus.ui.active = room; if(room.id >= 0) { Torus.ui.ids['tab-' + room.domain].classList.add('torus-tab-active'); Torus.ui.ids['tab-' + room.domain].classList.remove('torus-tab-ping'); Torus.ui.ids['tab-' + room.domain].classList.remove('torus-tab-message'); Torus.ui.ids['tab-' + room.domain].classList.remove('torus-tab-alert'); }	else {Torus.ui.ids['tab--1'].classList.add('torus-tab-active');} if(room.id > 0) { //chat if(!room.parent) { var link = document.createElement('a'); link.href = 'http://' + room.domain + '.wikia.com/wiki/'; link.textContent = room.domain; link.addEventListener('click', Torus.ui.click_link); Torus.ui.ids['info'].appendChild(Torus.i18n.html('info-public', link)); }		else { var link = document.createElement('a'); link.href = 'http://' + room.parent.domain + '.wikia.com/wiki/'; link.textContent = room.parent.domain; link.addEventListener('click', Torus.ui.click_link); Torus.ui.ids['info'].appendChild(Torus.i18n.html('info-private', link, document.createTextNode(room.priv_users.slice(0, room.priv_users.length - 1).join(', ') + ' ' + Torus.i18n.text('and') + ' ' + room.priv_users[room.priv_users.length - 1]))); }	}	else { //extension if(room.id == 0 || room.id == -1) { //status and menu var link = document.createElement('a'); link.href = 'http://' + Torus.local + '.wikia.com/wiki/'; link.textContent = Torus.local; link.addEventListener('click', Torus.ui.click_link); Torus.ui.ids['info'].appendChild(Torus.i18n.html('info-menu', document.createTextNode(Torus.pretty_version), link)); }		else { var link = document.createElement('a'); link.className = 'torus-fakelink'; link.textContent = '--- ' + Torus.i18n.text('info-menu-back') + ' ---'; link.addEventListener('click', Torus.ui.menu.tab_click); Torus.ui.ids['info'].appendChild(link); }	}	if(room.id >= 0) {Torus.ui.render(Torus.ui.ids['window']);} Torus.call_listeners(new Torus.classes.UIEvent('activate', room)); } Torus.ui.show = function(room) { if(room.id < 0) {throw new Error('Invalid room ' + room.domain + '. (ui.show)');} if(Torus.ui.active.id >= 0) {Torus.ui.active.last_viewed = (new Date).getTime;} for(var i = 0; i < Torus.ui.viewing.length; i++) {Torus.ui.viewing[i].last_viewed = (new Date).getTime;} if(room.viewing) { //unshow room.viewing = false; for(var i = 0; i < Torus.ui.viewing.length; i++) { if(Torus.ui.viewing[i] == room) {Torus.ui.viewing.splice(i, 1);} }		var tab = Torus.ui.ids['tab-' + room.domain]; tab.classList.remove('torus-tab-viewing'); Torus.util.empty(Torus.ui.ids['window']); Torus.ui.render(Torus.ui.ids['window']); Torus.call_listeners(new Torus.classes.UIEvent('unshow', room)); }	else { //show room.viewing = true; Torus.ui.viewing.push(room); Torus.ui.ids['tab-' + room.domain].classList.add('torus-tab-viewing'); Torus.util.empty(Torus.ui.ids['window']); Torus.ui.render(Torus.ui.ids['window']); Torus.call_listeners(new Torus.classes.UIEvent('show', room)); } } Torus.ui.render = function(el) { if(!el) {el = Torus.ui.ids['window'];} var rooms = []; var indexes = []; var active = false; if(Torus.ui.active != Torus.chats[0]) { for(var i = 0; i < Torus.ui.viewing.length; i++) { if(Torus.ui.viewing[i] == Torus.ui.active) {active = true;} if(Torus.logs.messages[Torus.ui.viewing[i].domain].length > 0) { rooms.push(Torus.logs.messages[Torus.ui.viewing[i].domain]); indexes.push(Torus.logs.messages[Torus.ui.viewing[i].domain].length - 1); }		}	}	if(!active && Torus.logs.messages[Torus.ui.active.domain].length > 0) { rooms.push(Torus.logs.messages[Torus.ui.active.domain]); indexes.push(Torus.logs.messages[Torus.ui.active.domain].length - 1); }	var bar = false; var frag = document.createDocumentFragment; //yo these things are so cool for(var i = 0; i < Torus.options['messages-general-max'] && rooms.length > 0; i++) { var message = rooms[0][indexes[0]]; var source = 0; for(var j = 1; j < rooms.length; j++) { if(rooms[j][indexes[j]].id > message.id) { message = rooms[j][indexes[j]]; source = j;			} }		indexes[source]--; if(indexes[source] == -1) { //no more messages rooms.splice(source, 1); indexes.splice(source, 1); }		if(!bar && message.id < Torus.ui.active.last_viewed) { var hr = document.createElement('hr'); hr.className = 'torus-message-separator'; if(frag.children.length == 0) {frag.appendChild(hr);} else {frag.insertBefore(hr, frag.firstChild);} bar = true; }		if(frag.children.length == 0) {frag.appendChild(Torus.ui.render_line(message));} else {frag.insertBefore(Torus.ui.render_line(message), frag.firstChild);} }	el.appendChild(frag); //rerender userlist //FIXME: now that this is a generalized function, should we still do this or move it somewhere else? if(Torus.ui.active.id > 0) { //FIXME: this is really hacky var e = {room: Torus.ui.active}; for(var i in Torus.ui.active.userlist) { e.user = i;			Torus.ui.update_user(e); }	}	el.scrollTop = el.scrollHeight; var event = new Torus.classes.UIEvent('render'); event.target = el; Torus.call_listeners(event); } Torus.ui.render_line = function(message) { if(message.type != 'io') {throw new Error('Torus.ui.render_line: Event type must be `io`.');} var line = document.createElement('div'); line.className = 'torus-message torus-room-' + message.room.domain; if(message.room != Torus.ui.active) {line.classList.add('torus-message-inactive');} var time = document.createElement('span'); time.className = 'torus-message-timestamp'; time.textContent = '[' + Torus.util.timestamp(message.time) + ']'; line.appendChild(time); var viewing = Torus.ui.viewing.length; if(Torus.ui.viewing.indexOf(Torus.chats[0]) != -1) {viewing--;} if(Torus.ui.viewing.indexOf(Torus.ui.active) != -1) {viewing--;} if(viewing > 0) { var max = message.room.name.length; for(var i = 0; i < Torus.ui.viewing.length; i++) { if(max < Torus.ui.viewing[i].name.length) {max = Torus.ui.viewing[i].name.length;} }		if(max < Torus.ui.active.name.length) {max = Torus.ui.active.name.length;} max -= message.room.name.length; var indent = ''; for(var i = 0; i < max; i++) {indent += ' ';} line.appendChild(document.createTextNode(' ')); var room = document.createElement('span'); room.className = 'torus-message-room'; room.textContent = '{' + message.room.name + '}'; line.appendChild(room); var span = document.createElement('span'); span.className = 'torus-whitespace'; span.textContent = indent; line.appendChild(indent); }	line.appendChild(document.createTextNode(' ')); switch(message.event) { case 'me': case 'message': if(message.ping) {line.classList.add('torus-message-ping');} if(message.event == 'message') { var span = document.createElement('span'); //this is arguably one of the dumber things i've ever done span.className = 'torus-whitespace'; //it works though span.textContent = ' '; //#yolo line.appendChild(span); line.appendChild(document.createTextNode('<')); line.appendChild(Torus.ui.span_user(message.user)); line.appendChild(document.createTextNode('> ')); }			else { line.appendChild(document.createTextNode('*')); var span = document.createElement('span'); //this is arguably one of the dumber things i've ever done span.className = 'torus-whitespace'; //it works though span.textContent = ' '; //#yolo line.appendChild(span); line.appendChild(Torus.ui.span_user(message.user)); line.appendChild(document.createTextNode(' ')); }			line.appendChild(message.html); break; case 'alert': line.appendChild(document.createTextNode('== ')); line.appendChild(message.html); break; case 'join': case 'rejoin': case 'ghost': case 'part': line.appendChild(document.createTextNode('== ')); line.appendChild(Torus.i18n.html('message-' + message.event, Torus.ui.span_user(message.user), document.createTextNode('{' + message.room.name + '}'))); break; case 'logout': line.appendChild(document.createTextNode('== ')); line.appendChild(Torus.i18n.html('message-logout', Torus.ui.span_user(message.user))); break; case 'ctcp': if(message.user == wgUserName) {line.appendChild(document.createTextNode(' >'));} else {line.appendChild(document.createTextNode(' <'));} var span = document.createElement('span'); //this is arguably one of the dumber things i've ever done span.className = 'torus-whitespace'; //it works though span.textContent = ' '; //#yolo line.appendChild(span); line.appendChild(Torus.ui.span_user(message.user)); if(!message.data) {line.appendChild(document.createTextNode(' CTCP|' + message.target + '|' + message.proto));} else {line.appendChild(document.createTextNode(' CTCP|' + message.target + '|' + message.proto + ': ' + message.data));} break; case 'mod': line.appendChild(document.createTextNode('== ')); line.appendChild(Torus.i18n.html('message-mod', Torus.ui.span_user(message.performer), Torus.ui.span_user(message.target), document.createTextNode('{' + message.room.name + '}'))); break; case 'kick': case 'ban': case 'unban': if(message.room.parent) {var domain = message.room.parent.domain;} else {var domain = message.room.domain;} var frag = document.createDocumentFragment; frag.appendChild(Torus.ui.span_user(message.target)); frag.appendChild(document.createTextNode(' ('));			var talk = document.createElement('a');				talk.href = 'http://' + domain + '.wikia.com/wiki/User_talk:' + message.target;				talk.textContent = Torus.i18n.text('message-banlinks-talk');				talk.addEventListener('click', Torus.ui.click_link);			frag.appendChild(talk);			frag.appendChild(document.createTextNode('|'));			var contribs = document.createElement('a');				contribs.href = 'http://' + domain + '.wikia.com/wiki/Special:Contributions/' + message.target;				contribs.textContent = Torus.i18n.text('message-banlinks-contribs');				contribs.addEventListener('click', Torus.ui.click_link);			frag.appendChild(contribs);			frag.appendChild(document.createTextNode('|'));			var ban = document.createElement('a');				ban.href = 'http://' + domain + '.wikia.com/wiki/Special:Log/chatban?page=User:' + message.target;				ban.textContent = Torus.i18n.text('message-banlinks-history'); ban.addEventListener('click', Torus.ui.click_link); frag.appendChild(ban); if(message.room.checkuser) { frag.appendChild(document.createTextNode('|')); var ccon = document.createElement('a'); ccon.href = 'http://' + domain + '.wikia.com/wiki/Special:Log/chatconnect?user=' + message.target; ccon.textContent = Torus.i18n.text('message-banlinks-chatconnect'); ccon.addEventListener('click', Torus.ui.click_link); frag.appendChild(ccon); }			frag.appendChild(document.createTextNode(')'));			line.appendChild(document.createTextNode('== '));			line.appendChild(Torus.i18n.html('message-' + message.event, Torus.ui.span_user(message.performer), frag, document.createTextNode('{' + message.room.name + '}'), document.createTextNode(message.expiry)));			break;		default: throw new Error('Message type ' + message.event + ' is not rendered. (ui.render_line)');	}	return line; } Torus.ui.render_popup = function(name, room, coords) {	var target = room.userlist[name];	var user = room.userlist[wgUserName];	var domain = room.domain;	if(room.parent) {domain = room.parent.domain;}	Torus.util.empty(Torus.ui.ids['popup']);	var avatar = document.createElement('img');	avatar.id = 'torus-popup-avatar';	avatar.src = target.avatar;	Torus.ui.ids['popup'].appendChild(avatar);	var info = document.createElement('div');	info.id = 'torus-popup-info';	Torus.ui.ids['popup-info'] = info;	var div = document.createElement('div');		var info_name = document.createElement('span');		info_name.id = 'torus-popup-name';		Torus.ui.ids['popup-name'] = info_name;		var userpage = document.createElement('a');			userpage.href = 'http://' + domain + '.wikia.com/wiki/User:' + encodeURIComponent(name);			userpage.textContent = name;			userpage.addEventListener('click', Torus.ui.click_link); info_name.appendChild(userpage); div.appendChild(info_name); if(target.mod || target.staff) { //star div.appendChild(document.createTextNode(' ')); var info_access = document.createElement('span'); info_access.id = 'torus-popup-access'; Torus.ui.ids['popup-access'] = info_access; var icon = document.createElement('img'); if(target.staff) { icon.className = 'torus-user-icon-staff'; icon.src = 'http://images2.wikia.nocookie.net/monchbox/images/f/f3/Icon-staff.png'; }			else { //target.mod icon.className = 'torus-user-icon-mod'; icon.src = 'http://images2.wikia.nocookie.net/monchbox/images/6/6b/Icon-chatmod.png'; }			info_access.appendChild(icon); if(!target.staff && target.givemod) {info_access.appendChild(document.createTextNode('+'));} div.appendChild(info_access); }		info.appendChild(div); var state = document.createElement('div'); state.id = 'torus-popup-status-state'; Torus.ui.ids['popup-status-state'] = state; state.textContent = target.status_state; info.appendChild(state); var message = document.createElement('div'); message.id = 'torus-popup-status-message'; Torus.ui.ids['popup-status-message'] = message; message.textContent = target.status_message; info.appendChild(message); Torus.ui.ids['popup'].appendChild(info); var userlinks = document.createElement('div'); userlinks.id = 'torus-popup-userlinks'; Torus.ui.ids['popup-userlinks'] = userlinks; var div = document.createElement('div'); var talk = document.createElement('a'); talk.className = 'torus-popup-userlink'; talk.href = 'http://' + domain + '.wikia.com/wiki/User_talk:' + encodeURIComponent(name); talk.addEventListener('click', Torus.ui.click_link); talk.textContent = Torus.i18n.text('popup-talk'); div.appendChild(talk); var contribs = document.createElement('a'); contribs.className = 'torus-popup-userlink'; contribs.href = 'http://' + domain + '.wikia.com/wiki/Special:Contributions/' + encodeURIComponent(name); contribs.addEventListener('click', Torus.ui.click_link); contribs.textContent = Torus.i18n.text('popup-contribs'); div.appendChild(contribs); userlinks.appendChild(div); div = document.createElement('div'); var chatban = document.createElement('a'); chatban.className = 'torus-popup-userlink'; chatban.href = 'http://' + domain + '.wikia.com/wiki/Special:Log/chatban?page=User:' + encodeURIComponent(name); chatban.addEventListener('click', Torus.ui.click_link); chatban.textContent = Torus.i18n.text('popup-history'); div.appendChild(chatban); var chatconnect = document.createElement('a'); chatconnect.className = 'torus-popup-userlink'; if(room.checkuser) { chatconnect.href = 'http://' + domain + '.wikia.com/wiki/Special:Log/chatconnect?user=' + encodeURIComponent(name); chatconnect.addEventListener('click', Torus.ui.click_link); }		else {chatconnect.classList.add('torus-popup-userlink-disabled');} chatconnect.textContent = Torus.i18n.text('popup-chatconnect'); div.appendChild(chatconnect); userlinks.appendChild(div); Torus.ui.ids['popup'].appendChild(userlinks); var actions = document.createElement('div'); actions.id = 'torus-popup-actions'; Torus.ui.ids['popup-actions'] = actions; var priv = document.createElement('a'); if(Torus.data.blockedBy.indexOf(name) != -1) {priv.className = 'torus-popup-action-disabled';} else { priv.className = 'torus-popup-action'; priv.setAttribute('data-user', name); priv.addEventListener('click', function {					Torus.ui.active.open_private([this.getAttribute('data-user')], function(event) {Torus.ui.activate(event.room);});				}); }			priv.textContent = Torus.i18n.text('popup-pm'); actions.appendChild(priv); var block = document.createElement('a'); block.className = 'torus-popup-action'; block.setAttribute('data-user', name); if(Torus.data.blocked.indexOf(name) != -1) { block.addEventListener('click', Torus.ui.popup_unblock); block.textContent = Torus.i18n.text('popup-unblock'); }			else { block.addEventListener('click', Torus.ui.popup_block); block.textContent = Torus.i18n.text('popup-block'); }		actions.appendChild(block); if((user.givemod || user.staff) && !target.mod && !target.staff) { var mod = document.createElement('a'); mod.className = 'torus-popup-action'; mod.addEventListener('click', function {this.children[0].style.display = 'block';}); var confirm = document.createElement('div'); confirm.id = 'torus-popup-modconfirm'; Torus.ui.ids['popup-modconfirm'] = confirm; var yes = document.createElement('input'); yes.id = 'torus-popup-modconfirm-yes'; Torus.ui.ids['popup-modconfirm-yes'] = yes; yes.type = 'button'; yes.value = Torus.util.cap(Torus.i18n.text('yes')); yes.addEventListener('click', function(event) {					event.stopPropagation;					this.parentNode.style.display = 'none';					Torus.ui.active.mod(this.getAttribute('data-user'));				}); yes.setAttribute('data-user', name); confirm.appendChild(yes); confirm.appendChild(document.createTextNode(' ' + Torus.i18n.text('popup-mod-areyousure') + ' ')); var no = document.createElement('input'); no.id = 'torus-popup-modconfirm-no'; Torus.ui.ids['popup-modconfirm-no'] = no; no.type = 'button'; no.value = Torus.util.cap(Torus.i18n.text('no')); no.addEventListener('click', function(event) {					event.stopPropagation;					this.parentNode.style.display = 'none';				}); confirm.appendChild(no); mod.appendChild(confirm); mod.appendChild(document.createTextNode(Torus.i18n.text('popup-mod'))); actions.appendChild(mod); }		else { var mod = document.createElement('a'); mod.className = 'torus-popup-action-disabled'; mod.textContent = Torus.i18n.text('popup-mod'); actions.appendChild(mod); }		if((user.staff || user.givemod || (user.mod && !target.mod)) && !target.staff && !target.givemod) { var kick = document.createElement('a'); kick.className = 'torus-popup-action'; kick.setAttribute('data-user', name); kick.addEventListener('click', function {Torus.ui.active.kick(this.getAttribute('data-user'));}); kick.textContent = Torus.i18n.text('popup-kick'); actions.appendChild(kick); var ban = document.createElement('a'); ban.className = 'torus-popup-action'; var modal = document.createElement('div'); modal.id = 'torus-popup-banmodal'; Torus.ui.ids['popup-banmodal'] = modal; modal.setAttribute('data-user', name); var div = document.createElement('div'); var expiry_label = document.createElement('label'); expiry_label.for = 'torus-popup-banexpiry'; expiry_label.textContent = Torus.i18n.text('popup-ban-expiry') + ':'; div.appendChild(expiry_label); div.appendChild(document.createTextNode(' ')); var expiry = document.createElement('input'); expiry.id = 'torus-popup-banexpiry'; Torus.ui.ids['popup-banexpiry'] = expiry; expiry.type = 'text'; expiry.placeholder = '1 day'; //FIXME: i18n expiry.addEventListener('keyup', function(event) {						if(event.keyCode == 13) {							var target = this.parentNode.parentNode.getAttribute('data-user');							var summary = this.parentNode.nextSibling.lastChild.value;							if(this.value) {var expiry = Torus.util.expiry_to_seconds(this.value);}							else {var expiry = 60 * 60 * 24;}							Torus.ui.active.ban(target, expiry, summary);						}					}); div.appendChild(expiry); modal.appendChild(div); div = document.createElement('div'); var reason_label = document.createElement('label'); reason_label.for = 'torus-popup-banexpiry'; reason_label.textContent = Torus.i18n.text('popup-ban-reason') + ':'; div.appendChild(reason_label); div.appendChild(document.createTextNode(' ')); var reason = document.createElement('input'); reason.id = 'torus-popup-banreason'; Torus.ui.ids['popup-banreason'] = reason; reason.placeholder = 'Misbehaving in chat'; //FIXME: i18n reason.addEventListener('keyup', function(event) {						if(event.keyCode == 13) {							var target = this.parentNode.parentNode.getAttribute('data-user');							var expiry = this.parentNode.previousSibling.lastChild.value;							var summary = this.value;							if(expiry) {expiry = Torus.util.expiry_to_seconds(expiry);}							else {expiry = 60 * 60 * 24;}							Torus.ui.active.ban(target, expiry, summary);						}					}); div.appendChild(reason); modal.appendChild(div); div = document.createElement('div'); var submit = document.createElement('input'); submit.id = 'torus-popup-banbutton'; Torus.ui.ids['popup-banbutton'] = submit; submit.type = 'submit' submit.value = Torus.i18n.text('popup-ban'); submit.addEventListener('click', function(event) {						var target = this.parentNode.parentNode.getAttribute('data-user');						var expiry = this.parentNode.previousSibling.previousSibling.lastChild.value;						var summary = this.parentNode.previousSibling.lastChild.value;						if(expiry) {expiry = Torus.util.expiry_to_seconds(expiry);}						else {expiry = 60 * 60 * 24;}						Torus.ui.active.ban(target, expiry, summary);					}); modal.appendChild(div); ban.appendChild(modal); ban.appendChild(document.createTextNode(Torus.i18n.text('popup-ban'))); actions.appendChild(ban); }		else { var kick = document.createElement('a'); kick.className = 'torus-popup-action-disabled'; kick.textContent = Torus.i18n.text('popup-ban'); actions.appendChild(kick); var ban = document.createElement('a'); ban.className = 'torus-popup-action-disabled'; ban.textContent = Torus.i18n.text('popup-ban'); actions.appendChild(ban); }	Torus.ui.ids['popup'].appendChild(actions); Torus.ui.ids['popup'].style.display = 'block'; if(coords) { Torus.ui.ids['popup'].style.right = 'auto'; Torus.ui.ids['popup'].style.left = coords.x + 'px'; Torus.ui.ids['popup'].style.top = coords.y + 'px'; }	else { var userlist = Torus.ui.ids['sidebar'].children; for(var i = 0; i < userlist.length; i++) { if(userlist[i].lastChild.innerHTML == name) { if(userlist[i].offsetTop - Torus.ui.ids['sidebar'].scrollTop + Torus.ui.ids['popup'].offsetHeight > Torus.ui.window.offsetHeight) {Torus.ui.ids['popup'].style.top = Torus.ui.window.offsetHeight - Torus.ui.ids['popup'].offsetHeight + 'px';} else {Torus.ui.ids['popup'].style.top = userlist[i].offsetTop - Torus.ui.ids['sidebar'].scrollTop + 'px';} break; }		}	}	var event = new Torus.classes.UIEvent('render_popup'); event.user = name; Torus.call_listeners(event); } Torus.ui.unrender_popup = function { Torus.ui.ids['popup'].style.top = ''; Torus.ui.ids['popup'].style.right = ''; Torus.ui.ids['popup'].style.left = ''; Torus.ui.ids['popup'].style.display = 'none'; Torus.util.empty(Torus.ui.ids['popup']); Torus.call_listeners(new Torus.classes.UIEvent('unrender_popup')); } Torus.ui.popup_block = function { this.appendChild(Torus.ui.img_loader); var el = this; Torus.io.block(this.getAttribute('data-user'), function { //FIXME: closure		Torus.util.empty(el);		el.removeEventListener('click', Torus.ui.popup_block);		el.addEventListener('click', Torus.ui.popup_unblock);		el.textContent = Torus.i18n.text('popup-unblock');	}); } Torus.ui.popup_unblock = function { this.appendChild(Torus.ui.img_loader); var el = this; Torus.io.unblock(this.getAttribute('data-user'), function { //FIXME: closure		Torus.util.empty(el);		el.removeEventListener('click', Torus.ui.popup_unblock);		el.addEventListener('click', Torus.ui.popup_block);		el.textContent = Torus.i18n.text('popup-block');	}); } Torus.ui.menu = {}; Torus.ui.menu.render = function { var name = document.createElement('div'); name.id = 'torus-menu-name'; Torus.ui.ids['menu-name'] = name; name.textContent = Torus.i18n.text('menu-torus', Torus.pretty_version); Torus.ui.ids['window'].appendChild(name); var links = document.createElement('div'); links.id = 'torus-menu-links'; Torus.ui.ids['menu-links'] = links; var github = document.createElement('a'); github.href = 'https://github.com/Monchoman45/Torus'; github.textContent = Torus.i18n.text('menu-fork'); github.addEventListener('click', Torus.ui.click_link); links.appendChild(github); links.appendChild(document.createTextNode(' | ')); var report = document.createElement('a'); report.href = 'https://github.com/Monchoman45/Torus/issues/new?labels=bug'; report.textContent = Torus.i18n.text('menu-report'); report.addEventListener('click', Torus.ui.click_link); links.appendChild(report); links.appendChild(document.createTextNode(' | ')); var suggest = document.createElement('a'); suggest.href = 'https://github.com/Monchoman45/Torus/issues/new?labels=feature-request'; suggest.textContent = Torus.i18n.text('suggest'); suggest.addEventListener('click', Torus.ui.click_link); links.appendChild(suggest); links.appendChild(document.createTextNode(' | ')); var doc = document.createElement('a'); doc.href = 'http://monchbox.wikia.com/wiki/Torus'; doc.textContent = Torus.i18n.text('menu-doc'); doc.addEventListener('click', Torus.ui.click_link); links.appendChild(doc); Torus.ui.ids['window'].appendChild(links); var extensions = document.createElement('div'); extensions.id = 'torus-menu-extensions'; Torus.ui.ids['menu-extensions'] = extensions; for(var i in Torus.ext) { if(i == 'ui') {continue;} var ext = document.createElement('a'); ext.id = 'torus-menu-ext-' + i;				Torus.ui.ids['menu-ext-' + i] = ext; ext.setAttribute('data-id', i); if(Torus.ext[i].text) {ext.textContent = Torus.ext[i].text;} else {ext.textContent = i;} ext.addEventListener('click', Torus.ui.menu.click_extension); extensions.appendChild(ext); }	Torus.ui.ids['window'].appendChild(extensions); } Torus.ui.menu.click_extension = function {Torus.ui.activate(Torus.ext[this.getAttribute('data-id')]);} Torus.ui.menu.tab_click = function {Torus.ui.activate(Torus.ext.ui);} //activating the ui extension gives you the menu Torus.ext.ui.add_listener('ui', 'activate', Torus.ui.menu.render); Torus.ext.ui.add_listener('ui', 'deactivate', Torus.util.null); //FIXME: i'm sure something important is supposed to go here Torus.ui.new_room = function(event) { event.room.add_listener('io', 'initial', Torus.ui.initial); event.room.add_listener('io', 'join', Torus.ui.update_user); event.room.add_listener('io', 'update_user', Torus.ui.update_user); event.room.add_listener('io', 'part', Torus.ui.remove_user); event.room.add_listener('io', 'logout', Torus.ui.remove_user); event.room.add_listener('io', 'ghost', Torus.ui.remove_user); event.room.add_listener('io', 'alert', Torus.ui.add_line); event.room.add_listener('io', 'message', Torus.ui.add_line); event.room.add_listener('io', 'me', Torus.ui.add_line); event.room.add_listener('io', 'join', Torus.ui.add_line); event.room.add_listener('io', 'part', Torus.ui.add_line); event.room.add_listener('io', 'logout', Torus.ui.add_line); event.room.add_listener('io', 'ghost', Torus.ui.add_line); event.room.add_listener('io', 'ctcp', Torus.ui.add_line); event.room.add_listener('io', 'mod', Torus.ui.add_line); event.room.add_listener('io', 'kick', Torus.ui.add_line); event.room.add_listener('io', 'ban', Torus.ui.add_line); event.room.add_listener('io', 'unban', Torus.ui.add_line); if(!event.room.parent && !Torus.ui.pings.dir[event.room.domain]) { Torus.ui.pings.dir[event.room.domain] = {}; for(var i in Torus.ui.pings.dir['#global']) {Torus.ui.pings.dir[event.room.domain][i] = Torus.ui.pings.dir['#global'][i];} Torus.ui.pings.dir[event.room.domain].enabled = true; Torus.ui.pings.dir[event.room.domain].literal = []; Torus.ui.pings.dir[event.room.domain].regex = []; Torus.ui.pings.rebuild; }	for(var i in Torus.logs) { if(!Torus.logs[i][event.room.domain]) {Torus.logs[i][event.room.domain] = [];} }	event.room.listeners.ui = {}; event.room.last_viewed = 0; if(isNaN(event.room.domain * 1)) { event.room.checkuser = false; Torus.io.jsonp('http://' + event.room.domain + '.wikia.com/api.php?action=query&list=users&ususers=' + encodeURIComponent(wgUserName) + '&usprop=rights&format=json', function(result) {			var rights = result.query.users[0].rights;			var found = false;			for(var i in rights) {				if(rights[i] == 'checkuser') {found = true; break;}			}			if(found) {				event.room.checkuser = true; //FIXME: closure				if(event.room.domain == Torus.local && !Torus.ext.ccui) {					Torus.util.load_js('http://localhost:8080/wiki/MediaWiki:Torus.js/ext/ccui/main.js?action=raw&ctype=text/javascript');					Torus.util.load_css('http://localhost:8080/wiki/MediaWiki:Torus.js/ext/ccui/main.css?action=raw&ctype=text/css');				}			}		}); } } Torus.ui.add_room = function(event) { Torus.alert(Torus.i18n.text('connecting', '{' + event.room.name + '}')); for(var i = 0; i < Torus.ui.ids['tabs'].children.length; i++) { if(Torus.ui.ids['tabs'].children[i].getAttribute('data-id') == event.room.domain) {return;} }	var tab = document.createElement('span'); tab.id = 'torus-tab-' + event.room.domain; Torus.ui.ids['tab-' + event.room.domain] = tab; tab.setAttribute('data-id', event.room.domain); tab.className = 'torus-tab'; tab.addEventListener('click', Torus.ui.tab_click); tab.textContent = event.room.name; if(event.room.id > 0) { var x = document.createElement('span'); x.className = 'torus-tab-close'; x.addEventListener('click', function(event) {			event.stopPropagation;			Torus.ui.remove_room(Torus.chats[this.parentNode.getAttribute('data-id')]);		}); x.textContent = 'x'; tab.appendChild(x); }	Torus.ui.ids['tabs'].appendChild(tab); if(!event.room.parent) {Torus.ui.activate(event.room);} } Torus.ui.remove_room = function(room) { if(room.connecting || room.connected) {room.disconnect('closed');} if(room == Torus.ui.active) { if(room.parent) {Torus.ui.activate(room.parent);} else {Torus.ui.activate(Torus.chats[0]);} //FIXME: activate the next chat tab to the left }	if(room.viewing) {Torus.ui.show(room);} Torus.ui.ids['tabs'].removeChild(Torus.ui.ids['tab-' + room.domain]); delete Torus.ui.ids['tab-' + room.domain]; } Torus.ui.add_line = function(event) { if(typeof event.text == 'string' && !event.html) {Torus.ui.parse_message(event);} Torus.logs.messages[event.room.domain].push(event); //Torus.logs.plain[event.room.domain].push(event); //TODO: this is supposed to be like just text right? if(event.room == Torus.ui.active || (event.room.viewing && Torus.ui.active.id > 0)) { var scroll = false; if(Torus.ui.ids['window'].offsetHeight + Torus.ui.ids['window'].scrollTop >= Torus.ui.ids['window'].scrollHeight) {scroll = true;} Torus.ui.ids['window'].appendChild(Torus.ui.render_line(event)); if(scroll) {Torus.ui.ids['window'].scrollTop = Torus.ui.ids['window'].scrollHeight;} if(Torus.ui.ids['window'].children.length > Torus.options['messages-general-max']) {Torus.ui.ids['window'].removeChild(Torus.ui.ids['window'].children[0]);} }	else { if(event.event == 'message' || event.event == 'me') { Torus.ui.ids['tab-' + event.room.domain].classList.add('torus-tab-message'); if(event.room.parent) {Torus.ui.ping(event.room);} }		else {Torus.ui.ids['tab-' + event.room.domain].classList.add('torus-tab-alert');} } } Torus.ui.update_user = function(event) { if(event.room != Torus.ui.active) {return;} var props = event.room.userlist[event.user]; var userlist = Torus.ui.ids['sidebar'].getElementsByTagName('li'); var li = false; for(var i = 0; i < userlist.length; i++) { //check if we're adding a new li or modifying an existing one if(userlist[i].className.indexOf('torus-user-' + encodeURIComponent(event.user)) != -1) { var li = userlist[i]; break; }	}	if(!li) { var li = document.createElement('li'); li.setAttribute('data-user', event.user); li.addEventListener('mouseover', function(event) {Torus.ui.render_popup(this.getAttribute('data-user'), Torus.ui.active);}); //FIXME: hardcoded function var sidebar = Torus.ui.ids['sidebar']; var added = false; for(var i = 0; i < sidebar.children.length; i++) { var child = event.room.userlist[sidebar.children[i].getAttribute('data-user')]; if((!props.staff && child.staff) || (!props.mod && child.mod)) {continue;} if((props.staff && !child.staff) || (props.mod && !child.mod) || Torus.util.compare_strings(event.user, sidebar.children[i].getAttribute('data-user')) < 0) { sidebar.insertBefore(li, sidebar.children[i]); added = true; break; }		}		if(!added) {sidebar.appendChild(li);} //is at the end of the alphabet }	while(li.firstChild) {li.removeChild(li.firstChild);} li.className = 'torus-user torus-user-' + encodeURIComponent(event.user); if(props.staff) { li.classList.add('torus-user-staff'); var icon = document.createElement('img'); icon.className = 'torus-user-icon-staff'; icon.src = 'http://images2.wikia.nocookie.net/monchbox/images/f/f3/Icon-staff.png'; li.appendChild(icon); }	else if(props.mod) { li.classList.add('torus-user-mod'); var icon = document.createElement('img'); icon.className = 'torus-user-icon-mod'; icon.src = 'http://images2.wikia.nocookie.net/monchbox/images/6/6b/Icon-chatmod.png'; li.appendChild(icon); }	li.appendChild(document.createTextNode(String.fromCharCode(160))); // var span = document.createElement('span'); span.className = 'torus-user-name'; if(props.status_state.toLowerCase == 'away') {span.classList.add('torus-user-away');} span.textContent = event.user; li.appendChild(span); } Torus.ui.remove_user = function(event) { if(event.room != Torus.ui.active) {return;} var userlist = Torus.ui.ids['sidebar'].getElementsByTagName('li'); for(var i = 0; i < userlist.length; i++) { if(userlist[i].className.indexOf('torus-user-' + encodeURIComponent(event.user)) != -1) { userlist[i].parentNode.removeChild(userlist[i]); break; }	} } Torus.ui.initial = function(event) { for(var i = 0; i < event.messages.length; i++) { Torus.ui.parse_message(event.messages[i]); var log = Torus.logs.messages[event.room.domain]; if(log.length == 0) {log.push(event.messages[i]);} else { var added = false; for(var j = log.length - 1; j >= 0; j--) { if(event.messages[i].id > log[j].id) { log.splice(j + 1, 0, event.messages[i]); added = true; break; }				else if(event.messages[i].id == log[j].id) { log[j] = event.messages[i]; added = true; break; }			}			if(!added) {log.unshift(event.messages[i]);} }	}	for(var i = 0; i < event.users.length; i++) {Torus.ui.update_user(event.users[i]);} if(event.room == Torus.ui.active) { Torus.util.empty(Torus.ui.ids['window']); Torus.ui.render(Torus.ui.ids['window']); }	if(event.room.parent && event.room != Torus.ui.active) {Torus.ui.ping(event.room);} } Torus.ui.parse_message = function(event) { event.ping = false; if(event.user != wgUserName && !event.room.parent && event.room != Torus.chats[0] && Torus.ui.pings.dir['#global'].enabled) { var text = event.text.toLowerCase; var global = Torus.ui.pings.dir['#global']; var local = Torus.ui.pings.dir[event.room.domain]; for(var i = 0; i < global.literal.length; i++) { if(text.indexOf(global.literal[i]) != -1) {event.ping = true; break;} }		if(!event.ping && local.enabled) { for(var i = 0; i < local.literal.length; i++) { if(text.indexOf(local.literal[i]) != -1) {event.ping = true; break;} }		}		if(!event.ping) { for(var i = 0; i < global.regex.length; i++) { var test = global.regex[i].test(text) global.regex[i].lastIndex = 0; if(test) {event.ping = true; break;} }		}		if(!event.ping && local.enabled) { for(var i = 0; i < local.regex.length; i++) { var test = local.regex[i].test(text); local.regex[i].lastIndex = 0; if(test) {event.ping = true; break;} }		}	}	if(event.ping) {Torus.ui.ping(event.room);} if(event.room.parent) {event.html = Torus.ui.parse_links(event.text, event.room.parent.domain);} else {event.html = Torus.ui.parse_links(event.text, event.room.domain);} } Torus.add_listener('chat', 'new', Torus.ui.new_room); Torus.add_listener('chat', 'open', Torus.ui.add_room); Torus.ui.input = function(event) { if(event.keyCode == 13 && !event.shiftKey) { //enter event.preventDefault; if(Torus.data.history[1] != this.value) { Torus.data.history[0] = this.value; Torus.data.history.unshift(''); }		Torus.data.histindex = 0; if(Torus.ui.active.id >= 0) { while(this.value.charAt(0) == '/') { if(this.value.charAt(1) == '/') { this.value = this.value.substring(1); break; }				if(this.value.indexOf('/me') == 0) {break;} if(this.value.indexOf('\n') != -1) { var command = this.value.substring(1, this.value.indexOf('\n')); this.value = this.value.substring(this.value.indexOf('\n') + 1); }				else { var command = this.value.substring(1); this.value = ''; }				var result = Torus.commands.eval(command); if(result === false) {Torus.alert('Can\'t find command `' + command.substring(0, command.indexOf(' ')) + '`.');} else if(result != undefined) {Torus.alert('' + result);} }		}		if(this.value && Torus.ui.active.id > 0) { if(this.value.indexOf('./') == 0) {Torus.ui.active.send_message(this.value.substring(1));} else {Torus.ui.active.send_message(this.value);} this.value = ''; }	}	else if(event.keyCode == 9 && Torus.ui.active.id > 0) { //tab event.preventDefault; if(!Torus.data.tabtext) { str = this.value; while(str[str - 1] == ' ') {str = str.substring(0, str.length - 1);} Torus.data.tabpos = str.lastIndexOf(' ') + 1; Torus.data.tabtext = str.substring(Torus.data.tabpos); }		var matches = 0; for(var user in Torus.ui.active.userlist) { if(user == 'length') {continue;} if(user.indexOf(Torus.data.tabtext) == 0) {matches++;} if(matches > Torus.data.tabindex) {break;} }		if(matches <= Torus.data.tabindex) { user = Torus.data.tabtext; Torus.data.tabindex = 0; }		else {Torus.data.tabindex++;} if(Torus.data.tabpos == 0) {this.value = user + (Torus.data.tabindex == 0 ? '' : ': ');} else {this.value = this.value.substring(0, Torus.data.tabpos) + user;} }	else if(event.keyCode == 38 && Torus.data.histindex + 1 < Torus.data.history.length && Torus.ui.active.id > 0) { //up Torus.data.histindex++; this.value = Torus.data.history[Torus.data.histindex]; }	else if(event.keyCode == 40 && Torus.data.histindex > 0 && Torus.ui.active.id > 0) { //down Torus.data.histindex--; this.value = Torus.data.history[Torus.data.histindex]; }	else if(event.keyCode != 39 && event.keyCode != 41 && Torus.ui.active.id > 0) { //anything other than left or right Torus.data.tabtext = ''; Torus.data.tabindex = 0; Torus.data.tabpos = 0; } } Torus.ui.click_link = function(event) { if(!this.href) { console.log('Torus.ui.click_link called on something with no href: ', this); return; }	event.preventDefault; if(this.href.indexOf('.wikia.com/wiki/Special:Chat') != -1 && Torus.options['misc-links-chat']) {Torus.open(this.href.substring(this.href.indexOf('://') + 3, this.href.indexOf('.wikia.com/wiki/Special:Chat')));} else {window.open(this.href, Torus.options['misc-links-target']);} } Torus.ui.tab_click = function(event) { event.preventDefault; var room = Torus.chats[this.getAttribute('data-id')]; if(event.shiftKey) { document.getSelection.removeAllRanges; if(Torus.ui.active.domain != room) {Torus.ui.show(room);} }	else {Torus.ui.activate(room);} } Torus.ui.sidebar_mouseover = function(event) { clearTimeout(Torus.ui.popup_timeout); Torus.ui.popup_timeout = 0; } Torus.ui.sidebar_mouseout = function(event) { Torus.ui.popup_timeout = setTimeout(Torus.ui.unrender_popup, 500); } Torus.ui.window_mouseover = function(event) { if(Torus.data.pinginterval != 0) { clearInterval(Torus.data.pinginterval); Torus.data.pinginterval = 0; document.title = Torus.data.titleflash; }	//if(Torus.ui.active.id > 0) { //	clearTimeout(Torus.ui.active.away_timeout); //	setTimeout(function {Torus.ui.active.set_status('away', ''); Torus.ui.active.auto_away = true;}, 5 * 60 * 1000); //} } Torus.ui.parser = {}; Torus.ui.parser.parse_plainlink = function(state) { var space = state.text.indexOf(' '); var line = state.text.indexOf('\n'); if(space != -1 && line != -1) { if(space < line) {var end = space;} else {var end = line;} }	else if(space != -1) {var end = space;} else if(line != -1) {var end = line;} else {var end = state.text.length;} var url = state.text.substring(0, end); while(url.charAt(url.length - 1) == '.' || url.charAt(url.length - 1) == ',' || url.charAt(url.length - 1) == '!' || url.charAt(url.length - 1) == '?') {url = url.substring(0, url.length - 1); end--;} state.text = state.text.substring(end); var link = document.createElement('a'); link.className = 'torus-message-link'; link.href = url; link.addEventListener('click', Torus.ui.click_link); link.textContent = url; return link; } Torus.ui.parser.parse_extlink = function(state) { var space = state.text.indexOf(' '); var end = state.text.indexOf(']', space); var url = state.text.substring(1, space); var display = state.text.substring(space + 1, end); if(end == -1 || space == -1 || display.trim == '' || space + 1 >= end || (state.text.indexOf('\n') != -1 && state.text.indexOf('\n') < end)) { var node = document.createTextNode(state.text.substring(0, 3)); state.text = state.text.substring(3); return node; }	state.text = state.text.substring(end + 1); var link = document.createElement('a'); link.className = 'torus-message-link'; link.href = url; link.addEventListener('click', Torus.ui.click_link); link.textContent = display; return link; } Torus.ui.parser.parse_locallink = function(state) { var pipe = state.text.indexOf('|'); var close = state.text.indexOf(']]'); if(close == -1 || (state.text.indexOf('\n') != -1 && state.text.indexOf('\n') < close)) { state.text = state.text.substring(2); return document.createTextNode('page, pipe trick			var title = state.text.substring(2, pipe);			var display = title.substring(title.indexOf(':') + 1); //strip everything up to first :			if(display[display.length - 1] == ')' && display.lastIndexOf('(') != -1) {display = display.substring(0, display.lastIndexOf('('));} //strip trailing parens		}		else { //is display			var title = state.text.substring(2, pipe);			var display = state.text.substring(pipe + 1, close);		}	}	else { //is page		var title = state.text.substring(2, close);		var display = title;	}	title = title.trim;	display = display.trim;	if(!title || !display) { //skip and anything		state.text = state.text.substring(2);		return document.createTextNode();	}	if(title.indexOf('c:') == 0) {title = 'w:' + title;} //c: on central is w:c: everywhere else	if(title.indexOf('w:c:') == 0) { //w:c:domain link		if(title == 'w:c:') { //this link goes nowhere			state.text = state.text.substring(2);			return document.createTextNode('[[');		}		if(title.indexOf(':', 4) != -1) { //is [[w:c:domain:page			var domain = title.substring(4, title.indexOf(':', 4));			var page = Torus.util.normalize_pagename(title.substring(title.indexOf(':', 4) + 1));			title = title.substring(0, title.indexOf(':', 4)) + ':' + page;		}		else { //is w:c:domain			var domain = title.substring(4);			var page = ;		}	}	else if(title.indexOf('w:') == 0) { //w: central link		var domain = 'c';		if(title == 'w:') {var page = ;}		else {			var page = Torus.util.normalize_pagename(title.substring(2));			title = 'w:' + page;		}	}	else if(state.wiki) { //local link		var domain = state.wiki;		var page = Torus.util.normalize_pagename(title);		title = page;	}	else { //no domain was specified and we don't know the local domain		state.text = state.text.substring(2);		return document.createTextNode('[[');	}	state.text = state.text.substring(close + 2);	var link = document.createElement('a');		link.className = 'torus-message-link';		link.href = 'http://' + domain + '.wikia.com/wiki/' + encodeURIComponent(page).replace(/%3A/g, ':').replace(/%20/g, '_').replace(/%2F/g, '/');		link.title = title;		link.addEventListener('click', Torus.ui.click_link);		link.textContent = display;	return link; } Torus.ui.parser.parse_newline = function(state) {	state.text = state.text.substring(1);	return document.createElement('br'); } Torus.ui.parser.hooks = {	'http://': Torus.ui.parser.parse_plainlink,	'https://': Torus.ui.parser.parse_plainlink,	'news://': Torus.ui.parser.parse_plainlink,	'ftp://': Torus.ui.parser.parse_plainlink,	'irc://': Torus.ui.parser.parse_plainlink,	'[http://': Torus.ui.parser.parse_extlink,	'[https://': Torus.ui.parser.parse_extlink,	'[news://': Torus.ui.parser.parse_extlink,	'[ftp://': Torus.ui.parser.parse_extlink,	'[irc://': Torus.ui.parser.parse_extlink,	'[//': Torus.ui.parser.parse_extlink,	'[[': Torus.ui.parser.parse_locallink,	'\n': Torus.ui.parser.parse_newline, }; Torus.ui.parse_links = function(text, wiki) {	if(!text) {return ;}	var state = {		text: text,		wiki: wiki,	};	var html = document.createElement('span');	html.className = 'torus-message-text';	var min = ;	var ref = -1	do {		ref = -1;		for(var i in Torus.ui.parser.hooks) {			var index = state.text.indexOf(i);			if(ref == -1 || (index != -1 && index < ref)) {				min = i;				ref = index;			}		}		if(ref == -1) {break;}		if(ref > 0) {			html.appendChild(document.createTextNode(state.text.substring(0, ref)));			state.text = state.text.substring(ref);		}		html.appendChild(Torus.ui.parser.hooks[min](state));	}	while(ref != -1);	html.appendChild(document.createTextNode(state.text));	//FIXME: combine adjacent text nodes	return html; } Torus.util.normalize_pagename = function(page) {	if(!page) {return ;}	if(page.indexOf(':') != -1) { //Namespace:Title		var namespace = page.substring(0, page.indexOf(':'));		var title = page.substring(page.indexOf(':') + 1);		page = Torus.util.cap(namespace) + ':' + Torus.util.cap(title);	}	else {page = Torus.util.cap(page);} //Title (mainspace)	while(page.indexOf('_') != -1) {page = page.replace('_', ' ');}	return page; } Torus.ui.img_loader = function {	var loader = document.createElement('img');		loader.className = 'torus-loader';		loader.src = 'http://slot1.images.wikia.nocookie.net/__cb1410215834/common/skins/common/images/ajax.gif';	return loader; } Torus.ui.span_user = function(user) {	var color = Torus.util.color_hash(user, Torus.options['misc-user_colors-hue'], Torus.options['misc-user_colors-val'], Torus.options['misc-user_colors-sat']);	var span = document.createElement('span');		span.className = 'torus-message-usercolor';		span.style.color = color;		span.textContent = user;	return span; } Torus.util.empty = function(el) {	var frag = document.createDocumentFragment;	while(el.firstChild) {frag.appendChild(el.firstChild);}	return frag; } Torus.util.color_hash = function(str, hue, sat, val) {	if(str === undefined) {throw new Error('Not enough parameters. (util.color_hash)');}	str += ;	if(!hue) {hue = 0;}	if(!sat) {sat = .7;}	if(!val) {val = .6;}	for(var i = 0; i < str.length; i++) {hue = 31 * hue + str.charCodeAt(i);} //same hash algorithm as webchat, except this is case sensitive	hue %= 360;	//1 letter variables are fun don't you love mathematicians	var c = val * sat;	var m = val - c;	var C = Math.floor((c + m) * 255).toString(16);	var X = Math.floor((c * (1 - Math.abs((hue / 60) % 2 - 1)) + m) * 255).toString(16);	var O = Math.floor(m * 255).toString(16);	if(C.length == 1) {C = '0' + C;}	if(X.length == 1) {X = '0' + X;}	if(O.length == 1) {O = '0' + O;}	switch(Math.floor(hue / 60)) {		case 0: return '#' + C + X + O;		case 1: return '#' + X + C + O;		case 2: return '#' + O + C + X;		case 3: return '#' + O + X + C;		case 4: return '#' + X + O + C;		case 5: return '#' + C + O + X;	} } Torus.i18n = {	lang: 'en', }; Torus.i18n.text = function(message) {	if(typeof Torus.i18n[Torus.i18n.lang] == 'object') {var lang = Torus.i18n.lang;}	else {var lang = 'en';}	if(Torus.i18n[lang][message]) {message = Torus.i18n[Torus.i18n.lang][message];}	else {lang = 'xxx';}	if(lang == 'xxx') {		for(var i = 1; i < arguments.length; i++) {message += ' $' + i + ' = ' + arguments[i];}	}	else {		for(var i = 1; i < arguments.length; i++) {			for(var ref = message.indexOf('$' + i); ref != -1; ref = message.indexOf('$' + i, ref + 1)) {				if(message.charAt(ref - 1) == '\\') {continue;}				message = message.substring(0, ref) + arguments[i] + message.substring(ref + ( + i).length + 1);			}			while(message.indexOf('\\$') != -1) {message = message.replace('\\$', '$');}		}	}	return message; } Torus.i18n.html = function(name) {	if(typeof Torus.i18n[Torus.i18n.lang] == 'object') {var lang = Torus.i18n.lang;}	else {var lang = 'en';}	if(Torus.i18n[lang][name]) {var message = Torus.i18n[lang][name];}	else {		var message = name;		lang = 'xxx';	}	var frag = document.createDocumentFragment;	if(lang == 'xxx') {		frag.appendChild(document.createTextNode(name));		for(var i = 1; i < arguments.length; i++) {			frag.appendChild(document.createTextNode(' $' + i + ' = '));			frag.appendChild(arguments[i]);		}	}	else {		for(var ref = message.indexOf('$'); ref != -1; ref = message.indexOf('$')) {			if(message.charAt(ref - 1) == '\\') {				frag.appendChild(document.createTextNode('$'));				message = message.substring(2);			}			else {				frag.appendChild(document.createTextNode(message.substring(0, ref)));				var index = ;				do {					var c = message.charAt(ref + index.length + 1);					if(c) {index += c;}					else {index += '.';}				}				while(arguments[index * 1]);				index = index.substring(0, index.length - 1) * 1;				if(isNaN(index)) {return document.createTextNode('I18N ERROR: ' + name);}				frag.appendChild(arguments[index]);				message = message.substring(ref + ( + index).length + 1);			}		}		frag.appendChild(document.createTextNode(message));	}	return frag; } Torus.i18n['en'] = {	'yes': 'yes',	'no': 'no',	'status': 'status',	'error-login': 'You don\'t appear to be logged in. You must have an account to use chat on Wikia. Please register or log in.',	'title': 'Torus - It\'s a donut - $1',	'connecting': 'Connecting to $1...',	'info-public': 'Public room of $1.',	'info-private': 'Private room of $1, between $2.',	'info-menu': 'Torus v$1, running on $2',	'info-menu-back': 'Back to menu',	'message-join': '$1 joined $2',	'message-rejoin': '$1 rejoined $2',	'message-part': '$1 left $2',	'message-ghost': '$1 ghosted $2',	'message-logout': '$1 logged out',	'message-mod': '$1 promoted $2 to chatmod of $3',	'message-kick': '$1 kicked $2 from $3',	'message-ban': '$1 banned $2 from $3 for $4',	'message-banlinks-talk': 't',	'message-banlinks-contribs': 'c',	'message-banlinks-history': 'log',	'message-banlinks-chatconnect': 'ccon',	'message-unban': '$1 unbanned $2 from $3',	'popup-talk': 'talk',	'popup-contribs': 'contribs',	'popup-history': 'ban history',	'popup-chatconnect': 'chatconnect',	'popup-pm': 'Private message',	'popup-block': 'Block PMs',	'popup-unblock': 'Unblock PMs',	'popup-mod': 'Promote to mod',	'popup-mod-areyousure': 'Are you sure?',	'popup-kick': 'Kick',	'popup-ban': 'Ban',	'popup-ban-expiry': 'Expiry',	'popup-ban-reason': 'Reason',	'menu-menu': 'menu',	'menu-torus': 'Torus v$1',	'menu-fork': 'fork me',	'menu-report': 'report a bug',	'menu-suggest': 'suggest a bug',	'menu-doc': '"documentation"',	'pings-alert': 'Alert',	'pings-interval': 'Interval',	'pings-beep': 'Beep',	'pings-sound': 'Sound',	'pings-literal': 'Literal',	'pings-regex': 'Regex',	'pings-add': '+ Add',	'options-enabled': 'Enabled',	'options-messages': 'Messages',	'options-messages-general': 'General',	'options-messages-general-max': 'Max',	'options-messages-general-rejoins': 'Rejoins',	'options-messages-general-timezone': 'Timezone',	'options-misc': 'Misc',	'options-misc-connection': 'Connection',	'options-misc-connection-default_rooms': 'Default rooms',	'options-misc-connection-local': 'Local',	'options-misc-user_colors': 'User colors',	'options-misc-user_colors-hue': 'Hue',	'options-misc-user_colors-sat': 'Sat',	'options-misc-user_colors-val': 'Val',	'options-misc-links': 'Links',	'options-misc-links-chat': 'Chat',	'options-misc-links-target': 'Target',	'themes-text': 'text',	'themes-link': 'link',	'themes-away': 'away',	'themes-ping': 'ping',	'commands-help': 'Help: $1:\n$2',	'commands-nohelp': 'No help data for $1',	'commands-dir': 'Commands:\n$1\nFull documentation: w:c:monchbox:Torus',	'commands-help-join': 'Usage: /join \nJoin the chat room associated with . `/join 0` is special, it is the same as `/logout`.\nFor example, `/join community` will take you to the room for community.wikia.com.',	'commands-help-part': 'Usage: /part \nLeave the room associated with . If is unspecified, you will leave the room you are currently viewing.',	'commands-help-logout': 'Usage: /logout\nLeave every room.',	'commands-help-kick': 'Usage: /kick \nKick from the current room.',	'commands-help-ban': 'Usage: /ban   \nBan or reban from the current room. Use quotes on anything with spaces (eg. /ban "A troll" "1 day" "being mean").',	'commands-help-private': 'Usage: /private   ...\nOpen a private room with each of the specified users. Use quotes on names with spaces (eg. /private "Some guy" Admin "Other guy")',	'commands-help-away': 'Usage: /away \nToggle your away status for the current room. If is specified, your status message will be set to that.',	'commands-help-back': 'Usage: /back \nSet your status state to `here` for the current room. If is specified, your status message will be set to that.',	'commands-help-status': 'Usage: /status  \nChange your status state and/or message for the current room. Your status state can only be one word (no spaces), but your status message can be as long as you want. Users on Special:Chat won\'t be able to see either.\nTwo status states are special:\n`here`: used to denote active users. `/back` will set your status state to this.\n`away`: user to denote inactive users. `/away` will set your status state to this.',	'commands-help-ctcp': 'Usage: /ctcp   \nClient to client protocol. Other users with Torus implement at least the "version".\nIf the target user has spaces in their name, you must surround their name with quotes.\n defaults to an empty string (meaning everyone). defaults to "version". defaults to an empty string.\nThis means that just "/ctcp" is equivalent to sending everyone in the room a "/ctcp version".',	//'commands-help-me': Usage: /me \nEmote yourself.',	'commands-help-options': 'Usage: /options\nView options.',	'commands-help-fullscreen': 'Usage: /fullscreen\nMake Torus fullscreen.',	'commands-help-help': 'Usage: /help \nDisplays help data.', }; //(function { //I really hate these but it's better then leaking temp variables everywhere //FIXME: iffy causes load order problems	Torus.util.load_css('http://monchbox.wikia.com/wiki/User:Monchoman45/monobook.css?action=raw&ctype=text/css&templates=expand&t=' + (new Date).getTime);	Torus.ui.window.id = 'torus';	Torus.ui.ids['torus'] = Torus.ui.window;	var tabs = document.createElement('div');		tabs.id = 'torus-tabs';		Torus.ui.ids['tabs'] = tabs;		var menu = document.createElement('span');			menu.id = 'torus-tab--1';			Torus.ui.ids['tab--1'] = menu;			menu.setAttribute('data-id', '-1');			menu.className = 'torus-tab';			menu.addEventListener('click', Torus.ui.menu.tab_click);			var img = document.createElement('img');				img.src = 'http://images2.wikia.nocookie.net/__cb20110812214252/monchbox/images/a/a1/Gear_icon.png';				img.width = '18';			menu.appendChild(img);			menu.appendChild(document.createTextNode(String.fromCharCode(160))); // 			menu.appendChild(document.createTextNode('menu'));		tabs.appendChild(menu);	Torus.ui.window.appendChild(tabs);	var sidebar = document.createElement('ul');		sidebar.id = 'torus-sidebar';		Torus.ui.ids['sidebar'] = sidebar;		sidebar.addEventListener('mouseover', Torus.ui.sidebar_mouseover);		sidebar.addEventListener('mouseout', Torus.ui.sidebar_mouseout);	Torus.ui.window.appendChild(sidebar);	var popup = document.createElement('div');		popup.id = 'torus-popup';		Torus.ui.ids['popup'] = popup;		popup.style.display = 'none';		popup.addEventListener('mouseover', Torus.ui.sidebar_mouseover);		popup.addEventListener('mouseout', Torus.ui.sidebar_mouseout);	Torus.ui.window.appendChild(popup);	var info = document.createElement('div');		info.id = 'torus-info';		Torus.ui.ids['info'] = info;	Torus.ui.window.appendChild(info);	var wind = document.createElement('div');		wind.id = 'torus-window';		Torus.ui.ids['window'] = wind;	Torus.ui.window.appendChild(wind);	var input = document.createElement('div');		input.id = 'torus-input';		Torus.ui.ids['input'] = input;		var inputbox = document.createElement('textarea');			inputbox.id = 'torus-input-box';			Torus.ui.ids['input-box'] = inputbox;			inputbox.onkeydown = Torus.ui.input;		input.appendChild(inputbox);	Torus.ui.window.appendChild(input); //}); Torus.ui.window.addEventListener('mouseover', Torus.ui.window_mouseover); Torus.add_listener('window', 'load', Torus.ui.onload); Torus.chats[0].add_listener('io', 'alert', Torus.ui.add_line); for(var i in Torus.logs) {Torus.logs[i][0] = [];} Torus.chats[0].listeners.ui = {}; Torus.ui.add_room({room: Torus.chats[0]}); Torus.ui.show(Torus.chats[0]); Torus.commands = {}; Torus.commands.join = {	help: 'commands-help-join',	func: function(room) {		if(room == '0') {			Torus.logout;			return;		}		Torus.open(room);	} }; Torus.commands.part = {	help: 'commands-help-part',	func: function(room) {		if(!room) {Torus.ui.active.disconnect('closed');}		else {			var chat = Torus.chats[room];			if(!chat || (!chat.connecting && !chat.connected)) {return 'Invalid room ' + room + '.';} //FIXME: i18n			else {chat.disconnect('closed');}		}	} }; Torus.commands.quit = '/logout'; Torus.commands.logout = {	help: 'commands-help-logout',	func: Torus.logout }; Torus.commands.kick = {	help: 'commands-help-kick',	func: function {		var user = ;		for(var i = 0; i < arguments.length; i++) {user += ' ' + arguments[i];}		user = user.substring(1);		Torus.ui.active.kick(user);	} }; Torus.commands.ban = {	help: 'commands-help-ban',	func: function(user, expiry, summary) {		if(!summary) {summary = 'Misbehaving in chat';} //FIXME: ?action=query&meta=allmessages		Torus.ui.active.ban(user, expiry, summary);	} }; Torus.commands.unban = {	help: 'commands-help-unban',	func: function(user) {Torus.ui.active.ban(user, 0, 'undo');} //FIXME: ?action=query&meta=allmessages }; Torus.commands.mod = '/givemod'; Torus.commands.givemod = {	help: 'commands-help-givemod',	func: function(user) {Torus.ui.active.mod(user);} }; Torus.commands.pm = '/private'; Torus.commands.query = '/private'; Torus.commands.priv = '/private'; Torus.commands.private = {	help: 'commands-help-private',	func: function {Torus.ui.active.open_private(Array.prototype.slice.call(arguments));} }; Torus.commands.away = {	help: 'commands-help-away',	func: function(message) {		var user = Torus.ui.active.userlist[wgUserName];		if(user.status_state == 'away') {			if(user.old_state == 'away') {Torus.ui.active.set_status('here', );}			else {Torus.ui.active.set_status(user.old_state, user.old_message);}		}		else {Torus.ui.active.set_status('away', message);}	} }; Torus.commands.back = {	help: 'commands-help-back',	func: function(message) {		if(!message) {message = ;}		Torus.ui.active.set_status('here', message);	} }; Torus.commands.status = {	help: 'commands-help-status',	func: function(state, message) {Torus.ui.active.set_status(state, message);} }; Torus.commands.ctcp = {	help: 'commands-help-ctcp',	func: function(target, proto, message) {Torus.ui.active.ctcp(target, proto, message);} }; /*Torus.commands.me = { //XXX: right now /me is implemented by literally sending /me	help: 'commands-help-me',	func: function {		var str = ;		for(var i = 0; i < arguments.length; i++) {str += ' ' + arguments[i];}		Torus.ui.active.send_message('* ' + wgUserName + str, false);	} };*/ Torus.commands.options = {	help: 'commands-help-options',	func: function {Torus.ui.activate(Torus.ext.options);} }; Torus.commands.fullscreen = {	help: 'commands-help-fullscreen',	func: Torus.ui.fullscreen }; Torus.commands.help = {	help: 'commands-help-help',	func: function {		var str = ;		for(var i = 0; i < arguments.length; i++) {str += ' ' + arguments[i];}		str = str.substring(1);		if(str) {			var help = Torus.commands.eval(str, 'help');			if(!help) {Torus.alert(Torus.i18n.text('commands-nohelp', str));}			else {return Torus.i18n.text('commands-help', str, Torus.i18n.text(help));}		}		else {			var coms = ;			for(var i in Torus.commands) {				if(typeof Torus.commands[i] != 'function' && typeof Torus.commands[i] != 'string') {coms += ', ' + i;}			}			coms = coms.substring(2);			return Torus.i18n.text('commands-dir', coms);		}	} }; Torus.commands.eval = function(str, prop) {	if(typeof str != 'string') {return false;}	var com = str.split(' ');	for(var i = 0; i < com.length; i++) {		if(com[i].charAt(0) == '"') {			com[i] = com[i].substring(1);			if(com[i].charAt(com[i].length - 1) == '"') {com[i] = com[i].substring(0, com[i].length - 1);}			else {				var j = i + 1;				for(j; j < com.length && com[j].charAt(com[j].length - 1) != '"'; j++) {com[i] += ' ' + com[j];}				com[i] += ' ' + com[j].substring(0, com[j].length - 1);				com.splice(i + 1, j - i);			}		}	}	var ref = Torus.commands;	var i = 0;	var cont = true;	while(ref[com[i]]) {		switch(typeof ref[com[i]]) {			case 'string':				if(ref[com[i]].charAt(0) == '/') {var line = ref[com[i]].substring(1) + ' ' + com.slice(i + 1).join(' ');}				else {var line = com.slice(0, i).join(' ') + ' ' + ref[com[i]] + ' ' + com.slice(i + 1).join(' ');} return Torus.commands.eval(line, prop); case 'object': if(typeof ref[com[i]].func == 'function') {var command = ref[com[i]];} //is a command else { ref = ref[com[i]]; i++; if(!ref[com[i]] && ref.default && ref.default.func) {var command = ref.default;} }				if(command) {cont = false;} break; default: cont = false; break; }		if(cont == false) {break;} }	if(command) { if(prop == '*') {return ref;} else if(prop) {return command[prop];} else {return command.func.apply(ref, com.slice(i + 1));} }	else {return false;} } Torus.ui.ping = function(room) { if(!Torus.ui.pings.dir['#global'].enabled || !Torus.ui.window.parentNode) {return;} if((room != Torus.ui.active && !room.viewing) || Torus.ui.active.id <= 0) {Torus.ui.ids['tab-' + room.domain].classList.add('torus-tab-ping');} if(room.parent) {var domain = room.parent.domain;} else {var domain = room.domain;} if(Torus.data.pinginterval == 0) { Torus.data.titleflash = document.title; document.title = Torus.ui.pings.dir[domain].alert; Torus.data.pinginterval = setInterval(function {			if(document.title != Torus.ui.pings.dir[domain].alert) {document.title = Torus.ui.pings.dir[domain].alert;}			else {document.title = Torus.data.titleflash;}		}, Torus.ui.pings.dir[domain].interval); if(Torus.ui.pings.dir[domain].beep) { var beep = document.createElement('audio'); beep.src = Torus.ui.pings.dir[domain].sound; beep.play; }	}	Torus.call_listeners(new Torus.classes.UIEvent('ping', room)); } //basically a more specific version of options.js Torus.ui.pings = new Torus.classes.Extension('pings', -3); Torus.ui.pings.text = 'Pings'; Torus.ui.pings.dir = { '#global': { enabled: true, alert: 'Activity!', interval: 500, beep: true, sound: 'http://images.wikia.com/monchbox/images/0/01/Beep-sound.ogg', regex: [], literal: [wgUserName], }, } Torus.ui.pings.selected = '#global'; Torus.ui.pings.ui = { sidebar: document.createDocumentFragment, }; Torus.ui.pings.rebuild = function { Torus.ui.pings.ui = {}; Torus.ui.pings.ui.sidebar = document.createDocumentFragment; for(var i in Torus.ui.pings.dir) { var li = document.createElement('li'); li.className = 'torus-sidebar-button'; if(i == Torus.ui.pings.selected) {li.classList.add('torus-sidebar-button-selected');} li.setAttribute('data-id', i); li.textContent = i;			li.addEventListener('click', Torus.ui.pings.click_sidebar); Torus.ui.pings.ui.sidebar.appendChild(li); var frag = document.createDocumentFragment; Torus.ui.pings.ui['group_' + i] = frag; var fieldset = document.createElement('fieldset'); fieldset.id = 'torus-pings-' + i + '-fieldset'; Torus.ui.ids['pings-' + i + '-fieldset'] = fieldset; fieldset.className = 'torus-pings-fieldset'; var legend = document.createElement('legend'); legend.textContent = i;			fieldset.appendChild(legend); var enabled = document.createElement('div'); var label = document.createElement('label'); label.setAttribute('for', 'torus-pings-' + i + '-enabled'); label.textContent = Torus.i18n.text('options-enabled') + ':'; enabled.appendChild(label); enabled.appendChild(document.createTextNode(' ')); var input = document.createElement('input'); input.id = 'torus-pings-' + i + '-enabled'; Torus.ui.ids['pings-' + i + '-enabled'] = input; input.className = 'torus-option-boolean'; input.type = 'checkbox'; input.checked = Torus.ui.pings.dir[i].enabled; input.setAttribute('data-id', i); input.addEventListener('blur', Torus.ui.pings.blur_enabled); enabled.appendChild(input); fieldset.appendChild(enabled); var alert = document.createElement('div'); var label = document.createElement('label'); label.setAttribute('for', 'torus-pings-' + i + '-alert'); label.textContent = Torus.i18n.text('pings-alert') + ':'; alert.appendChild(label); alert.appendChild(document.createTextNode(' ')); var input = document.createElement('input'); input.id = 'torus-pings-' + i + '-alert'; Torus.ui.ids['pings-' + i + '-alert'] = input; input.className = 'torus-option-string'; input.type = 'text'; input.value = Torus.ui.pings.dir[i].alert; input.setAttribute('data-id', i); input.addEventListener('blur', Torus.ui.pings.blur_alert); alert.appendChild(input); fieldset.appendChild(alert); var interval = document.createElement('div'); var label = document.createElement('label'); label.setAttribute('for', 'torus-pings-' + i + '-interval'); label.textContent = Torus.i18n.text('pings-interval') + ':'; interval.appendChild(label); interval.appendChild(document.createTextNode(' ')); var input = document.createElement('input'); input.id = 'torus-pings-' + i + '-input'; Torus.ui.ids['pings-' + i + '-input'] = input; input.className = 'torus-option-number'; input.type = 'number'; input.value = Torus.ui.pings.dir[i].interval; input.setAttribute('data-id', i); input.addEventListener('blur', Torus.ui.pings.blur_interval); interval.appendChild(input); fieldset.appendChild(interval); var beep = document.createElement('div'); var label = document.createElement('label'); label.setAttribute('for', 'torus-pings-' + i + '-beep'); label.textContent = Torus.i18n.text('pings-beep') + ':'; beep.appendChild(label); beep.appendChild(document.createTextNode(' ')); var input = document.createElement('input'); input.id = 'torus-pings-' + i + '-beep'; Torus.ui.ids['pings-' + i + '-beep'] = input; input.className = 'torus-option-boolean'; input.type = 'checkbox'; input.checked = Torus.ui.pings.dir[i].beep; input.setAttribute('data-id', i); input.addEventListener('blur', Torus.ui.pings.blur_beep); beep.appendChild(input); fieldset.appendChild(beep); var sound = document.createElement('div'); var label = document.createElement('label'); label.setAttribute('for', 'torus-pings-' + i + '-sound'); label.textContent = Torus.i18n.text('pings-sound') + ':'; sound.appendChild(label); sound.appendChild(document.createTextNode(' ')); var input = document.createElement('input'); input.id = 'torus-pings-' + i + '-sound'; Torus.ui.ids['pings-' + i + '-sound'] = input; input.className = 'torus-option-string'; input.type = 'text'; input.value = Torus.ui.pings.dir[i].sound; input.setAttribute('data-id', i); input.addEventListener('blur', Torus.ui.pings.blur_sound); sound.appendChild(input); fieldset.appendChild(sound); var literal = document.createElement('div'); var label = document.createElement('label'); label.setAttribute('for', 'torus-pings-' + i + '-literal'); label.textContent = Torus.i18n.text('pings-literal') + ':'; literal.appendChild(label); literal.appendChild(document.createElement('br')); var textarea = document.createElement('textarea'); textarea.id = 'torus-pings-' + i + '-literal'; Torus.ui.ids['pings-' + i + '-literal'] = textarea; textarea.className = 'torus-pings-literal'; textarea.rows = 5 textarea.value = Torus.ui.pings.dir[i].literal.join('\n'); textarea.setAttribute('data-id', i); textarea.addEventListener('blur', Torus.ui.pings.blur_literal); literal.appendChild(textarea); fieldset.appendChild(literal); var regex = document.createElement('div'); var label = document.createElement('label'); label.setAttribute('for', 'torus-pings-' + i + '-regex'); label.textContent = Torus.i18n.text('pings-regex') + ':'; regex.appendChild(label); regex.appendChild(document.createElement('br')); var textarea = document.createElement('textarea'); textarea.id = 'torus-pings-' + i + '-regex'; Torus.ui.ids['pings-' + i + '-regex'] = textarea; textarea.className = 'torus-pings-regex'; textarea.rows = 10; textarea.value = ''; for(var j = 0; j < Torus.ui.pings.dir[i].regex.length; j++) {textarea.value += Torus.ui.pings.dir[i].regex[j].toString + '\n';} textarea.setAttribute('data-id', i); textarea.addEventListener('blur', Torus.ui.pings.blur_regex); regex.appendChild(textarea); fieldset.appendChild(regex); frag.appendChild(fieldset); }	var add = document.createElement('li'); add.id = 'torus-pings-add'; Torus.ui.ids['pings-add'] = add; add.className = 'torus-sidebar-button'; add.textContent = '+ Add'; add.addEventListener('click', Torus.ui.pings.click_add); Torus.ui.pings.ui.sidebar.appendChild(add); if(Torus.ui.active == Torus.ui.pings) { Torus.util.empty(Torus.ui.ids['sidebar']); Torus.util.empty(Torus.ui.ids['window']); Torus.ui.pings.render(Torus.ui.pings.selected); } } Torus.ui.pings.render = function(group) { if(!group || typeof group == 'object') {group = Torus.ui.pings.selected;} //group == object when ui.activate fires if(group === '') {group = '#global';} //this happens if someone clicks "add", then goes away and comes back if(Torus.ui.ids['window'].firstChild) { //a group is already rendered, put it back Torus.ui.pings.ui['group_' + Torus.ui.pings.selected] = Torus.util.empty(Torus.ui.ids['window']); }	for(var i = 0; i < Torus.ui.ids['sidebar'].children.length; i++) { if(Torus.ui.ids['sidebar'].children[i].getAttribute('data-id') == group) {Torus.ui.ids['sidebar'].children[i].classList.add('torus-sidebar-button-selected');} else {Torus.ui.ids['sidebar'].children[i].classList.remove('torus-sidebar-button-selected');} }	Torus.ui.ids['window'].appendChild(Torus.ui.pings.ui['group_' + group]); Torus.ui.ids['sidebar'].appendChild(Torus.ui.pings.ui.sidebar); if(!group) {Torus.ui.pings.save;} //FIXME: when does this happen? else {Torus.ui.pings.selected = group;} } Torus.ui.pings.unrender = function(event) { Torus.ui.pings.ui.sidebar = event.old_sidebar; Torus.ui.pings.ui['group_' + Torus.ui.pings.selected] = event.old_window; Torus.ui.pings.save; } Torus.ui.pings.click_sidebar = function {Torus.ui.pings.render(this.getAttribute('data-id'));} Torus.ui.pings.click_add = function { //FIXME: this works, but is stupid if(!Torus.ui.pings.selected) {return;} Torus.ui.pings.ui['group_' + Torus.ui.pings.selected] = Torus.util.empty(Torus.ui.ids['window']); for(var i = 0; i < Torus.ui.ids['sidebar'].children.length; i++) { Torus.ui.ids['sidebar'].children[i].classList.remove('torus-sidebar-button-selected'); }	this.classList.add('torus-sidebar-button-selected'); Torus.ui.pings.selected = ''; var add = document.createElement('div'); var label = document.createElement('label'); label.setAttribute('for', 'torus-pings-add-input'); label.textContent = Torus.i18n.text('pings-add') + ':'; add.appendChild(label); add.appendChild(document.createTextNode(' ')); var input = document.createElement('input'); input.id = 'torus-pings-add-input'; Torus.ui.ids['pings-add-input'] = input; input.addEventListener('keyup', function {				if(event.keyCode == 13) {					if(!Torus.ui.pings.dir[this.value]) {						Torus.ui.pings.dir[this.value] = {};						for(var i in Torus.ui.pings.dir['#global']) {Torus.ui.pings.dir[this.value][i] = Torus.ui.pings.dir['#global'][i];}						Torus.ui.pings.dir[this.value].enabled = true;						Torus.ui.pings.dir[this.value].literal = [];						Torus.ui.pings.dir[this.value].regex = [];					}					Torus.ui.pings.selected = this.value;					Torus.ui.pings.rebuild;				}			}); add.appendChild(input); Torus.ui.ids['window'].appendChild(add); input.focus; } Torus.ui.pings.blur_enabled = function {Torus.ui.pings.dir[this.getAttribute('data-id')].enabled = this.checked;} Torus.ui.pings.blur_alert = function {Torus.ui.pings.dir[this.getAttribute('data-id')].alert = this.value;} Torus.ui.pings.blur_interval = function {Torus.ui.pings.dir[this.getAttribute('data-id')].interval = this.value;} Torus.ui.pings.blur_beep = function {Torus.ui.pings.dir[this.getAttribute('data-id')].beep = this.checked;} Torus.ui.pings.blur_sound = function {Torus.ui.pings.dir[this.getAttribute('data-id')].sound = this.value;} Torus.ui.pings.blur_literal = function { var arr = []; var pings = this.value.toLowerCase.split('\n'); for(var i = 0; i < pings.length; i++) { if(!pings[i]) {continue;} arr.push(pings[i]); }	Torus.ui.pings.dir[this.getAttribute('data-id')].literal = arr; } Torus.ui.pings.blur_regex = function { var arr = []; var pings = this.value.split('\n'); for(var i = 0; i < pings.length; i++) { var regex = Torus.util.parse_regex(pings[i]); if(!regex) {continue;} arr.push(regex); }	Torus.ui.pings.dir[this.getAttribute('data-id')].regex = arr; } Torus.ui.pings.save = function { var save = {}; var pings = Torus.ui.pings.dir; for(var i in pings) { save[i] = {}; for(var j in pings[i]) {save[i][j] = pings[i][j];} save[i].regex = []; for(var j = 0; j < pings[i].regex.length; j++) {save[i].regex.push(pings[i].regex[j].toString);} }	window.localStorage.setItem('torus-pings', JSON.stringify(save)); } Torus.ui.pings.load = function { var load = JSON.parse(window.localStorage.getItem('torus-pings')); if(load) { for(var i in load) { for(var j = 0; j < load[i].regex.length; j++) { var regex = Torus.util.parse_regex(load[i].regex[j]); if(!regex) {load[i].regex.splice(j, 1); j--;} load[i].regex[j] = regex; }			Torus.ui.pings.dir[i] = load[i]; }	}	Torus.ui.pings.rebuild; } Torus.ui.pings.add_listener('ui', 'activate', Torus.ui.pings.render); Torus.ui.pings.add_listener('ui', 'deactivate', Torus.ui.pings.unrender); Torus.add_listener('window', 'load', Torus.ui.pings.load); Torus.add_listener('window', 'unload', Torus.ui.pings.save); Torus.ui.themes = new Torus.classes.Extension('themes', -4); Torus.ui.themes.text = 'Themes'; Torus.ui.themes.dir = { 'binary': { url: 'http://localhost:8080/wiki/MediaWiki:Torus.js/ui/themes/binary.css?action=raw&ctype=text/css', name: 'Binary', loaded: true, },	'creampuff': { url: 'http://localhost:8080/wiki/MediaWiki:Torus.js/ui/themes/creampuff.css?action=raw&ctype=text/css', name: 'Creampuff', loaded: true, },	'default': { url: 'http://localhost:8080/wiki/MediaWiki:Torus.js/ui/themes/default.css?action=raw&ctype=text/css', name: 'Default', loaded: true, },	'plain': { url: 'http://localhost:8080/wiki/MediaWiki:Torus.js/ui/themes/plain.css?action=raw&ctype=text/css', name: 'Plain', loaded: true, }, }; Torus.ui.themes.selected = 'default'; Torus.ui.themes.html = document.createDocumentFragment; Torus.ui.themes.rebuild = function { var table = document.createElement('table'); table.id = 'torus-themes-table'; Torus.ui.ids['themes-table'] = table; for(var i in Torus.ui.themes.dir) { if(!Torus.ui.themes.dir[i].loaded) { Torus.util.load_css(Torus.ui.themes.dir[i].url); Torus.ui.themes.dir[i].loaded = true; }		var tr = document.createElement('tr'); var td = document.createElement('td'); td.className = 'border2'; var radio = document.createElement('input'); radio.id = 'torus-theme-' + i;					Torus.ui.ids['theme-' + i] = radio; radio.className = 'torus-theme-radio'; radio.type = 'radio'; radio.name = 'torus-theme'; radio.value = i;					radio.addEventListener('click', Torus.ui.themes.click_theme); if(i == Torus.ui.themes.selected) {radio.checked = true;} td.appendChild(radio); var label = document.createElement('label'); label.setAttribute('for', 'torus-theme-' + i); label.className = 'torus-theme-label'; label.textContent = Torus.ui.themes.dir[i].name; td.appendChild(label); tr.appendChild(td); var td = document.createElement('td'); td.className = 'border2'; var preview = document.createElement('table'); preview.id = 'torus-preview-' + i;					Torus.ui.ids['preview-' + i]; preview.className = 'torus-theme-preview'; for(var j = 1; j <= 5; j++) { var cell = document.createElement('td'); cell.className = 'torus-theme-cell bg' + j;							if(j <= 2) {cell.className += ' border1';} else if(j == 3) {cell.className += ' border2';} else {cell.className += ' border3';} if(j >= 2 && j <= 4) { var text1 = document.createElement('div'); text1.className = 'text1'; text1.textContent = Torus.i18n.text('themes-text'); cell.appendChild(text1); var text2 = document.createElement('div'); text2.className = 'text2'; text2.textContent = Torus.i18n.text('themes-link'); cell.appendChild(text2); var text3 = document.createElement('div'); text3.className = 'text3'; text3.textContent = Torus.i18n.text('themes-away'); cell.appendChild(text3); var text4 = document.createElement('div'); text4.className = 'text4'; text4.textContent = Torus.i18n.text('themes-ping'); cell.appendChild(text4); }						preview.appendChild(cell); }				td.appendChild(preview); tr.appendChild(td); table.appendChild(tr); }	Torus.ui.themes.html.appendChild(table); }; Torus.ui.themes.select = function(theme) { Torus.ui.window.classList.remove('theme-' + Torus.ui.themes.selected); Torus.ui.themes.selected = theme; Torus.ui.window.classList.add('theme-' + theme); //Torus.ui.themes.rebuild; //FIXME: this is a dumb way of making sure the radio button is in the right place }; Torus.ui.themes.add = function(name, obj) { if(!obj.url) {throw new Error('Tried to add theme `' + name + '`, but didn\'t provide a url');} if(!obj.name) {obj.name = Torus.util.cap(name);} Torus.ui.themes.dir[name] = obj; Torus.ui.themes.rebuild; }; Torus.ui.themes.click_theme = function(event) {Torus.ui.themes.select(this.value);}; Torus.ui.themes.render = function(event) { Torus.ui.ids['window'].appendChild(Torus.ui.themes.html); }; Torus.ui.themes.unrender = function(event) { Torus.ui.themes.html = event.old_window; }; Torus.ui.themes.load = function(event) { Torus.ui.themes.select('default'); Torus.ui.themes.rebuild; }; Torus.ui.themes.add_listener('ui', 'activate', Torus.ui.themes.render); Torus.ui.themes.add_listener('ui', 'deactivate', Torus.ui.themes.unrender); Torus.add_listener('window', 'load', Torus.ui.themes.load); //FIXME: some kind of defaults, with option to restore defaults Torus.options['messages-general-max'] = 200; Torus.options['messages-general-rejoins'] = false; Torus.options['messages-general-timezone'] = 0; Torus.options['misc-connection-default_rooms'] = ''; Torus.options['misc-connection-local'] = true; Torus.options['misc-user_colors-enabled'] = true; Torus.options['misc-user_colors-hue'] = 0; Torus.options['misc-user_colors-sat'] = .7; Torus.options['misc-user_colors-val'] = .6; Torus.options['misc-links-chat'] = true; Torus.options['misc-links-target'] = '_blank'; new Torus.classes.Extension('options', -2); Torus.ext.options.text = 'Options'; Torus.ext.options.ui = { sidebar: document.createDocumentFragment }; //FIXME: fill ext.options.dir with elements from a for i in Torus.options Torus.ext.options.selected = 'messages'; Torus.ext.options.dir = {}; Torus.ext.options.types = {}; Torus.ext.options.dir.messages = { label: 'options-messages', general: { label: 'options-messages-general', max: { type: 'number', label: 'options-messages-general-max', help: '', //TODO: },		rejoins: { type: 'boolean', label: 'options-messages-general-rejoins', help: '', //TODO: },		timezone: { type: 'number', label: 'options-messages-general-timezone', help: '', //TODO: },	}, }; Torus.ext.options.dir.misc = { label: 'options-misc', connection: { label: 'options-misc-connection', default_rooms: { type: 'text', label: 'options-misc-connection-default_rooms', help: '', //TODO: },		local: { type: 'boolean', label: 'options-misc-connection-local', help: '', //TODO: },	},	user_colors: { label: 'options-misc-user_colors', enabled: { type: 'boolean', },		hue: { type: 'number', label: 'options-misc-user_colors-hue', help: '', //TODO: },		sat: { type: 'number', label: 'options-misc-user_colors-sat', help: '', //TODO: },		val: { type: 'number', label: 'options-misc-user_colors-val', help: '', //TODO: },	},	links: { label: 'options-misc-links', chat: { type: 'boolean', label: 'options-misc-links-chat', help: '', //TODO: },		target: { type: 'string', label: 'options-misc-links-target', help: '', //TODO: },	}, }; Torus.ext.options.rebuild = function { Torus.ext.options.ui = {}; Torus.ext.options.ui.sidebar = document.createDocumentFragment; for(var i in Torus.ext.options.dir) { var li = document.createElement('li'); li.className = 'torus-sidebar-button'; if(i == Torus.ext.options.selected) {li.classList.add('torus-sidebar-button-selected');} li.setAttribute('data-id', i); li.textContent = Torus.i18n.text(Torus.ext.options.dir[i].label); li.addEventListener('click', Torus.ext.options.click_sidebar); Torus.ext.options.ui.sidebar.appendChild(li); var frag = document.createDocumentFragment; for(var j in Torus.ext.options.dir[i]) { if(j == 'label') {continue;} var fieldset = document.createElement('fieldset'); fieldset.id = 'torus-option-set-' + i + '-' + j;				Torus.ui.ids['option-set-' + i + '-' + j] = fieldset; var legend = document.createElement('legend'); if(Torus.ext.options.dir[i][j].enabled && Torus.ext.options.dir[i][j].enabled.type == 'boolean') { //FIXME: target var label = document.createElement('label'); label.setAttribute('for', 'torus-option-value-' + i + '-' + j + '-enabled'); label.textContent = Torus.i18n.text(Torus.ext.options.dir[i][j].label); legend.appendChild(label); legend.appendChild(document.createTextNode(' ')); legend.appendChild(Torus.ext.options.types['boolean'](i + '-' + j + '-enabled')); }					else {legend.textContent = Torus.i18n.text(Torus.ext.options.dir[i][j].label);} fieldset.appendChild(legend); for(var k in Torus.ext.options.dir[i][j]) { if(k == 'label' || k == 'enabled') {continue;} if(Torus.ext.options.dir[i][j][k].target) {var option = Torus.ext.options.dir[i][j][k].target;} else {var option = i + '-' + j + '-' + k;} var div = document.createElement('div'); div.id = 'torus-option-value-' + option; Torus.ui.ids['option-value-' + option]; var label = document.createElement('label'); label.setAttribute('for', 'torus-option-value-' + option + '-input'); label.textContent = Torus.i18n.text(Torus.ext.options.dir[i][j][k].label); div.appendChild(label); div.appendChild(document.createTextNode(': ')); div.appendChild(Torus.ext.options.types[Torus.ext.options.dir[i][j][k].type](option)); fieldset.appendChild(div); }			frag.appendChild(fieldset); }		Torus.ext.options.ui['group_' + i] = frag; }	if(Torus.ui.active == Torus.ext.options) { Torus.util.empty(Torus.ui.ids['sidebar']); Torus.util.empty(Torus.ui.ids['window']); Torus.ext.options.render(Torus.ext.options.selected); } } Torus.ext.options.render = function(group) { if(!group || typeof group == 'object') {group = Torus.ext.options.selected;} //group == object when ui.activate fires if(Torus.ui.ids['window'].firstChild) { //an options group is already rendered, put it back Torus.ext.options.ui['group_' + Torus.ext.options.selected] = Torus.util.empty(Torus.ui.ids['window']); }	for(var i = 0; i < Torus.ui.ids['sidebar'].children.length; i++) { if(Torus.ui.ids['sidebar'].children[i].getAttribute('data-id') == group) {Torus.ui.ids['sidebar'].children[i].classList.add('torus-sidebar-button-selected');} else {Torus.ui.ids['sidebar'].children[i].classList.remove('torus-sidebar-button-selected');} }	Torus.ui.ids['window'].appendChild(Torus.ext.options.ui['group_' + group]); Torus.ui.ids['sidebar'].appendChild(Torus.ext.options.ui.sidebar); if(!group) {Torus.save_options;} //FIXME: when does this happen? else {Torus.ext.options.selected = group;} } Torus.ext.options.unrender = function(event) { Torus.ext.options.ui.sidebar = event.old_sidebar; Torus.ext.options.ui['group_' + Torus.ext.options.selected] = event.old_window; Torus.save_options; } Torus.ext.options.types['text'] = function(option) { var textarea = document.createElement('textarea'); textarea.id = 'torus-option-value-' + option + '-input'; Torus.ui.ids['option-value-' + option + '-input'] = textarea; textarea.className = 'torus-option-text'; textarea.rows = 6; textarea.value = Torus.options[option]; textarea.setAttribute('data-id', option); textarea.addEventListener('blur', Torus.ext.options.blur_textarea); return textarea; } Torus.ext.options.types['string'] = function(option) { var input = document.createElement('input'); input.id = 'torus-option-value-' + option + '-input'; Torus.ui.ids['option-value-' + option + '-input'] = input; input.className = 'torus-option-string'; input.type = 'text'; input.value = Torus.options[option]; input.setAttribute('data-id', option); input.addEventListener('blur', Torus.ext.options.blur_string); return input; } Torus.ext.options.types['number'] = function(option) { var input = document.createElement('input'); input.id = 'torus-option-value-' + option + '-input'; Torus.ui.ids['option-value-' + option + '-input'] = input; input.className = 'torus-option-number'; input.type = 'number'; input.value = Torus.options[option]; input.setAttribute('data-id', option); input.addEventListener('blur', Torus.ext.options.blur_number); return input; } Torus.ext.options.types['boolean'] = function(option) { var checkbox = document.createElement('input'); checkbox.id = 'torus-option-value-' + option + '-input'; Torus.ui.ids['option-value-' + option + '-input'] = checkbox; checkbox.className = 'torus-option-boolean'; checkbox.type = 'checkbox'; checkbox.checked = Torus.options[option]; checkbox.setAttribute('data-id', option); checkbox.addEventListener('change', Torus.ext.options.click_boolean); return checkbox; } Torus.ext.options.blur_textarea = function {Torus.options[this.getAttribute('data-id')] = this.value;} Torus.ext.options.blur_string = function {Torus.options[this.getAttribute('data-id')] = this.value;} Torus.ext.options.blur_number = function {Torus.options[this.getAttribute('data-id')] = this.value * 1;} Torus.ext.options.click_boolean = function {Torus.options[this.getAttribute('data-id')] = this.checked;} Torus.ext.options.click_sidebar = function {Torus.ext.options.render(this.getAttribute('data-id'));} Torus.ext.options.add_listener('ui', 'activate', Torus.ext.options.render); Torus.ext.options.add_listener('ui', 'deactivate', Torus.ext.options.unrender); Torus.add_listener('ext', 'load_options', Torus.ext.options.rebuild); window.addEventListener('beforeunload', Torus.unload); if(document.readyState == 'complete') {Torus.onload;} else {window.addEventListener('load', Torus.onload);}