User:Md gilbert/indicoder.js

/** * jQuery indicoder v0.01 * Annotation plugin for jquery * * Licensed under ... * Copyright 2012 Michael Gilbert */

// Globals var readOnly = false;

var indicoder = {

settings: { style: 'right', // Available styles could include: left, bottom, right, floating (not done) server: 'http://localhost/indicoder/web/app_dev.php', // Will need to update showThirdPartyStats: true, isActive: true, barTitle: ' IndiCoder Annotation Tool ', selector: '#content > #bodyContent > .mw-content-ltr p, ' + '#content > #bodyContent > .mw-content-ltr h2, ' + //'#content > #bodyContent > .mw-content-ltr td, ' + // Breaks annotating diffs '#content > #bodyContent > .mw-content-ltr ul, ' + '#content > #bodyContent > .mw-content-ltr li, ' + '#content > #bodyContent > .mw-content-ltr dd, ' + '#content > #bodyContent > .mw-content-ltr dl, ' + // For diffs '#content > #bodyContent > .mw-content-ltr .diff-context div, ' + '#content > #bodyContent > .mw-content-ltr .diff-addedline div' },

// Initialize the tool initialize: function { indicoder.loadCss("https://localhost/indicoder.css"); indicoder._create; }, // end initialize

// Create the tool _create: function(options) { indicoder.options = $.extend( true, {}, indicoder.settings, options ); var selector = indicoder.options.selector;

// Wrap selectable blocks with our divs // (Could alternatively go through with each and split into sentances) $(selector).wrap(" ");

// Grab existing codes for this user/codesheet/url, // go through and check each block for existing annotations. var icUser = indicoder.readCookie('ic_loggedIn'); var icCodesheet = indicoder.readCookie('ic_codesheet'); var url = document.URL;

// Only load codes if we're logged in and viewing a codesheet if (icUser && icCodesheet) { indicoder._shrinkFixed; indicoder._draw; indicoder._drawElements(icUser, icCodesheet, url); }   // Or if we're logged in, just draw so the codesheet select action is available if (icUser && ! icCodesheet) { indicoder._shrinkFixed; indicoder._draw; } },

// Shrink fixed position elements so they don't overlap with IndiCoder's currently drawn style // TODO: It's possible this could be more flexible if shrink was called from draw. // If that change is made, remove from create above, add to draw, and store previous // values so they can be restored in restoreFixed (stub below). // This would remove the need to have 3rd party libraries shrink their own // elements on initial IndiCoder creation. _shrinkFixed : function { if (indicoder.options.style == 'right') { // Go through all fixed position elements and adjust as necessary // TODO: caching elements for faster queries?? $('*').each(function(index) {       // If it spans the whole document width, shrink it        if ( $(this).css('position') === 'fixed' && $(this).width == $(document).width ) {          $(this).css({'width': '80%'});        // Or if it's anchored in the right of the page, move it left        } else if ( $(this).css('position') === 'fixed' && $(this).css('right') == '0px' ) {          $(this).css({'right': '20%'});        // Or, last ditch, if it's fixed and none of the above just shrink        } else if ( $(this).css('position') === 'fixed') {          $(this).css({'width': '80%'});        }      }); } else if (this.options.style == 'left') { // TODO: as above } else if (this.options.style == 'bottom') { // TODO: as above } },

// Restores fixed position elements to their previous state when the IndiCoder menu is destroyed _restoreFixed : function {

},

_drawElements: function(icUser, icCodesheet, url) { var request = this.options.server + '/rest/getcodes'; var instance = this; $.post(request,     {        user: icUser,        url: url,        cid: icCodesheet,        selector: instance.options.selector,      }, function(data) {        $('.ic-block').each(function { var ttn = this.tagName; var index = $(this).parent.children.index(ttn); var xpath = getElementXPath(this); for (var i in data.tags) { // Don't test match based on offset, that changes with different screen resolutions if (data.tags[i].text == $(this).text && (data.tags[i].index == index || data.tags[i].xpath == xpath)) { //if (data.tags[i].text == $(this).text && data.tags[i].xpath == xpath) { $('#indicoder-toolbox').data('ic-tag', data.tags[i].codeText); instance._dressBlock(this, data.tags[i]); data.tags[i].dressed = true; console.log("Placed tag: " + data.tags[i].codeText); //console.log(getElementXPath(this)); }         }          for (var i in data.textElements) { if (data.textElements[i].text == $(this).text && (data.textElements[i].index == index || data.textElements[i].xpath == xpath)) { $('#indicoder-toolbox').data('ic-text', data.textElements[i].codeText); instance._dressBlock(this, data.textElements[i]); data.textElements[i].dressed = true; console.log("Placed text element: " + data.textElements[i].codeText); } /* Debugging, for some reason a text element was being orphaned, but now I can't repro... if (data.textElements[i].text == $(this).text) { console.log("This: " + xpath + ", Stored: " + data.textElements[i].xpath); } if (xpath == data.textElements[i].xpath) { console.log("Matching xpath: " + xpath + ", not matching text:"); console.log("Stored: " + data.textElements[i].text); console.log("This:  " + $(this).text); }         }        });        // TODO: Handle orphans        for (var i in data.tags) {          if (! data.tags[i].dressed) {           console.log("Found an orphan tag on block with id: " + data.tags[i].id + ", text: '" + data.tags[i].text + "'");            console.log("Orphan index: " + data.tags[i].index);          }        }        for (var i in data.textElements) {          if (! data.textElements[i].dressed) {           console.log("Found an orphan textElement on block with id: " + data.textElements[i].id + ", text: '" + data.textElements[i].text + "'");          }        }      }, "json"    ); }, _loginUser: function { // Set the cookie var user = $('#_username').val; indicoder.createCookie('ic_loggedIn', user, 1); //window.location.reload; return false; }, _dressBlock: function(block, element) { // If we're adding a tag var icCode = document.createElement('i'); icCode.id = 'ic-code-' + element.id; var instance = this;

if (element.type == 'tags') { $(icCode).addClass("icon-tag ic-block-tag"); }   // If we're adding a text element else if (element.type == 'textElements') { $(icCode).addClass("icon-comment ic-block-text"); }   $(block).prepend(icCode); $(icCode).hover(     function {        // Display the code tooltip        instance._createTooltip(block, element);      },       function {        // Destroy the (all) code tooltip(s)        $('.ic-tooltip').stop.fadeOut('fast', function { $('.ic-tooltip').remove; });      }    ); $(block).addClass('ic-block-dressed'); $(block).removeClass('ic-block-dressed-hover'); }, _createTooltip: function(block, element) { var icUser = indicoder.readCookie('ic_loggedIn'); var instance = this; var tip = document.createElement('div'); tip.id = 'ic-tooltip-' + element.id; $(tip).addClass('ic-tooltip'); $(tip).addClass('ic-shadow'); var label = element.type == 'tags' ? 'Tagged' : 'Text'; var html = " " + label + ": " + element.codeText + " " + " Id: " + element.id + " " + " Created by: " + element.user + " " + " Created on: " + element.created.date + " "; // Add delete link if we're the creator or if editThirdParty is set on the codesheet // (TODO: security pass) if (icUser == element.user || (element.editThirdParty && element.editThirdParty == 1)) { html += " Delete "; }   $(tip).html(html); $('#ic-code-' + element.id).append(tip); $('#ic-delete-element').click(function {     var request = instance.options.server + '/rest/deletecode';      $.post(request, { eid: element.id }, function(data) { // If we successfully deleted the element, remove the tag from the block $('#ic-code-' + element.id).remove; $(block).removeClass('ic-block-dressed'); $(block).removeClass('ic-block-dressed-hover'); // And decrement the label in the URL list var url = document.URL; var scrub = url.replace(/[\/\:\_\-\.\?\&\= ]/g, ""); var prev = parseInt($('#' + scrub + '-' + element.type).html); $('#' + scrub + '-' + element.type).html(prev - 1); });   });    $(tip).stop.fadeIn('fast'); }, _draw: function { var annDiv = document.createElement("div"); $(annDiv).addClass(this.options.className); annDiv.id = 'indicoder-toolbox'; document.body.appendChild(annDiv);

// Shift everything on the page to make room for the toolbar given desired style // (left, bottom, right, floating, etc) if (this.options.style == 'right') { //$('body').css({'margin-right': '20%', 'position': 'relative'}); $('body').wrapInner(' '); $('#indicoder-toolbox').addClass('ic-toolbar-right'); $('#indicoder-toolbox').html(' '); } else if (this.options.style == 'left') { //$('body').css({'margin-left': '20%', 'position': 'relative'}); $('body').wrapInner(' '); $('#indicoder-toolbox').addClass('ic-toolbar-left'); $('#indicoder-toolbox').html(' '); }

var icUser = indicoder.readCookie('ic_loggedIn'); var icCodesheet = indicoder.readCookie('ic_codesheet'); // If we're not logged in, display the login promptOB if (! icUser) { this._drawLogin; } else if (icUser && ! icCodesheet) { // If we're logged in but haven't selected a codesheet, display codesheet list this._drawSelect; } else { this._drawCodesheet(icCodesheet); }

// Otherwise, display codesheet information }, // Destroys the toolbar, moves all elements back to their previous positions _destroy: function { $('#indicoder-toolbox').remove; indicoder.eraseCookie('ic_loggedIn'); indicoder.eraseCookie('ic_codesheet'); if (this.options.style == 'right') { //$('body').css({'margin-right': '0%'}); //$('#indicoder-space-wrapper').remove; $('#indicoder-space-wrapper').replaceWith($('#indicoder-space-wrapper').children); } else if (this.options.style == 'left') { //$('body').css({'margin-left': '0%'}); //$('#indicoder-space-wrapper').remove; $('#indicoder-space-wrapper').replaceWith($('#indicoder-space-wrapper').children); } },  _drawTitleBar: function { var ic = this; var style = this.options.style;

// Empty the title bar first $("#ic-title-" + style).remove;

var title = this.options.barTitle; var icUser = indicoder.readCookie('ic_loggedIn'); var html = ""; if (icUser) { html += " Logged in as " + icUser + " - "; }   html += title + " "; $('#ic-footer-' + style).append(html);

// Add the logout link if (icUser) { var logout = document.createElement("a"); $(logout).css({ 'display': 'inline' }); $(logout).attr("href", "javascript:;"); $(logout).html("(Logout)"); $(logout).click(function {       ic._logoutUser;      }); $('#ic-title-bar').append(logout); } },  _logoutUser: function { indicoder.eraseCookie('ic_loggedIn'); //indicoder._drawTitleBar; indicoder._destroy; indicoder._draw; }, _drawLogin: function { var style = indicoder.options.style; $('#ic-content' + style).html(     'Enter your user name and login to select a codesheet and begin coding. '    ); var icForm = document.createElement("div"); $(icForm).addClass('well form-inline ic-' + style); icForm.id = 'ic_login'; $(icForm).css({'background-color': 'rgba(0,0,0,.0)'}); $(icForm).html(''); $('#ic-content-' + style).append(icForm); $(icForm).onkeyup = (function {     console.log("keyed");    }); // Add the login button var loginButton = document.createElement("button"); loginButton.innerHTML = 'Sign in'; loginButton.id = 'login_btn'; $(loginButton).addClass('btn'); $('#ic_login').append(loginButton);

this._drawTitleBar;

$('#login_btn').click(function {    indicoder._loginUser;     indicoder._drawSelect;   }); $('#_username').keyup(function(e) {    if (e.which == 13) {       indicoder._loginUser;       indicoder._drawSelect;     }   });

return true; }, _drawSelect: function { var icUser = indicoder.readCookie('ic_loggedIn'); var request = indicoder.options.server + '/rest/usercodesheets/' + icUser; var style = indicoder.options.style; // Fetch codesheets for this user and allow selection $.getJSON(request, function(data) {     // Handle unknown user      if (data.codesheets.errorstatus == 'fail') {        console.log("Unknown user");        indicoder.eraseCookie('ic_loggedIn');        indicoder._drawLogin;        $('#ic_login').append(" " + data.codesheets.message + " ");        return false;      }      // If style is left or right, display vertical list.  If it's bottom display horizontal list      if (style == 'left' || style == 'right') {        $('#ic-content-' + style).html( 'Please select one of your available codesheets ' + ' '       );      } else {        $('#ic-content-' + style).html( ' '         );        }        indicoder._drawTitleBar;

// Create the back arrow var back = document.createElement("div"); back.innerHTML = ''; $(back).click(function {         indicoder.eraseCookie('ic_codesheet');          indicoder._drawSelect;        }); $('#ic-back').append(back); // Create links for Urls, tags, text, etc

/**** URL links ****/ // If the current url is not in the codesheet list, set cookie and redirect to the first url //var curl = window.location.protocol + "//" + window.location.host + window.location.pathname; var curl = document.URL; var highlight = ''; for (var i in data.codesheet.urls) { var url = data.codesheet.urls[i].trim; if (! url || url === null) { continue; }         var m = url.match(/.*\/(.*)/i); var urlPage = m === null ? url : m[1]; var urlDiv = document.createElement("div"); if (url == curl) { highlight = url; indicoder.createCookie('ic_currentUrl', url, 1); $(urlDiv).addClass('ic-curl'); }         $(urlDiv).addClass('ic-url ic-' + style); urlDiv.id = url; var tags = data.codesheet.elements[url].tags; var text = data.codesheet.elements[url].textElements; var page = data.codesheet.elements[url].page.length ? data.codesheet.elements[url].page.length : 0; var scrub = url.replace(/[\/\:\_\-\.\?\&\= ]/g, ""); var urlNum = parseInt(parseInt(i) + 1); urlDiv.innerHTML = " " + urlNum + " " + urlPage + " (Tags: " + tags + ", " +           "Text annotations: " + text + " , " +            "Page annotations: " + page + " ) "; $(urlDiv).click(function {           window.location = this.id;          }); if (tags == 0 && text == 0 && page == 0) { $(urlDiv).addClass('ic-url-new'); }         $('#codesheet-urls').append(urlDiv); }       if (! highlight) { //indicoder.createCookie('ic_currentUrl', data.codesheet.urls[0], 1); //window.location = data.codesheet.urls[0]; readOnly = true; } else { // Grab existing codes for this user/codesheet/url, go through and check each block for existing annotations. /* Double-draws the elements... var icUser = indicoder.readCookie('ic_loggedIn'); var icCodesheet = indicoder.readCookie('ic_codesheet'); var url = window.location.protocol + "//" + window.location.host + window.location.pathname; indicoder._drawElements(icUser, icCodesheet, url); //window.location.reload;

// Scroll to the current url var offsetTop = document.getElementById(highlight).offsetTop - 80; document.getElementById('codesheet-urls').scrollTop = offsetTop; }       /**** End URL links ****/

/**** Check early, if we're not on a code-able page, direct the user to click a URL link ****/ if (readOnly == true) { var directDiv = document.createElement("div"); $(directDiv).addClass("ic-description ic-right"); $(directDiv).html("You are not on a code-able URL. Click one of the links above to annotate"); $('#codesheet-tags').append(directDiv); return false; }       /**** End URL notice ****/

/**** Tag links ****/ $("#codesheet-tags").before("Tag: "); for (var i in data.codesheet.tags) { var tag = data.codesheet.tags[i]; var tagDiv = document.createElement("div"); $(tagDiv).addClass('ic-tag ic-' + style); $(tagDiv).attr("title", tag.d); tagDiv.id = tag.t;         tagDiv.innerHTML = tag.t;

/***** Clicking the tag ******/ if (readOnly === false) { $(tagDiv).click(function {             // TODO: write tooltip              var clickedTag = this;              $('#indicoder-toolbox').data('ic-tag', this.id);              document.body.style.cursor = 'pointer';              // Add hover property when tag is selected              $('.ic-block').hover( function { $(this).addClass('ic-block-dressed-hover'); }, function { $(this).removeClass('ic-block-dressed-hover'); } );             // Highlight the currently activated tag              $('.ic-tag').removeClass('ic-ctag');              $(clickedTag).addClass('ic-ctag');

/***** Applying a tag to a block *****/ $('.ic-block').unbind('click'); $('.ic-block').click(function {               var t = $('#indicoder-toolbox').data('ic-tag');                var ttn = this.tagName;                //var url = window.location.protocol + "//" + window.location.host + window.location.pathname;                var url = document.URL;                var scrub = url.replace(/[\/\:\_\-\.\?\&\= ]/g, "");                var block = this;                var xpath = getElementXPath(this);

// When we click a block, save the tag var request = indicoder.options.server + '/rest/addcode'; $.post(request,                   {                    user: icUser,                    cid: id,                    text: $(this).text,                    md5: MD5($(this).text),                    offset: $(this).offset.top + ', '+ $(this).offset.left,                    index: $(this).parent.children.index(ttn),                    xpath: xpath,                    codeType: 'tags',                    codeText: t,                    url: url,                    selector: indicoder.options.selector,                    callback: '?',                  }, function(data) {                    // Check for success                    console.log("requested code add");                    //console.log(data.text);                    var prev = parseInt($('#' + scrub + '-tags').html);                    $('#' + scrub + '-tags').html(prev + 1);                    // Then dress the block on success indicoder._dressBlock(block, data); $('.ic-curl').removeClass('ic-url-new'); }, "json" );

/*               $.ajax({                  type: "POST",                  url: request,                  contentType: "application/json; charset=UTF-8",                  dataType: "json",                  data: "{user: '" + icUser + "', cid: '" + id + "', text: '" + $(this).text + "', " +                    "md5: '" + MD5($(this).text) + "', offset: '" + $(this).offset.top + ', '+ $(this).offset.left + "', " +                    "index: '" + $(this).parent.children.index(ttn) + "', codeType: 'tags', codeText: '" + t + "', " +                    "url: '" + url + "', selector: '" + indicoder.options.selector + "', callback: '?'}",                  success: function(data) {                    // Check for success                    console.log("requested code add");                    var prev = parseInt($('#' + scrub + '-tags').html);                    $('#' + scrub + '-tags').html(prev + 1); // Then dress the block on success indicoder._dressBlock(block, data); $('.ic-curl').removeClass('ic-url-new'); }, error: function(data) {

}               });

// Then undo the tag settings document.body.style.cursor = 'auto'; $('.ic-block').hover(                 function { $(this).stop },                  function { $(this).stop }                ); $(clickedTag).removeClass('ic-ctag'); $('.ic-block').unbind; $('#indicoder-toolbox').data('ic-tag', ''); });           });          }          $('#codesheet-tags').append(tagDiv); }       /**** End tag links ****/

/**** Page level annotations ****/ var pageAnnotations = []; $("#codesheet-page").before("Page elements: "); for (var i in data.codesheet.page) { var url = document.URL.trim; var scrubUrl = url.replace(/[\/\:\_\-\.\?\&\= ]/g, ""); var page = data.codesheet.page[i]; var scrubId = page.t.replace(/[\/\:\_\-\.\?\&\= ]/g, ""); var pageDiv = document.createElement("div"); $(pageDiv).addClass('ic-page ic-' + style); $(pageDiv).attr("title", page.d); pageDiv.id = scrubId; var pages = data.codesheet.elements[url].page; pageAnnotations[scrubId] = []; pageAnnotations[scrubId]['title'] = page.t;         pageAnnotations[scrubId]['desc'] = page.d;          pageAnnotations[scrubId]['scrubId'] = scrubId; pageAnnotations[scrubId]['checked'] = false; for (var x in pages) { if (pages[x].text == page.t) { $(pageDiv).attr("eid", pages[x].id); pageAnnotations[scrubId]['checked'] = true; }         }          var checked = pageAnnotations[scrubId]['checked'] ? 'CHECKED' : ''; pageDiv.innerHTML = " " + page.t;

$('#codesheet-page').append(pageDiv);

/***** Clicking the page element should (un)check the checkbox *****/ if (readOnly === false) { $(pageDiv).click(function {             var thisId = this.id;

// Either add or remove based on if it's currently checked if (pageAnnotations[thisId]['checked']) { var request = indicoder.options.server + '/rest/deletecode'; $.post(request, { eid: $('#' + thisId).attr('eid'), callback: '?' }, function(data) {                 // If we successfully deleted the element, uncheck the box                  pageAnnotations[thisId]['checked'] = false;                  $('#page_' + thisId).prop("checked", false);                  // And update the url link label                  var prev = parseInt($('#' + scrubUrl + '-page').html);                  $('#' + scrubUrl + '-page').html(prev - 1);                }); } else { var request = indicoder.options.server + '/rest/addcode'; $.post(request,                 {                    user: icUser,                    cid: id,                    text: ,                    md5: ,                    offset: '',                    index: 0,                    codeType: 'page',                    codeText: pageAnnotations[thisId]['title'],                    url: url,                    selector: indicoder.options.selector,                    callback: '?',                  }, function(data) {                    // Check for success, update the element id                    $('#' + thisId).attr('eid', data.id);                    // Check the box                    pageAnnotations[thisId]['checked'] = true;                    $('#page_' + thisId).prop("checked", true);                    // Update the url link label                    var prev = parseInt($('#' + scrubUrl + '-page').html);                    $('#' + scrubUrl + '-page').html(prev + 1); $('.ic-curl').removeClass('ic-url-new'); }, "json" );             }            });          }          //$('#codesheet-page').append(pageDiv); }       /**** End page level annotations ****/

/**** Text annotations ****/ if (readOnly === false) { // Add label and text box $("#codesheet-open").before("Text: "); $("#codesheet-open").append(" "); // Listen for input $('#codesheet-text').keyup(function(event) {           var text = $('#codesheet-text').val;            if (text) {              document.body.style.cursor = 'pointer';              $('.ic-block').hover( function { $(this).addClass('ic-block-dressed-hover'); }, function { $(this).removeClass('ic-block-dressed-hover'); } );

/***** Applying a text element to a block *****/ $('.ic-block').unbind('click'); $('.ic-block').click(function {

// When we click a block, save the text element var t = $('#codesheet-text').val; var ttn = this.tagName; //var url = window.location.protocol + "//" + window.location.host + window.location.pathname; var url = document.URL; var xpath = getElementXPath(this);

var request = indicoder.options.server + '/rest/addcode'; var block = this; $.post(request,                 {                    user: icUser,                    cid: id,                    text: $(this).text,                    md5: MD5($(this).text),                    offset: $(this).offset.top + ', '+ $(this).offset.left,                    index: $(this).parent.children.index(ttn),                    xpath: xpath,                    codeType: 'textElements',                    codeText: t,                    url: url,                    selector: indicoder.options.selector,                    callback: '?',                  }, function(data) {                    console.log("Applied textElement");                    var scrub = url.replace(/[\/\:\_\-\.\?\&\= ]/g, "");                    var prev = parseInt($('#' + scrub + '-textElements').html);                    $('#' + scrub + '-textElements').html(prev + 1);                    // Then, dress the block indicoder._dressBlock(block, data); }, "json" );

// Then, undo the text element settings document.body.style.cursor = 'auto'; $('.ic-block').hover(                 function { $(this).stop },                  function { $(this).stop }                ); $('.ic-block').unbind; $('#codesheet-text').val(''); });           }          });        }        /**** End text annotations ****/

}, "json"); },

/******************* * UTILITY FUNCTIONS ********************/ test: function { console.log("Test - Indicoder"); }, loadCss: function(url) { var head = document.getElementsByTagName('head')[0], link = document.createElement('link'); link.type = 'text/css'; link.rel = 'stylesheet'; link.href = url; head.appendChild(link); return link; },

/****** Cookie functions ******/ createCookie: function(name,value,days) { var expires = ''; if (days) { var date = new Date; date.setTime(date.getTime+(days*24*60*60*1000)); expires = "; expires="+date.toGMTString; } else { expires = ""; }

// If we're loading from a local file, set cookie using $.data if (window.location.hostname == '') { var cs = $('body').data('cookie'); var c = name + "=" + value + expires + "; path=/;"; $('body').data('cookie', cs ? cs + c : c); } else { document.cookie = name+"="+value+expires+"; path=/"; } },  readCookie: function(name) { var nameEQ = name + "="; var ca = ''; // If we're loading from a local file, get cookie using $.data if (window.location.hostname == '') { var cs = $('body').data('cookie'); if (typeof(cs) != 'undefined') { ca = cs.split(';'); }   } else { ca = document.cookie.split(';'); }   for(var i=0;i < ca.length;i++) { var c = ca[i]; while (c.charAt(0)==' ') c = c.substring(1,c.length); if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); }   return null; }, eraseCookie: function(name) { indicoder.createCookie(name,"",-1); }, /******* End Cookie functions ******/

/***** XPATH functions (firebug) *****/ /**  * Gets an XPath for an element which describes its hierarchical location. */ getElementXPath: function(element) { if (element && element.id) return '//*[@id="' + element.id + '"]'; else return getElementTreeXPath(element); }, getElementTreeXPath: function(element) { var paths = [];

// Use nodeName (instead of localName) so namespace prefix is included (if any). for (element && element.nodeType == 1; element = element.parentNode) {       var index = 0; for (var sibling = element.previousSibling; sibling; sibling = sibling.previousSibling) {           // Ignore document type declaration. if (sibling.nodeType == Node.DOCUMENT_TYPE_NODE) continue;

if (sibling.nodeName == element.nodeName) ++index; }

var tagName = element.nodeName.toLowerCase; var pathIndex = (index ? "[" + (index+1) + "]" : ""); paths.splice(0, 0, tagName + pathIndex); }

return paths.length ? "/" + paths.join("/") : null; },

getElementCSSPath: function(element) {   var paths = [];

for (element && element.nodeType == 1; element = element.parentNode) {       var selector = getElementCSSSelector(element); paths.splice(0, 0, selector); }

return paths.length ? paths.join(" ") : null; },

trim: function(text) {   return text.replace(/^\s*|\s*$/g,""); },

cssToXPath: function(rule) {   var regElement = /^([#.]?)([a-z0-9\\*_-]*)((\|)([a-z0-9\\*_-]*))?/i; var regAttr1 = /^\[([^\]]*)\]/i; var regAttr2 = /^\[\s*([^~=\s]+)\s*(~?=)\s*"([^"]+)"\s*\]/i;   var regPseudo = /^:([a-z_-])+/i;    var regCombinator = /^(\s*[>+\s])?/i;    var regComma = /^\s*,/i;

var index = 1; var parts = ["//", "*"]; var lastRule = null;

while (rule.length && rule != lastRule) {       lastRule = rule;

// Trim leading whitespace rule = trim(rule); if (!rule.length) break;

// Match the element identifier var m = regElement.exec(rule); if (m) {           if (!m[1]) {               // XXXjoe Namespace ignored for now if (m[5]) parts[index] = m[5]; else parts[index] = m[2]; }           else if (m[1] == '#') parts.push("[@id='" + m[2] + "']"); else if (m[1] == '.') parts.push("[contains(concat(' ',normalize-space(@class),' '), ' " + m[2] + " ')]");

rule = rule.substr(m[0].length); }

// Match attribute selectors m = regAttr2.exec(rule); if (m) {           if (m[2] == "~=") parts.push("[contains(@" + m[1] + ", '" + m[3] + "')]"); else parts.push("[@" + m[1] + "='" + m[3] + "']");

rule = rule.substr(m[0].length); }       else {           m = regAttr1.exec(rule); if (m) {               parts.push("[@" + m[1] + "]"); rule = rule.substr(m[0].length); }       }

// Skip over pseudo-classes and pseudo-elements, which are of no use to us       m = regPseudo.exec(rule); while (m) {           rule = rule.substr(m[0].length); m = regPseudo.exec(rule); }

// Match combinators m = regCombinator.exec(rule); if (m && m[0].length) {           if (m[0].indexOf(">") != -1) parts.push("/"); else if (m[0].indexOf("+") != -1) parts.push("/following-sibling::"); else parts.push("//");

index = parts.length; parts.push("*"); rule = rule.substr(m[0].length); }

m = regComma.exec(rule); if (m) {           parts.push(" | ", "//", "*"); index = parts.length-1; rule = rule.substr(m[0].length); }   }

var xpath = parts.join(""); return xpath; },

getElementsBySelector: function(doc, css) {   var xpath = cssToXPath(css); return getElementsByXPath(doc, xpath); },

getElementsByXPath: function(doc, xpath) {   var nodes = [];

try { var result = doc.evaluate(xpath, doc, null, XPathResult.ANY_TYPE, null); for (var item = result.iterateNext; item; item = result.iterateNext) nodes.push(item); }   catch (exc) {       // Invalid xpath expressions make their way here sometimes. If that happens, // we still want to return an empty set without an exception. }

return nodes; },

getRuleMatchingElements: function(rule, doc) {   var css = rule.selectorText; var xpath = cssToXPath(css); return getElementsByXPath(doc, xpath); } /***** END XPATH functions *****/ };

// Load everything window.onload = function { console.log("Loading Indicoder"); mw.loader.using( ["mediawiki.api"], function {   indicoder.initialize;  });

}

//