User:Slevinski/signwriting viewer.js

From Wikimedia Incubator

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
/**
 * SignWriting Styled Viewer
 * 
 * Copyright 2007-2013 Stephen E Slevinski Jr
 * Steve (Slevin@signpuddle.net)
 *
 * This file is part of SWIS: the SignWriting Icon Server.
 *
 * SWIS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * SWIS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with SWIS.  If not, see <http://www.gnu.org/licenses/>.
 *
 * END Copyright
 *
 * @copyright 2007-2013 Stephen E Slevinski Jr
 * @author Steve (slevin@signpuddle.net)
 * @license http://opensource.org/licenses/GPL-2.0 GPL
 * @access public
 * @version 1.0.0.rc.3
 * @filesource
 *
 */
 
/**
 *   define a function that uses regular expression to modify a node,
 *   then crawl the document object model for TEXT elements and apply the function
 */
 
(function(){ // Use this instead of .ready, so that styles can be applied before the body starts loading, if this is loaded from the head.
 
/*
 * Miscellaneous bugs:
 * IE8 needs mutation events work-around.
 */
 
    var newStyle = "a:hover .signwritingtext { opacity: 0.8; } \
      .signwritingtext{ \
        cursor: vertical-text; \
        background-position: 100% 0; \
        background-repeat: no-repeat; \
        background-origin: content-box; } \
      .signwritingtext span{ display: table-cell; \
        vertical-align: middle; \
        font-size: 0%; \
        color: transparent; \
        text-align: center; \
        height:inherit;} \
      a .signwritingtext, label .signwritingtext, .ui-button .signwritingtext{ cursor: inherit; }"
    ///* writing modes
    var tempDiv = document.createElement('div'), elemStyle =  tempDiv.style, 
      listSLs = [ "ase", "rsl", "ise", "mfs", "mdl", "bfi", "fse" ],
      // signLang = ( listSLs[ $.inArray( mw.config.get( "wgPageContentLanguage" ), listSLs ) ] || listSLs[ $.inArray( mw.config.get( "wgUserLanguage" ), listSLs ) ] ), 
      wmSupport = ( '-webkit-writing-mode' in elemStyle || '-moz-writing-mode' in elemStyle || '-ms-writing-mode' in elemStyle || 'writing-mode' in elemStyle );
 
    // Temporary, until Bug 56920 is resolved.
    var signLang = ( listSLs[ $.inArray( mw.config.get( "wgPageName" ).replace(/^(?:.+?:)?W[pbtqny]\/([a-z\-]{2,7})(?:\/.*)$/, "$1"), listSLs ) ] || listSLs[ $.inArray( mw.config.get( "wgUserLanguage" ), listSLs ) ] );
 
    // tooltips css
    newStyle += 
      ".swtooltip { position: absolute; min-width: 10px; min-height: 10px; pointer-events: none; padding: 4px; font-size: 70%; white-space: nowrap;" +
        // Use native tooltip styling where possible. Currently only supported by FF.
        // Chromium bug for -webkit-appearance: tooltip; is Issue 17371.
        ( "MozAppearance" in elemStyle ? "-moz-appearance: tooltip; " : "border-radius: 1px; background-color: #FFFFDD; border: 1px solid #AAAAAA; color: #333333; box-shadow: 0 0 3px rgba(0,0,0,.3); -webkit-box-shadow: 0 0 3px rgba(0,0,0,.3); opacity: 0.9; ") +
        "z-index: 1101; display: none; }"; // TODO: build a workaround for pointer-events for nonsupporting browsers (IE10<).
    if ( signLang && wmSupport ) {
        elemStyle.cssText = "-ms-writing-mode: tb-lr;";
        newStyle += "\
            body { \
              -webkit-writing-mode: vertical-lr; \
              -moz-writing-mode: vertical-lr; \
              -ms-writing-mode: tb-lr; \
              writing-mode: vertical-lr; \
            } \
            .signwritingtext { \
              display: inline-block;" +
              // IE considers vertical-align: bottom; to be block-end instead of block-start like other browsers.
              // Should really check for the actual performance. Maybe just checking if it accepts tb-lr won't cause problems?
              ( elemStyle.writingMode !== "tb-lr" ? "vertical-align: bottom;" : "vertical-align: top;" ) + "\
            } \
            #mw-content-text .signwritingtext { \
              border-left: 1px solid #DDD; \
            } \
            #mw-content-text .mw-editsection .signwritingtext { \
              border-left: 0; \
            } \
            #siteNotice { display: none } \/* temporary *\/ \
            "
        $( document ).ready( function(){
            var msgs = ({
              "ase" : "User:Yair rand/ASLMessages.js‎"
            })[ signLang ];
            msgs && importScript( msgs ); /* to be removed as soon as ASL interface is available */
            importScript( "User:Yair rand/SWSelectBox.js" ); /* Should be merged here eventually. */
        })
        importStylesheet("User:Yair rand/rotatevector.css"); /* to be removed upon resolution of Bug 9436 */
    }
    //*/
    mw.util.addCSS( newStyle );
 
var svgSupported = document.createElement('div');
svgSupported.innerHTML = '<svg/>';
svgSupported = (svgSupported.firstChild && svgSupported.firstChild.namespaceURI) == "http://www.w3.org/2000/svg" ? "svg" : "png";
 
var r = /(?:A(?:S[123][0-9a-f]{2}[0-5][0-9a-f])+)?([BLMR])[0-9]{3}x[0-9]{3}(?:S[123][0-9a-f]{2}[0-5][0-9a-f][0-9]{3}x[0-9]{3})*\s*|S38[7-9ab][0-5][0-9a-f][0-9]{3}x[0-9]{3}\s*/;
 
var swDiv = (function(){
  var div = document.createElement( "div" );
  div.className = "signwritingtext";
  div.appendChild( document.createElement( "span" ) );
  return div;
})();
 
window.signwriting_styled=(function (node) {
    var u = 'http://swis.wmflabs.org/',
        v = '1.0.0-rc.3',
        s1, s2, d, p, o = { L : -1, R : 1 }, f,
        r2 = /[0-9]{3}x[0-9]{3}/g;
 
    function rgbToHex(rgb) {
        if ( /^#[0-9a-fA-F]{6}$/.test( rgb ) ) {
            return rgb.substr( 1 );
        }
        var rgbvals = /rgba?\(([^,]+),([^,]+),([^,\)]+)/i.exec(rgb);
        if (!rgbvals) {
            // ie8< don't like to give clean color values. not much can be done with color names other than check if "black",
            // so other color names (other than white, which it defaults to) won't work. switch #XXX for #XXXXXX in JS:
            return rgb === 'black' ? '000000' : 
                /#[0-9a-fA-F]{3}$/.test(rgb) ? rgb.substr(1).replace(/([0-9a-fA-F])/g,"$1$1") : 
                    'ffffff'; // ie8< fix
        }
        var pad = function (value) {
            return (value < 16 ? '0' + value.toString( 16 ) : value.toString( 16 ) );
        };
        return pad( 0 | rgbvals[1] ) + pad( 0 | rgbvals[2] ) + pad( 0 | rgbvals[3] );
    }
 
    f = function ( node, background ) {
        for( var ex, value, color, size, nS; ex = r.exec( value = node.nodeValue ); ) {
            color = color || rgbToHex(jQuery(node.parentNode).css('color'));
            size = size || parseInt(jQuery(node.parentNode).css('font-size')) / 30 || 0.4;
            var x, x1 = 500,
                x2 = 500,
                y, y1 = 500,
                y2 = 500,
                k, m = ex[0], index = ex.index, parent = node.parentNode;
            k = ex[ 1 ];
            m.replace(r2, function ($0) {
                x = parseInt($0.slice(0, 3));
                y = parseInt($0.slice(4, 7));
                x1 = Math.min(x1, x);
                x2 = Math.max(x2, x);
                y1 = Math.min(y1, y);
                y2 = Math.max(y2, y);
            });
            if ( k === undefined ) {
                x2 = 1000 - x1;
                y2 = 1000 - y1;
            }
            var div = swDiv.cloneNode( true ), span = div.firstChild, style = div.style;
            style.marginRight = 10 * size + 'px';
            style.width = ( ( x2 + ( ( o[k] || 0 ) * 75 ) - 390 ) * size ) + 'px';
            style.height = ( y2 - y1 + 20 ) * size + 'px';
            style.backgroundImage = 'url(\'' + u + 'glyphogram.php?font=' + svgSupported + '&text=' + m.replace( /\s+$/, '' ) + '&line=' + color + '&fill=' + rgbToHex( background() ) + '&size=' + size + '\')';
            var pS = node.previousSibling;
            if ( index > 0 ) {
                node = node.splitText( index );
            } else if ( k === undefined && pS ) {
                var nowrap = document.createElement( "span" );
                nowrap.style.whiteSpace = "nowrap";
                nowrap.appendChild( pS );
                nowrap.appendChild( div );
                div = nowrap;
            }
            if ( value.length > index + m.length ) {
                nS = node.splitText( m.length );
                span.appendChild( node );
                parent.insertBefore( div, node = nS );
            } else {
                nS = node.nextSibling;
                span.appendChild( node );
                if ( nS ) {
                    parent.insertBefore( div, nS );
                } else {
                    parent.appendChild( div );
                }
                return;
            }
        }
    };
 
    function fswReplace(node, background) {
        if (node.nodeType === 3) {
            f( node, background );
        } else {
            var nodes = node.nodeName !== 'TEXTAREA' && node.nodeType !== 8 && node.childNodes;
            if (nodes) {
                var _bg, _background = function(){
                  if( _bg ) {
                    return _bg;
                  } else {
                    _bg = $( node ).css('background-color');
                    return _bg = ( _bg && _bg !== 'rgba(0, 0, 0, 0)' && _bg !== 'transparent' ) ? _bg : background();
                  }
                }
                for( var i = nodes.length; i--; ){ fswReplace(nodes[i], _background); }
            }
        }
    };
 
    if (!node || !node.nodeType) node = document.body;
    function findBackground( node ) {
        for( var background, parent = node; ( background = $( parent ).css('background-color') ) && ( background.toString() === 'rgba(0, 0, 0, 0)' || background.toString() === 'transparent' ); ) {
            parent = parent.parentNode;
        }
        return background || '#ffffff';
    }
    fswReplace(node, function(){return findBackground( node ); } );
 
    // MUTATION OBSERVERS
 
    // These things moniter every dom addition and class/style change in the document. 
    // If there's no dynamic changes in the doc, don't use them, as they have serious performance issues.
    // MOs are supported by IE11+, FF14+, Chrome, Safari 6+, Opera 15+, and a bunch of mobile stuffs.
    // DOM events are supported by IE9/10, and are used as a backup system.
 
    ///*
    if( !window.swMO && signLang ) { // don't do it more than once, don't do it when not on an ASL page to conserve performance.
        (function(){
            var MO = window.MutationObserver || window.WebKitMutationObserver,
              rgb = rgbToHex,
              rLine = /&line=[0-9A-Fa-f]+/, rFill = /&fill=[0-9A-Fa-f]+/;
            window.swMO = true;
            if( MO ) {
                (new MO(function(mutations) { 
                    // var z = new Date;
                    var mutations_ = [], uniqueElems = [];
                    mutations.forEach(function( mutation ) { // first, clear out the mutations with duplicate elements
                        if( uniqueElems.indexOf( mutation.target ) === -1 ) {
                            mutations_.push( mutation );
                            uniqueElems.push( mutation.target );
                        }
                    });
                    mutations_.forEach(function(mutation) { // too much duplication below. todo: fix.
                        if( mutation.attributeName === "style" && $( mutation.target ).is( ":animated" ) ) {
                            return;
                        }
                        $( mutation.target ).find( ".signwritingtext" ).css( "background-image", function( a, b ){
                            var j = b.replace( rLine, "&line=" + rgb( $( this ).css("color") ) ); //there's probably a faster way to do this...
                            if( j !== b ){ // don't change unless it actually needs changing
                                return j.replace( rFill, "&fill=" + rgb( findBackground( this ) ) );
                            }
                        });
                    });
                    // console.log( "TIME:", new Date - z );
                }).observe( document.body, { subtree: true, attributes: true, attributeFilter: ["class", "style"] }));
            } else {
                document.body.addEventListener && (function(){
                    var elemsToDo = [], timer = false;
                    document.body.addEventListener( "DOMAttrModified", function( e ) { // IE10<
                        if( e.attrName === "class" || e.attrName === "style" ) {
                            var target = e.target;
                            if( elemsToDo.indexOf( target ) === -1 && elemsToDo.push( target ) && timer === false ) {
                                timer = setTimeout( function() {
                                    timer = false;
                                    elemsToDo.forEach( function( elem ){
                                        if( $( elem ).is( ":animated" ) ) {
                                            return;
                                        }
                                        $( elem ).find( ".signwritingtext" ).css( "background-image", function( a, b ){
                                             var j = b.replace( rLine, "&line=" + rgb( $( this ).css("color") ) );
                                             if( j !== b ){ // don't change unless it actually needs changing
                                                 return j.replace( rFill, "&fill=" + rgb( findBackground( this ) ) );
                                             }
                                         });
                                    });
                                    elemsToDo.length = 0; // empty the array
                                }, 32 );
                            }
                        }
                    });
                })();
                // Technically there are things other than class and style that
                // can alter CSS, but it would be too heavy to moniter all that.
            }
        })();
 
        (function(){ // now for characterData and childList monitering. TODO: merge the observers? (maybe not, actually...)
            var MO = window.MutationObserver || window.WebKitMutationObserver, swcn = "signwritingtext",
              MOopts = { subtree: true, characterData: true, childList: true }, tMO, body = document.body,
              elemsToDo = [], allElems = [];
            function findstuff( addedNode ){
                var aP;
                if( addedNode.nodeType === 3 ) {
                    ( aP = addedNode.parentNode ) && elemsToDo.indexOf( aP ) === -1 && body.contains( aP ) &&
                        r.test( addedNode.nodeValue ) === true &&  // is pre-regexping helpful?
                        elemsToDo.push( aP );
                } else if( addedNode.nodeType === 1 && allElems.indexOf( addedNode ) === -1 ) {
                    allElems.push( addedNode );
                    [].forEach.call( addedNode.childNodes, findstuff );
                }
            }
            function processElem( elem ) {
                if( elem.parentNode && elem.parentNode.className !== "signwritingtext" && elem.parentNode.className !== "swtooltip" ) {
                    // BUG: if mutated elem contains both sw as text and already-
                    // processed signwriting, the sw node gets re-styled, resulting
                    // in an extra layer of signwriting block inside the existing one.
                    signwriting_styled( elem );
                }
            };
            if( MO ) {
                tMO = new MO(function(mutations) {
                    mutations.forEach(function( mutation ) {
                        if( mutation.type === "characterData" ) {
                            findstuff( mutation.target );
                        } else {
                            [].forEach.call( mutation.addedNodes, findstuff );
                        }
                    });
                    elemsToDo.length && tMO.disconnect();
                    elemsToDo.forEach( processElem );
                    elemsToDo.length && tMO.observe( body, MOopts );
                    allElems.length = elemsToDo.length = 0;
                });
                tMO.observe( body, MOopts );
            } else {
                var timer = false, ignoreMutations = false; // too much duplication. todo: fix.
                // todo: IE onDOMCharacterDataModified
                body.addEventListener && body.addEventListener('DOMNodeInserted', function( e ){
                    if( ignoreMutations === false ) {
                        findstuff( e.target );
                        if( timer === false ) {
                            timer = setTimeout( function() {
                                ignoreMutations = true;
                                elemsToDo.forEach( processElem );
                                timer = ignoreMutations = false;
                                allElems.length = elemsToDo.length = 0;
                            }, 32 );
                        }
                    }
                });
            }
        })();
 
      // TEXT HIGHLIGHTING
      // still a bit buggy.
      window.getSelection && (function() { // if getSelection isn't available, there's nothing we can do.
        var Highlight, HighlightText, $elem = $( tempDiv ).appendTo("body"), tempHighlightText = $elem.css( "color" );
        elemStyle.color = "HighlightText";
        elemStyle.backgroundColor = "Highlight";
        HighlightText = $elem.css( "color" ); // Find the default highlighted text color (typically white).
        Highlight = $elem.css( "background-color" );
        if( HighlightText !== tempHighlightText || Highlight !== $elem.css( "background-color", "" ).css( "background-color") ) { // did Highlight or HighlightText work?
          mw.util.addCSS( ".signwritingtext ::selection, .signwritingtext::selection{ background-color: transparent; }"); // don't double-highlight.
          Highlight = rgbToHex( Highlight ); // ... and the highlighted background color.
          HighlightText = rgbToHex( HighlightText );
          // var $bb = $("<div>").appendTo("body").css({"position":"absolute","top":0,"left":0});
          var lineAndFill = "&line=" + HighlightText + "&fill=" + Highlight,
            rLineAndFill = /&line=[0-9a-fA-F]+&fill=[0-9a-fA-f]+/,
            undoThese = [], highlightedElems = [], $body = $( document.body ), 
            prevEditedAnchor, prevEditedFocus, prevFocusOffset;
          function fixSelection( e ){
            if( e.which === 0 ) { // No mouse button pressed.
              return;
            }
            e.type !== "mousemove" && e.type !== "keydown" && e.type !== "keyup" && 
              $body[ e.type === "mousedown" ? "on" : "off" ]( "mousemove", fixSelection ); // track mousemove only when the mouse is down.
            var q = window.getSelection(), // note: range is not supported by some browsers
              rangeSupport, MSselection, MSrange;
            /*
 
            if( !( rangeSupport = q.type !== undefined ) ) { // option one: use Range
              if( !( MSselection = document.selection ) ) { // option two: MS TextRange
                return; // option three: give up, because nothing's going to work.
              }
            }
            */
 
            if( ( ( rangeSupport = q.type !== undefined ) ? 
                q.type === "Range" : 
                ( ( MSselection = document.selection ) && MSselection.type === "Text" ) ) &&
              q.isCollapsed === false && 
              q.focusNode && q.anchorNode
            ) {
              if( q.focusNode === prevEditedFocus && q.anchorNode === prevEditedAnchor && q.focusOffset === prevFocusOffset ) { // anchor/focus haven't moved. nothing needs doing.
                return;
              }
              var comparePosition = q.anchorNode.compareDocumentPosition( q.focusNode );
              if( ( comparePosition & 6 ) === 0 ) {
                if( q.anchorNode.parentNode.parentNode.className !== "signwritingtext" ) {
                  return;
                }
                comparePosition = q.focusOffset < q.anchorOffset ? 2 : 4;
              }
              var range = q.getRangeAt( 0 ),
                startElem = $( ( comparePosition & 4 ) ? q.anchorNode : q.focusNode ).closest( ".signwritingtext>span" )[ 0 ],
                endElem = $( ( comparePosition & 4 ) ? q.focusNode : q.anchorNode ).closest( ".signwritingtext>span" )[ 0 ];
              if( ( startElem || ( startElem = endElem ) ) && ( endElem || ( endElem = startElem ) ) ) {
                if( rangeSupport ) {
                  if( comparePosition & 2 ) { // backwards selection
                    range.setEndAfter( endElem ); // needs a bit of fiddling to keep the selection backwards-facing.
                    range.collapse( false ); // collapse to end
                    q.removeAllRanges();
                    q.addRange( range ); // make the selection a single point at the end, then extend it backward.
                    q.extend( startElem.parentNode, 0 );
                  } else if ( comparePosition & 4 ) { // forwards selection
                    range.setStartBefore( startElem ); // keep the selection including both the start and end nodes.
                    range.setEndAfter( endElem );
                    q.addRange( range );
                  }
                } else { // IE
                  // this should probably use a different variable.
                  range = document.body.createTextRange(); // keep the selection including the start node. moving the end messes up IE.
                  range.moveToElementText( startElem );
                  MSrange = MSselection.createRange();
                  MSrange.setEndPoint( "StartToStart", range );
                }
                // $bb.text( q ); // for testing
                startElem = startElem.parentNode; endElem = endElem.parentNode;
                range = q.getRangeAt( 0 ); // unnecessary?
                prevFocusOffset = q.focusOffset;
                // check anchor and focus again after changes
                if( prevEditedFocus === q.focusNode && prevEditedAnchor === q.anchorNode ) { // same anchor/focus. No highlighting changes.
                  return;
                }
                var compareFocus = prevEditedFocus && prevEditedFocus.compareDocumentPosition( q.focusNode );
                // is the cDP actually quicker than just checking everything each time?
                var compareEditedFocusToAnchor = ( ( compareFocus | comparePosition ) & 6 ) === 6 ?
                  prevEditedFocus && prevEditedFocus.compareDocumentPosition( q.anchorNode )
                    :
                  compareFocus;
                var selectionContains = q.containsNode ?
                  function( elem ) { return q.containsNode( elem ) || elem === startElem || elem === endElem; } :
                  function( elem ) { // IE
                    var dup = MSrange.duplicate();
                    dup.moveToElementText( elem );
                    return MSrange.inRange( dup ) || elem === startElem;
                  };
                if( highlightedElems.length !== 0 && compareEditedFocusToAnchor === compareFocus ) { // find elems to unhighlight
                  // console.log( "Find unhighlightable..." );
                  for( var i = highlightedElems.length, elem; i--; ) {
                    elem = highlightedElems[ i ];
                    if( !selectionContains( elem ) ) {
                      undoThese.splice( i, 1 )[ 0 ]();
                      highlightedElems.splice( i, 1 );
                    }
                  }
                }
                prevEditedFocus = q.focusNode; prevEditedAnchor = q.anchorNode;
                // check if the focus moved in selection's direction (so that it's possible for new elems to be highlighted)...
                if( compareFocus === comparePosition || ( compareFocus & 6 ) === 0 ) {
                  var cAC = range.commonAncestorContainer;
                  // Find highlightable...
                  ( cAC.nodeType === 3 ? $( startElem ) : $( cAC ).find(".signwritingtext") ).each(function(){ // find potential sw divs
                    if( highlightedElems.indexOf( this ) === -1 && selectionContains( this ) ) {
                      var $elem = $( this );
                      $elem.css( "background-color", "#" + Highlight ).css( "background-image", function(a, original) {
                        var rep = original.replace( rLineAndFill, lineAndFill );
                        if( rep !== original ) {
                          highlightedElems.push( this );
                          undoThese.push( function(){
                            // BUG: if style change caused background to change, the undo will reverse the new change
                            $elem.css( { "background-image" : original, "background-color" : "" } );
                          });
                          return rep;
                        }
                      })
                    }
                  });
                }
              }
            } else { // unhighlight everything
              for( ; undoThese.length; ) {
                undoThese.shift()();
              }
              highlightedElems.length = 0;
              prevEditedAnchor = prevEditedFocus = undefined;
            }
          }
          $body
            .on( "keyup",  fixSelection )
            .on( "mousedown mouseup keydown", function( e ) { // any other events that can change the selection?
              setTimeout( function(){ 
                fixSelection( e );
              }, 1 );
            });
        }
        $elem.remove();
      })();
 
      wmSupport && (function(){ // Fix webkit anchor-handling bug.
        // First, check whether the bug is present. Put together an overflowed div and put it in the body.
        var $child, $parent = $( "<div>" ).css( { display: 'inline-block', width: 100, height: 100, overflow: "scroll" } )
          .append( $child = $( "<div>" ).css( { margin: 200, height: 200, width: 200 } ) ).appendTo( "body" ), 
          parent = $parent[ 0 ];
        if ( parent.scrollByLines ) { // atm the only browsers to support this are also the only ones that need this check, so...
          parent.scrollByLines(1); // which way is "down" in scrolling? try to scroll toward the "bottom" by one line. 
          if ( parent.scrollTop > parent.scrollLeft ) { // which direction did it scroll? the correct behaviour is rightward.
            function anchorFix( $target ) {
              $target.length && setTimeout( function(){ // use setTimeout to make sure link-following is done first.
                var $headerParent = $target.parent( "h2" );
                if ( $headerParent.length ) {
                  $target = $headerParent;
                }
                window.scrollTo( $target.offset().left, 0 ); // ... and fix the scroll position.
              }, 1);
            }
            $("[href^=\"#\"]:not([href=\"#\"])").on("click", function(){
              var $target = $( this.getAttribute( "href" ) ); // I know this looks like an XSS vector, but it's not.
              anchorFix( $target );
            });
            if ( location.hash ) { // in case the page has a hash at the start
              anchorFix( $( location.hash ) ); // seems to sometimes be a few pixels off. dunno why.
            }
          }
        }
        $parent.remove();
      })();
    }
    //*/
 
});
 
// attach the function to crawl to the DOM to .ready because the page might not be loaded yet
 
$( document ).ready(function(){signwriting_styled();});
 
$( document ).ready(function( $ ){ // tooltips
 
var swt = $("<div>").addClass( "swtooltip" ).appendTo( document.body ),
  timer,
  rcache = {},
  x, y;
 
$( "body" ).on( "mouseover", "[title]", function(e) { // would manually checking [title] be faster?
  var title = this.title, rc = rcache[ title ], onmm = function(e){ x = e.pageX; y = e.pageY; };
  if ( rc !== false && ( rc === undefined ? ( rcache[ title ] = r.test( title ) ) : true ) ) {
    x = e.pageX; y = e.pageY;
    var t = this;
    timer && clearTimeout( timer );
    if( !rc ) {
      rc = rcache[ title ] = $( "<span>" ).text( title );
      swt.empty().append( rc );
      signwriting_styled( rc[ 0 ] ); // technically unnecessary with the mutationobservers running, but I suspect it might give a slight speed boost
    } else {
      swt.empty().append( rc ); // todo: remove duplication
    }
    timer = setTimeout( function(){ 
      swt.css( { top: y + 20, left: x + 1 } ).fadeIn( 50 ); 
      var $w = $( window ), 
        oTop = swt[ 0 ].offsetHeight, oLeft = swt[ 0 ].offsetWidth,
        wTop = $w.scrollTop() + $w.height(), wLeft = $w.scrollLeft() + $w.width(); // Bug: IE flips width and height values.
      if( x + oLeft > wLeft ) {
        swt.css( { left : wLeft - oLeft } );
      }
      if( y + oTop > wTop ) {
        swt.css( { top : wTop - oTop } );
      }
    }, 350);
    $( t ).data("swtitle", t.title ).removeAttr( "title" ) // make sure the native tooltip doesn't go off
      .on( "mousemove", onmm ) // update mouse positions
      .one( "mouseleave", function() {
        $( t ).attr( "title", $( t ).data( "swtitle" ) ).off( "mousemove", onmm );
        timer && clearTimeout( timer );
        swt.fadeOut( 0 );
      })
  }
} );
 
});
 
})();