William Bowling is sharing code with you

Bitbucket is a code hosting site. Unlimited public and private repositories. Free for small teams.

Don't show this again

wbowling / adium (fork of adium / adium)

Fork of Adium for patches/improvements

Clone this repository (size: 338.7 MB): HTTPS / SSH
hg clone https://bitbucket.org/wbowling/adium
hg clone ssh://hg@bitbucket.org/wbowling/adium

adium / Plugins / WebKit Message View / Template.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
        <base href="%@">
        <script type="text/javascript" defer="defer">
                // NOTE:
                // 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();
                        range.selectNode(node);
                        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() {
                        var self = this;
                        this.fragment = document.createDocumentFragment();
                        this.timeoutID = 0;
                        this.coalesceRounds = 0;
                        this.isCoalescing = false;
                        this.isConsecutive = undefined;
                        this.shouldScroll = undefined;
                        
                        function outputHTML() {
                                var insert = document.getElementById("insert");
                                if(!!insert && self.isConsecutive) {
                                        insert.parentNode.replaceChild(self.fragment, insert);
                                } else {
                                        if(insert)
                                                insert.parentNode.removeChild(insert);
                                        // insert the documentFragment into the live DOM
                                        document.getElementById("Chat").appendChild(self.fragment);
                                }
                                alignChat(self.shouldScroll);
                                
                                // reset state to empty/non-coalescing
                                self.shouldScroll = undefined;
                                self.isConsecutive = undefined;
                                self.isCoalescing = false;
                                self.coalesceRounds = 0;
                        }
                        
                        // 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");
                                if(insert)
                                        insert.parentNode.removeChild(insert);
                        }
                        
                        // (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)
                                        self.cancel();
                        }
                        
                        // if we need to append content into an insertion div,
                        // we need to clear the buffer and cancel the timeout.
                        this.cancel = function() {
                                if(self.isCoalescing) {
                                        window.clearTimeout(self.timeoutID);
                                        outputHTML();
                                }
                        }
                        
                        
                        // 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
                                if(self.isConsecutive) {
                                        self.cancel();
                                }
                                self.isConsecutive = false;
                                rmInsertNode();
                                var node = createHTMLNode(html);
                                self.fragment.appendChild(node);
                                
                                node = null;

                                if(shouldScroll) self.shouldScroll = shouldScroll;
                                self.coalesce();
                        }
                        
                        this.appendNext = function(html, shouldScroll) {
                                if(undefined === self.isConsecutive)
                                        self.isConsecutive = true;
                                var node = createHTMLNode(html);
                                var insert = self.fragment.querySelector("#insert");
                                if(insert) {
                                        insert.parentNode.replaceChild(node, insert);
                                } else {
                                        self.fragment.appendChild(node);
                                }
                                node = null;
                                if(shouldScroll)
                                        self.shouldScroll = shouldScroll;
                                self.coalesce();
                        }
                        
                        this.replaceLast = function (html, shouldScroll) {
                                rmInsertNode();
                                var node = createHTMLNode(html);
                                var lastMessage = self.fragment.lastChild;
                                lastMessage.parentNode.replaceChild(node, lastMessage);
                                node = null;
                                if(shouldScroll)
                                        self.shouldScroll = shouldScroll;
                        }
                }
                var coalescedHTML;

                //Appending new content to the message view
                function appendMessage(html) {
                        var shouldScroll;
                        
                        // Only call nearBottom() if should scroll is undefined.
                        if(undefined === coalescedHTML.shouldScroll) {
                                shouldScroll = nearBottom();
                        } else {
                                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){
                        var shouldScroll;
                        if(undefined === coalescedHTML.shouldScroll) {
                                shouldScroll = nearBottom();
                        } else {
                                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){
                        var shouldScroll;
                        // only replace messages if we're already coalescing
                        if(coalescedHTML.isCoalescing){
                                if(undefined === coalescedHTML.shouldScroll) {
                                        shouldScroll = nearBottom();
                                } else {
                                        shouldScroll = coalescedHTML.shouldScroll;
                                }
                                coalescedHTML.replaceLast(html, shouldScroll);
                        } else {
                                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");
                                if(insert){
                                        var parentNode = insert.parentNode;
                                        parentNode.removeChild(insert);
                                        var lastMessage = document.getElementById("Chat").lastChild;
                                        document.getElementById("Chat").removeChild(lastMessage);
                                }

                                //Now append the message itself
                                appendHTML(html);

                                alignChat(shouldScroll);
                        }
                }

                //Auto-scroll to bottom.  Use nearBottom to determine if a scrollToBottom is desired.
                function nearBottom() {
                        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\">";
                        if( url.length ) 
                                code += "@import url( \"" + url + "\" );";
                        code += "</style>";
                        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() {
                        var node = event.target;
                        if (node.tagName.toLowerCase() != 'img')
                                return;
                                
                        imageSwap(node, false);
                }
                
                /* Converts textual emoticons to images if textToImagesFlag is true, otherwise vice versa */
                function imageSwap(node, textToImagesFlag) {
                        var shouldScroll = nearBottom();
                        
                        var images = [node];
                        if (event.altKey) {
                                while (node.id != "Chat" && node.parentNode.id != "Chat")
                                        node = node.parentNode;
                                images = node.querySelectorAll(textToImagesFlag ? "a" : "img");
                        }
                        
                        for (var i = 0; i < images.length; i++) {
                                textToImagesFlag ? textToImage(images[i]) : imageToText(images[i]);
                        }
                        
                        alignChat(shouldScroll);
                }

                function textToImage(node) {
                        if (!node.getAttribute("isEmoticon"))
                                return;
                        //Swap the image/text
                        var img = document.createElement('img');
                        img.setAttribute('src', node.getAttribute('src'));
                        img.setAttribute('alt', node.firstChild.nodeValue);
                        img.className = node.className;
                        node.parentNode.replaceChild(img, node);
                }
                
                function imageToText(node)
                {
                        if (client.zoomImage(node) || !node.alt)
                                return;
                        var a = document.createElement('a');
                        a.setAttribute('onclick', 'imageSwap(this, true)');
                        a.setAttribute('src', node.getAttribute('src'));
                        a.setAttribute('isEmoticon', true);
                        a.className = node.className;
                        var text = document.createTextNode(node.alt);
                        a.appendChild(text);
                        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;

                        if (windowHeight > 0) {
                                var contentElement = document.getElementById('Chat');
                                var contentHeight = contentElement.offsetHeight;
                                if (windowHeight - contentHeight > 0) {
                                        contentElement.style.position = 'relative';
                                        contentElement.style.top = (windowHeight - contentHeight) + 'px';
                                } else {
                                        contentElement.style.position = 'static';
                                }
                        }

                        if (shouldScroll) scrollToBottom();
                }

                window.onresize = function windowDidResize(){
                        alignChat(true/*nearBottom()*/); //nearBottom buggy with inactive tabs
                }
                
                function adiumOnLoad() {
                        alignChat(true);
                        coalescedHTML = new CoalescedHTML();
                }
        </script>

        <style type="text/css">
                .actionMessageUserName { display:none; }
                .actionMessageBody:before { content:"*"; }
                .actionMessageBody:after { content:"*"; }
                hr#focus { border: 0; border-bottom: 1px solid red; width: 25%%; margin: 0 auto 0 auto; }
                * { word-wrap:break-word; }
                img.scaledToFitImage { height: auto; max-width: 100%%; }
        </style>

        <!-- This style is shared by all variants. !-->
        <style id="baseStyle" type="text/css" media="screen,print">
                %@
        </style>

        <!-- Although we call this mainStyle for legacy reasons, it's actually the variant style !-->
        <style id="mainStyle" type="text/css" media="screen,print">
                @import url( "%@" );
        </style>

</head>
<body onload="adiumOnLoad();" style="==bodyBackground==">
%@
<div id="Chat">
</div>
%@
</body>
</html>