/* $Id: onet6.js 1492 2009-03-12 17:35:30Z  $
 *
 * This file is meant to be used in "compressed" form so I'm not saving many
 * bytes and opting for clearer code where appropriate. While the site aspect
 * is in the works I won't be publishing it compressed, though. A full version
 * should always be made available once development gets slowed down.
 *
 * It is also processed by JSLint <http://www.jslint.com/>.
 *
 * Note it requires base2.DOM to be loaded and siteURL (String)
 * and localDomains (Array) initialised.
 */

var onet = {
    contentScope: null,         // HTMLElement#content
    localDomains: [],  // Array of RegExp's of the site-local domains
    siteUrl: '',      // Base URL of the site
    // {{{ // init() Initialise all of onet's stuff
    init: function() {
        // No W3C DOM no party :p
        if (!document.getElementById || !document.getElementsByTagName) { return; }
        // base2.DOM is also mandatory
        if (!base2 || !base2.DOM) { return; }

        var self=onet; // this would be window because we will run on onload

        base2.DOM.bind(document);

        self.contentScope = document.getElementById('content');

        self.extensions();
        self.styleExternalLinks();
        self.hlCommentformRows();
        self.autoHideSections();
    } // }}} // init()
    ,
    // {{{ // addLoadEvent() Adds an event to window.onload, based on
    // Simon Willison's code
    addLoadEvent: function(func) {
        var prevOnLoad = window.onload;
        if ('function' !== typeof prevOnLoad) {
            window.onload = func;
        }
        else {
            window.onload = function() {
                if (prevOnLoad) { prevOnLoad(); }
                func();
            };
        }
    } // }}} // addLoadEvent()
    ,
    // {{{ // extensions() Extend already existing classes with non-standard methods
    extensions: function() { 
        // Array.push()
        if (!Array.prototype.push) {
            Array.prototype.push = function(newElement) {
                this[this.length] = newElement;
            };
        }
        // Array.forEach()
        if (!Array.prototype.forEach) {
            Array.prototype.forEach = function(func) {
                for (var i=0; i<this.length; ++i) {
                    func(this[i]);
                }
            };
        }
        /*
        // Array.intersect() // See http://snippetsnap.com/snippets/552-Javascript-Array-Intersect
        if (!Array.prototype.intersect) {
            Array.prototype.intersect = function(arr) {
                return this.findAll( function(element) { return arr.include(element); } );
            }
        }
        */

        // HTMLElement.innerText_() when retrieved with base2
        // like .innerHTML but with tags stripped
        // Note gecko could use __defineGetter__ and textContent, but
        // this is probably more X-Browser
        if (base2) {
            base2.DOM.HTMLElement.prototype.innerText_ = function() {
                return this.innerHTML.replace(/<.*?>/g, '');
            };
        }
    } // }}} // extensions()
    ,
    // {{{ // styleExternalLinks() Add some extra styling to links considered off-site
    styleExternalLinks: function() {
        // Note that in the context of cssQuery href's are verbatim while in
        // the context of raw DOM/JS hrefs are fully qualified always.
// FIXME: Why?:       // Using cssQuery :not():not() doesn't seem to work, and using
        // a JS match we need to test unequivocally for local
        // only URLs, that's why I add the site URL to this.localDomains as a RE

        // Add class external to links considered off-site...
        // XXX: Opera9 chokes on this
        var external = document.matchAll('a:not([href^="'+ this.siteUrl + '"])');

        external = external.filter(
            function (e) {
                // Filter out all inlined javascript
                if (/^javascript:/.test(e.href)) {
                    return false;
                }

                // Links with class "noextsym" are also excluded
                if (e.hasClass('noextsym')) {
                    return false;
                }

                // Filter out any extra domains considered local
                for (var i=0; i<onet.localDomains.length; ++i) { // this!=onet
                    if (onet.localDomains[i].test(e.href)) { return false; }
                }

                return true;
            }
        ); // Note external became an array after filtering

        external.forEach( function(e) {
            e.addClass('external');
        });
    } // }}} // styleExternalLinks()
    ,
    // {{{ // hlCommentformRows() Highlights the row of each input in the "add comments" form
    hlCommentformRows: function () {
        document.matchAll('#wp-commentform input[type=text]').forEach(
            function (e) {
                e.onfocus = function(x) {
                    base2.DOM.bind(this.parentNode);
                    this.parentNode.addClass('focused-row');
                };
                e.onblur = function(x) {
                    this.parentNode.removeClass('focused-row');
                };
            }
        );
    } // }}} // hlCommentformRows()
    ,
    /*************** HIDE SECTIONS ************* {{{
     *  ...And allow showing them when clicking a link that points to the anchor.
     *     Based on the explanations on A list apart's article
     *      «Let them eat cake» <http://alistapart.com/articles/eatcake>
     * Usage: wrap sections in a <div class="sectionable" /> with an id,
     *     create an index with links that point to the sections and with
     *     a rel attribute that includes section. Links to subsections should
     *     have a rel with subsection (they'll display the parent section and
     *     its subsections).
     *     Hiding stuff is not done directly but by adding classes. Note
     *     this is required to print correctly/nicely (see the article
     *     for details).
     *     Two classes are used:
     *     navelement : indicates something is only useful to navigate
     *                  the page (i.e.: shouldn't be displayed in print)
     *     nodisplay : shouldn't be displayed in non-print media
     *     Additionally, if a link to #content (to top) exists, prev/next links
     *     are added
     *
     * Tested in Firefox2/Linux, IE7/Win, Opera9/Win and Safari3/Win.
     * No browser-specific hacks are used here.
     */

    // {{{ // _createLink() Helper function for _addRelativeNavLinks(), creates a new link
    _createLink: function(parentNode, baseLink, title, textData) {
        var newLink = document.createElement('A');
        newLink.href = baseLink.getAttribute('href');
        newLink.onclick = baseLink.onclick;
        newLink.title = title;
        newLink.appendChild( document.createTextNode( textData ) );
        parentNode.appendChild( newLink );
    } // }}} /// _createLink()
    ,
    // {{{ // _addRelativeNavLinks() Adds the previous/next navigational links
    // to any "back-to-top" links. Helper function for autoHideSections()
    _addRelativeNavLinks: function(section,prevId,nextId) {
        var backLink = document.matchSingle('#'+section.id +' a[href$="#content"]');
        if (null === backLink) { return; }
        backLink.addClass('navelement nodisplay');

        var pNode = backLink.parentNode;
        if (!(prevId || nextId)) { return; }

        pNode.appendChild( document.createTextNode('   ') );
        // Wrap the newer links and don't show them in print
        var wrapper = document.createElement('SPAN');
        pNode.appendChild(wrapper);
        pNode = wrapper;
        pNode.addClass('navelement');

        if (null !== prevId) { // FIXME: <OL>: Note doesn't seem to work with comma selectors
            var prevLink = document.matchSingle('ul a[href$="#'+prevId+'"]', this.contentScope);
            this._createLink(pNode, prevLink, prevLink.innerText_(), '\u00ab\u00b7'); // laquo nbsp
        }
        if (prevId && nextId) {
            // nbsp middot nbsp
            pNode.appendChild(document.createTextNode('\u00a0\u00b7\u00a0'));
        }
        if (null !== nextId) {
            var nextLink = document.matchSingle('ul a[href$="#'+nextId+'"]', this.contentScope);
            this._createLink(pNode, nextLink, nextLink.innerText_(), '\u00b7\u00bb'); // nbsp raquo
        }
    } // }}} // addRelativeNavLinks()
    ,
    // {{{ // autoHideSections() Automatically hide any sections defined as
    // per the explanations above, except for the active one
    autoHideSections: function() {
        var secs = document.matchAll('.sectionable', this.contentScope);
        if (0 === secs.length) { return; }

        var reSection  = /\s*section\s*/i;
        var reBookmark = /\s*bookmark\s*/i;

        // Modify links to sections to show the associated section
        for (var i=0; i<secs.length; ++i) {
            var e=secs.item(i);

            // Get the first link to the section...
            var link = document.matchSingle('a[href$="#' + e.id + '"]', this.contentScope);
            if (null === link) { continue; }

            // Only if it has rel with "section" and bookmark
            var rel=link.getAttribute('rel');
            if ('string' != typeof rel || !reSection.test(rel) || !reBookmark.test(rel)) {
                continue;
            }

            // Hide the element (*only*) if we found a valid link to show it
            e.addClass('nodisplay');

            // Can't set onclick in text form in IE
            link.onclick = function(e) {
                onet.switchActiveSection(this.href.replace(/^.*#/, ''));
            };

            // Match also any sub-sections
            var li=link.parentNode;
            if ('LI' != li.tagName) { continue; }

            // Select all sub-lists (ol or ul)
            if ('' === link.id) { link.setAttribute('id', 'JS--linkTo-'+e.id); }
            var subLinks = document.matchAll('a#' + link.id + ' + ul li a, '+
                                             'a#' + link.id + ' + ol li a',
                                             this.contentScope);
            if (0 === subLinks.length) { continue; }

            subLinks.forEach(
                function(curLink) {
                    curLink.setAttribute('_section', e.id);
                    curLink.onclick = function(X){
                        onet.switchActiveSection(this.getAttribute('_section'));
                    };
                }
            );
        }

        // Add prev/next navigation links replacing any back-to-top links
        for (var s=0;s<secs.length;++s){
            var prevSId=(s>0) ? secs.item(s-1).id : null;
            var nextSId=(s<(secs.length-1)) ? secs.item(s+1).id : null;
            //addNextSectionNavLink(secs.item(s).id, prevSId, nextSId);
            this._addRelativeNavLinks(secs.item(s), prevSId, nextSId);
        }
        // assert secs.length > 0

        // If a section is pointed in the URL (with a hash), show it and jump to it
        var selSect = secs.item(0);
        var jumpTo = null;
        // Does the url point to a section?...
        if (-1 != window.location.href.indexOf('#')) {
            var pointedId=window.location.hash.replace(/^#/,'');
            if (pointedId && pointedId != 'content') {
                /*
                   var pointedElement = document.getElementById(pointedId);
                   base2.DOM.bind(pointedElement);
                 */ // Same as...
                var pointedElement = document.matchSingle('#'+pointedId,this.contentScope);
                if (null !== pointedElement) {
                    if (pointedElement.hasClass('sectionable')) {
                        // No need to search further
                        selSect = pointedElement;
                    }
                    else {
                        document.matchAll('a[href$="#'+pointedId+'"]',this.contentScope).forEach(
                            function followLink(currentLink) {
                                if ('' !== currentLink.getAttribute('_section')) {
                                    currentLink.onclick();
                                }
                            }
                        );//forEach
                    } // if-else
                    jumpTo = pointedId;
                } // if
            } // if
        }// if

        selSect.removeClass('nodisplay');//re-show the appropriate section
        if (jumpTo) {
            // Is this safe???
            window.location.hash='#'+jumpTo;
        }

    } // }}} // autoHideSections()
    ,
    // {{{ // switchActiveSection() Interactively show a section and hide the others
    switchActiveSection: function(divId) {
        // Show the appropriate div...
        document.getElementById(divId).removeClass('nodisplay');
        // ...update the link of the newly shown div...
        //document.getElementById(linkId).setAttribute('href', '#'+divId);
        // ...and hide the others
        document.getElementsByClassName('sectionable').forEach(
            function(e){
                // Hide all except the target
                if (e.id != divId) { e.addClass('nodisplay'); }
            }
        );
    } // }}} // switchActiveSection()

    // }}} // HIDE SECTIONS-RELATED STUFF
};

onet.addLoadEvent(onet.init);

// http://vim.sourceforge.net/tips/tip.php?tip_id=83
// vim:set ts=4 sw=4 smarttab et filetype=javascript foldmethod=marker:
