<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <script type="text/javascript" defer="defer"> // Any percent signs in this file must be escaped! // Use two escape signs (%%) to display it, this is passed through a format call! function appendHTML(html) { var node = document.getElementById("Chat"); var range = document.createRange(); var documentFragment = range.createContextualFragment(html); node.appendChild(documentFragment); // a coalesced HTML object buffers and outputs DOM objects en masse. // saves A LOT of CSS recalculation time when loading many messages. // (ex. a long twitter timeline) function CoalescedHTML() { this.fragment = document.createDocumentFragment(); this.isCoalescing = false; this.isConsecutive = undefined; this.shouldScroll = undefined; var appendElement = function (elem) { document.getElementById("Chat").appendChild(elem); var insert = document.getElementById("insert"); if(!!insert && self.isConsecutive) { insert.parentNode.replaceChild(self.fragment, insert); insert.parentNode.removeChild(insert); // insert the documentFragment into the live DOM appendElement(self.fragment); alignChat(self.shouldScroll); // reset state to empty/non-coalescing self.shouldScroll = undefined; self.isConsecutive = undefined; self.isCoalescing = false; // creates and returns a new documentFragment, containing all content nodes // which can be inserted as a single node. function createHTMLNode(html) { var range = document.createRange(); range.selectNode(document.getElementById("Chat")); return range.createContextualFragment(html); // removes first insert node from the internal fragment. function rmInsertNode() { var insert = self.fragment.querySelector("#insert"); insert.parentNode.removeChild(insert); function setShouldScroll(flag) { if(flag && undefined === self.shouldScroll) self.shouldScroll = flag; // hook in a custom method to append new data this.setAppendElementMethod = function (func) { if(typeof func === 'function') // (re)start the coalescing timer. // we wait 25ms for a new message to come in. // If we get one, restart the timer and wait another 10ms. // If not, run outputHTML() // We do this a maximum of 400 times, for 10s max that can be spent // coalescing input, since this will block display. this.coalesce = function() { window.clearTimeout(self.timeoutID); self.timeoutID = window.setTimeout(outputHTML, 25); self.isCoalescing = true; self.coalesceRounds += 1; if(400 < self.coalesceRounds) // if we need to append content into an insertion div, // we need to clear the buffer and cancel the timeout. this.cancel = function() { window.clearTimeout(self.timeoutID); // coalased analogs to the global functions this.append = function(html, shouldScroll) { // if we started this fragment with a consecuative message, // cancel and output before we continue self.isConsecutive = false; var node = createHTMLNode(html); self.fragment.appendChild(node); setShouldScroll(shouldScroll); this.appendNext = function(html, shouldScroll) { if(undefined === self.isConsecutive) self.isConsecutive = true; var node = createHTMLNode(html); var insert = self.fragment.querySelector("#insert"); insert.parentNode.replaceChild(node, insert); self.fragment.appendChild(node); setShouldScroll(shouldScroll); this.replaceLast = function (html, shouldScroll) { var node = createHTMLNode(html); var lastMessage = self.fragment.lastChild; lastMessage.parentNode.replaceChild(node, lastMessage); setShouldScroll(shouldScroll); //Appending new content to the message view function appendMessage(html) { // Only call nearBottom() if should scroll is undefined. if(undefined === coalescedHTML.shouldScroll) { shouldScroll = nearBottom(); shouldScroll = coalescedHTML.shouldScroll; appendMessageNoScroll(html, shouldScroll); function appendMessageNoScroll(html, shouldScroll) { shouldScroll = shouldScroll || false; // always try to coalesce new, non-griuped, messages coalescedHTML.append(html, shouldScroll) function appendNextMessage(html){ if(undefined === coalescedHTML.shouldScroll) { shouldScroll = nearBottom(); shouldScroll = coalescedHTML.shouldScroll; appendNextMessageNoScroll(html, shouldScroll); function appendNextMessageNoScroll(html, shouldScroll){ shouldScroll = shouldScroll || false; // only group next messages if we're already coalescing input coalescedHTML.appendNext(html, shouldScroll); function replaceLastMessage(html){ // only replace messages if we're already coalescing if(coalescedHTML.isCoalescing){ if(undefined === coalescedHTML.shouldScroll) { shouldScroll = nearBottom(); shouldScroll = coalescedHTML.shouldScroll; coalescedHTML.replaceLast(html, shouldScroll); shouldScroll = nearBottom(); //Retrieve the current insertion point, then remove it //This requires that there have been an insertion point... is there a better way to retrieve the last element? -evands var insert = document.getElementById("insert"); var parentNode = insert.parentNode; parentNode.removeChild(insert); var lastMessage = document.getElementById("Chat").lastChild; document.getElementById("Chat").removeChild(lastMessage); //Now append the message itself //Auto-scroll to bottom. Use nearBottom to determine if a scrollToBottom is desired. return ( document.body.scrollTop >= ( document.body.offsetHeight - ( window.innerHeight * 1.2 ) ) ); function scrollToBottom() { document.body.scrollTop = document.body.offsetHeight; //Dynamically exchange the active stylesheet function setStylesheet( id, url ) { var code = "<style id=\"" + id + "\" type=\"text/css\" media=\"screen,print\">"; code += "@import url( \"" + url + "\" );"; var range = document.createRange(); var head = document.getElementsByTagName( "head" ).item(0); range.selectNode( head ); var documentFragment = range.createContextualFragment( code ); head.removeChild( document.getElementById( id ) ); head.appendChild( documentFragment ); /* Converts emoticon images to textual emoticons; all emoticons in message if alt is held */ document.onclick = function imageCheck() { if (node.tagName.toLowerCase() != 'img') /* Converts textual emoticons to images if textToImagesFlag is true, otherwise vice versa */ function imageSwap(node, textToImagesFlag) { var shouldScroll = nearBottom(); while (node.id != "Chat" && node.parentNode.id != "Chat") images = node.querySelectorAll(textToImagesFlag ? "a" : "img"); for (var i = 0; i < images.length; i++) { textToImagesFlag ? textToImage(images[i]) : imageToText(images[i]); function textToImage(node) { if (!node.getAttribute("isEmoticon")) var img = document.createElement('img'); img.setAttribute('src', node.getAttribute('src')); img.setAttribute('alt', node.firstChild.nodeValue); img.setAttribute('width', node.getAttribute('width')); img.setAttribute('height', node.getAttribute('height')); img.className = node.className; node.parentNode.replaceChild(img, node); function imageToText(node) if (client.zoomImage(node) || !node.alt) var a = document.createElement('a'); a.setAttribute('onclick', 'imageSwap(this, true)'); a.setAttribute('src', node.getAttribute('src')); a.setAttribute('isEmoticon', true); a.setAttribute('width', node.getAttribute('width')); a.setAttribute('height', node.getAttribute('height')); a.className = node.className; var text = document.createTextNode(node.alt); node.parentNode.replaceChild(a, node); //Align our chat to the bottom of the window. If true is passed, view will also be scrolled down function alignChat(shouldScroll) { var windowHeight = window.innerHeight; var contentElement = document.getElementById('Chat'); var heightDifference = (windowHeight - contentElement.offsetHeight); if (heightDifference > 0) { contentElement.style.position = 'relative'; contentElement.style.top = heightDifference + 'px'; contentElement.style.position = 'static'; if (shouldScroll) scrollToBottom(); window.onresize = function windowDidResize(){ alignChat(true/*nearBottom()*/); //nearBottom buggy with inactive tabs coalescedHTML = new CoalescedHTML(); .actionMessageUserName { display:none; } .actionMessageBody:before { content:"*"; } .actionMessageBody:after { content:"*"; } * { word-wrap:break-word; text-rendering: optimizelegibility; } img.scaledToFitImage { height: auto; max-width: 100%%; } <!-- This style is shared by all variants. !--> <style id="baseStyle" type="text/css" media="screen,print"> <!-- Although we call this mainStyle for legacy reasons, it's actually the variant style !--> <style id="mainStyle" type="text/css" media="screen,print"> <body onload="initStyle();" style="==bodyBackground==">