User:Cacycle/wikiWatch.js

//

// ==UserScript==

// @name wikiWatch

// @namespace http://en.wikipedia.org/wiki/User:Cacycle/

// @description A MediaWiki watchlist, recent changes, and user contributions page tool that sorts into namespaces, adds unwatch links, and auto-expands entries

// @include /Special:

// @exclude

//

// @homepage http://en.wikipedia.org/wiki/User:Cacycle/wikiWatch

// @source http://en.wikipedia.org/wiki/User:Cacycle/wikiWatch.js

// @author Cacycle (http://en.wikipedia.org/wiki/User:Cacycle)

// @license Released into the public domain

// @version 0.9.5

// ==/UserScript==

//

// add wrapped wikiWatch code to body in order to access and use page scripts and variables under Greasemonkey

//

// start of global code wrapper

if (typeof(wwInstalledFlag) == 'undefined') { window.wwInstalledFlag = false; }

window.WWWrapper = function() {

window.wwInstalledFlag = true;

if (typeof(wwSetupFlag) == 'undefined') { window.wwSetupFlag = false; }

var wwOpenPopup;

var wwEditDivs = [];

//

// WWMain: wikiWatch setup

//

window.WWMain = function() {

WWRemoveEventListener(window, 'load', WWMain, false);

// check if this has already been run, either as a wiki gadget, wiki user script, or a Greasemonkey user script

if (wwSetupFlag == true) {

return;

}

wwSetupFlag = true;

// customizable definitions

// number of most recent entries in short display

var wwRecentShort = wwRecentShort || 15;

// texts

var WWText = WWText || {

'WWRecentAll': 'All',

'WWRecentAllToggle': '(most recent only)',

'WWRecentTop': 'All',

'WWRecentTopToggle': '(show all)',

'WWUnwatch': 'unwatch'

};

// css styles

var WWCss = WWCss ||

'.WWToggle { font-weight: normal; }' +

'.WWSectionFooter { margin-left: 0.5em; }' +

'.WWDayHeading { background-color: #c0c0c0; border: gray solid 1px; margin: 1.25em 0 0.75em 0 !important; padding: 0 2em; }' +

'.WWSectionHeading { margin: 0 0 0.5em 0; padding: 0 0 0.75em 0.5em; }' +

'.WWSectionBlock { margin: 0 0 0.75em 0; }' +

'.WWSection { border: gray solid 1px; padding: 0 0.5em 0.75em 0.5em; }' +

'.WWSection > H5 { padding-top: 0.3em; padding-bottom: 0; }' +

'.WWPopup { display: block; padding: 0 1em 1.5em 2em; position: absolute; left: 17em; right: 1em; border: 1px solid black; border-top: none; border-right: 1px solid gray; }' +

'.WWPopup TT { display: none; }' +

'.WWDebug { display: none; position: static; margin-top: 3em; margin-left: 10em; zIndex: 1000000;}' +

// 'TD': 'text-indent: -2em; padding-left: 2em;',

'.WWSection-1, .WWSection-1 DIV { background-color: #f8fcff; }' +

'.WWSection-2, .WWSection-2 DIV { background-color: #f8fcff; }' +

'.WWSection0, .WWSection0 DIV { background-color: #ffffff; }' +

'.WWSection1, .WWSection1 DIV { background-color: #f2f2f2; }' +

'.WWSection2, .WWSection2 DIV { background-color: #e5e5e5; }' +

'.WWSection3, .WWSection3 DIV { background-color: #d0d0d0; }' +

// colors: HSB 40°-5%-100% (light), 40°-5%-95% (dark), 40° steps

'.WWSection4, .WWSection4 DIV { background-color: #fffbf2; }' +

'.WWSection5, .WWSection5 DIV { background-color: #f2eee6; }' +

'.WWSection6, .WWSection6 DIV { background-color: #fbfff2; }' +

'.WWSection7, .WWSection7 DIV { background-color: #eef2e6; }' +

'.WWSection8, .WWSection8 DIV { background-color: #f2fff2; }' +

'.WWSection9, .WWSection9 DIV { background-color: #e6f2e6; }' +

'.WWSection10, .WWSection10 DIV { background-color: #f2fffb; }' +

'.WWSection11, .WWSection11 DIV { background-color: #e6f2ee; }' +

'.WWSection12, .WWSection12 DIV { background-color: #f2fbff; }' +

'.WWSection13, .WWSection13 DIV { background-color: #e6eef2; }' +

'.WWSection14, .WWSection14 DIV { background-color: #f2f2ff; }' +

'.WWSection15, .WWSection15 DIV { background-color: #e6e6f2; }' +

'.WWSection16, .WWSection16 DIV { background-color: #fbf2ff; }' +

'.WWSection17, .WWSection17 DIV { background-color: #eee6f2; }' +

'.WWSection18, .WWSection18 DIV { background-color: #fff2fb; }' +

'.WWSection19, .WWSection19 DIV { background-color: #f2e6ee; }' +

'.WWSection20, .WWSection20 DIV { background-color: #fff2f2; }' +

'.WWSection21, .WWSection21 DIV { background-color: #f2e6e6; }' +

'.WWSection22, .WWSection22 DIV { background-color: #fff2fb; }' +

'.WWSection23, .WWSection23 DIV { background-color: #f2e6ee; }'

;

// add stylesheet definitions

var styles = new WWStyleSheet();

styles.addRules(WWCss);

// detect pagetype and get the smallest container to replace for compatibility

var wwWatchlist = false;

var wwRecentchanges = false;

var wwContributions = false;

var container = document.body;

if (mw.config.get('wgCanonicalSpecialPageName') == 'Watchlist') {

wwWatchlist = true;

var watchlistElement = document.getElementById('watchlist-message');

if (watchlistElement != null) {

container = watchlistElement.parentNode;

}

}

else if (mw.config.get('wgCanonicalSpecialPageName') == 'Recentchanges') {

wwRecentchanges = true;

watchlistElement = document.getElementById('recentchangestext');

if (watchlistElement != null) {

container = watchlistElement.parentNode;

}

}

else if (mw.config.get('wgCanonicalSpecialPageName') == 'Contributions') {

wwContributions = true;

watchlistElement = document.getElementById('contentSub');

if (watchlistElement != null) {

container = watchlistElement.parentNode;

}

}

var regExpMatch;

// get watchlist block

var sectionBlockIds = [];

// h4: watchlist, recent changes; ul, p: contributions

var regExpWatchlist = new RegExp('(]*?\\bid="?namespace"?[^>]*>(.|\\s)*?)(<(h4|ul)\\b[^>]*>(.|\\s)*?)((

.*?

)?]*?\\bclass="?printfooter"?[^>]*>)', 'i');

// replace watchlist container

container.innerHTML = container.innerHTML.replace(regExpWatchlist,

function (p, p1, p2, p3, p4, p5, p6) {

var namespaceBlock = p1;

var watchlistBlock = p3;

var pageBottom = p6;

// add unwatch links

if ( (wwWatchlist == true) || (wwContributions == true) ) {

// 12 link 2 action3 3 4 41

watchlistBlock = watchlistBlock.replace(/((]*>)[^<]+(<\/a>))/gi, '$1, $2unwatch$3' + WWText['WWUnwatch'] + '$4');

}

// get namespaces

var namespaceName = [];

var namespaceNumber = [];

var namespaceHash = [];

var ns = 0;

var namespaceSelect = namespaceBlock;

// get namespace select

if ( (regExpMatch = /(]*?\bid=\"?namespace\"?[^>]*>(.|\s)*?<\/select>)/i.exec(namespaceSelect) ) != null) {

namespaceSelect = regExpMatch[1];

}

// cycle through namespace options

var regExpNamespace = new RegExp(']*?\\bvalue="?(\\d+)"?[^>]*>((.|\\s)*?)', 'gi');

while ( (regExpMatch = regExpNamespace.exec(namespaceSelect) ) != null) {

namespaceNumber[ns] = regExpMatch[1];

namespaceName[ns] = regExpMatch[2];

if (namespaceNumber[ns] == 0) {

namespaceName[ns] = namespaceName[ns].substr(0, 1).toUpperCase() + namespaceName[ns].substr(1);

}

namespaceHash[ 'WW' + namespaceName[ns] ] = ns;

ns ++;

}

var namespaceMax = ns;

namespaceName[-1] = WWText['WWRecentTop'];

namespaceName[-2] = WWText['WWRecentAll'];

var namespaceSection = [];

// get day block

var day = 0;

var sorted = '';

var regExpDays;

// contributions has no h4 but ul

if (wwContributions == true) {

regExpDays = new RegExp('((()))\\s*(]*>)\\s*((.|\\s)*?)\\s*()', 'gi');

}

// watchlist and recent changes have h4 and optionally ul

else {

regExpDays = new RegExp('(]*>\\s*((.|\\s)*?)\\s*)\\s*(]*>)?\\s*((.|\\s)*?)\\s*()?\\s*(?=(]*>|$))', 'gi');

}

// cycle through days or whole block

while ( (regExpMatch = regExpDays.exec(watchlistBlock) ) != null) {

var dayHeading = regExpMatch[1];

var dayContainerTop = regExpMatch[4] || '';

var dayContainer = regExpMatch[5];

var dayContainerBottom = regExpMatch[7] || '';

var mostRecent = 0;

// initialize and clear for +=

for (var ns = -2; ns <= namespaceMax; ns ++) {

namespaceSection[ns] = '';

}

namespaceSection[-2] = dayContainer;

// add day heading

if (dayHeading != '') {

sorted += '

' + dayHeading + '
';

}

// define regexps outside the loops

var regExpArticles = new RegExp('((<(table|li)\\b[^>]*?>(.|\\s)*?)\\s*(]*>(.|\\s)*?

)?)', 'gi');

var regExpArticleName = new RegExp(']*?\\btitle=(\\"([^\\n\\>"]*)\\"|([^\\s\\">]*))[^>]*>(\\2|\\4)', 'gi');

// get article block

while ( (regExpMatch = regExpArticles.exec(dayContainer) ) != null) {

var articleEditsBlock = regExpMatch[1];

var articleBlock = regExpMatch[2];

var editsBlock = regExpMatch[5];

// get article name

while ( (regExpMatch = regExpArticleName.exec(articleBlock) ) != null) {

var articleName = regExpMatch[4];

// get article namespace

regExpMatch = /^(.*?):/.exec(articleName);

var namespaceIndex = 0;

if (regExpMatch != null) {

namespaceIndex = namespaceHash[ 'WW' + regExpMatch[1] ];

}

// append article block to section

if (namespaceIndex != null) {

namespaceSection[namespaceIndex] += articleEditsBlock;

// add to most recent section

if (mostRecent < wwRecentShort) {

namespaceSection[-1] += articleEditsBlock;

mostRecent ++;

}

}

}

// finish articles

}

// finish sections

for (var ns = -2; ns <= namespaceMax; ns ++) {

// sort the two main namespaces last

if (ns == 0) {

ns = 2;

}

else if (ns == namespaceMax) {

ns = 0;

}

else if (ns == 2) {

break;

}

// skip empty sections

if (namespaceSection[ns] == '') {

continue;

}

// rename all and top section elements

if ( (ns == -2) || (ns == -1) ) {

namespaceSection[ns] = namespaceSection[ns].replace(/(<[^>]*?\bid=(\"?)RC[IML]\d+)\2/g, '$1.' + ns + '$2');

namespaceSection[ns] = namespaceSection[ns].replace(/(<[^>]*?=\"?javascript:toggleVisibility\(\'RC[IML]\d+)(\',\s*\'RC[IML]\d+)(\',\s*\'RC[IML]\d+)(\')/g, '$1.' + ns + '$2.' + ns + '$3.' + ns + '$4');

}

var sectionStyle = '';

var headerToggle = '';

var footerToggle = '';

if (ns == -1) {

var topToggle = WWText['WWRecentTopToggle'].replace(/\{day\}/g, day)

headerToggle += ' ' + topToggle;

footerToggle = '

' + topToggle + '
';

if (wwRecentchanges == true) {

sectionStyle = ' style="display: none;"';

}

}

else if (ns == -2) {

var allToggle = WWText['WWRecentAllToggle'].replace(/\{day\}/g, day)

headerToggle = ' ' + allToggle;

footerToggle = '

' + allToggle + '
';

if (wwRecentchanges == false) {

sectionStyle = ' style="display: none;"';

}

}

// add sections to sorted watchlist

sorted += '

' + namespaceName[ns] + headerToggle + '
' + dayContainerTop + namespaceSection[ns] + dayContainerBottom + footerToggle + '
';

// remember section block names

sectionBlockIds.push('WWSectionBlock' + ns + '-' + day);

}

// finish day

day ++;

}

// return changed document.body

return(namespaceBlock + '

' + sorted + '
' + pageBottom);

}

);

// ceate list of expandable entries for closing popups

var divs = document.getElementsByTagName('div');

for (var i = 0; i < divs.length; i ++) {

if (divs[i].id.match(/^RCI[\d\.\-]+/) != null) {

wwEditDivs.push(divs[i]);

}

}

// regexp to identify article links from link href

var regExpStr = '^' + mw.config.get('wgServer') + mw.config.get('wgArticlePath') + '$';

regExpStr = regExpStr.replace(/\$\d/, '[^\\?]*?');

regExpStr = regExpStr.replace(/(file:\/\/)([A-Z]:)(\/)/, '$1(/$2)?$3');

regExpStr = regExpStr.replace(/\/()/g, '\\/');

regExpStr = regExpStr.replace(/\./g, '\\.');

var regExpArticleLink = new RegExp(regExpStr);

// get all page links

var bodyContent = document.getElementById('bodyContent');

if (bodyContent == null) {

return;

}

var links = bodyContent.getElementsByTagName('a');

// cycle through all links

var linkNumber = 0;

for (var i = 0; i < links.length; i ++) {

// check if this is an article link

if (links[i].innerHTML.replace(/\"/g, '') != links[i].title) {

continue;

}

if (links[i].href.match(regExpArticleLink) == null) {

continue;

}

// add mouseover event to link

WWAddEventListener(links[i], 'mouseover', WWOpenPopup, false);

// remove title

links[i].title = '';

// find the container table element

var mainTable = links[i];

while (mainTable != null) {

if (mainTable.nodeName == 'TABLE') {

break;

}

mainTable = mainTable.parentNode;

}

// check if next sibling is an edits div

if (mainTable == null) {

continue;

}

var editsDiv = mainTable.nextSibling;

while (editsDiv != null) {

if (editsDiv.nodeType != 3) {

break;

}

editsDiv = editsDiv.nextSibling;

}

// add link id for easy access of related edits div

if (editsDiv == null) {

continue;

}

if (editsDiv.nodeName != 'DIV') {

continue;

}

var editsDivId = editsDiv.id;

if (editsDivId.match(/^RCI[\d\.\-]+/) != null) {

links[i].id = 'WW' + linkNumber + '-' + editsDivId;

linkNumber ++;

}

}

// close popup when leaving a section

for (var i = 0; i < sectionBlockIds.length; i ++) {

WWAddEventListener( document.getElementById( sectionBlockIds[i] ), 'mouseout', WWClosePopupsHandler, false );

}

return;

}

//

// WWToggleVisibility: replacement for toggleVisibility

//

function WWToggleVisibility(levelId, otherId, linkId) {

var thisLevel = document.getElementById(levelId);

var otherLevel = document.getElementById(otherId);

var linkLevel = document.getElementById(linkId);

if (thisLevel.className == 'WWPopup') {

thisLevel.style.display = 'none';

thisLevel.className = 'WWStandardExpand';

}

if (thisLevel.style.display == 'none') {

thisLevel.style.display = 'block';

otherLevel.style.display = 'none';

linkLevel.style.display = 'inline';

}

else {

thisLevel.style.display = 'none';

otherLevel.style.display = 'inline';

linkLevel.style.display = 'none';

}

WWClosePopups(levelId);

return;

}

var toggleVisibility = WWToggleVisibility;

//

// WWOpenPopup: open detailed edits preview

//

function WWOpenPopup(event) {

// MS IE compatibility fix

event = WWEvent(event);

if (event == null) {

return;

}

// get link id with div RCI id

var lnk = event.target

var levelId;

if (lnk == null) {

return;

}

if (lnk.nodeName != 'A') {

return;

}

// only close popups for single-entry links

if (lnk.id == '') {

WWClosePopups();

}

// popup edits

else {

levelId = lnk.id.replace(/^WW\d+-/, '');

if (levelId != '') {

var thisLevel = document.getElementById(levelId);

// show edits div as popup

thisLevel.className = 'WWPopup';

thisLevel.style.display = 'block';

wwOpenPopup = thisLevel;

// down arrow back to right arrow

var regExpMatch = /^RCI([\d\.\-]+)/.exec(levelId);

if (regExpMatch != null) {

var idNumber = regExpMatch[1];

var otherLevel = document.getElementById('RCM' + idNumber);

var linkLevel = document.getElementById('RCL' + idNumber);

otherLevel.style.display = 'inline';

otherLevel.blur();

linkLevel.style.display = 'none';

}

WWClosePopups(levelId);

}

}

return;

}

//

// WWClosePopups: close all edit popups

//

function WWClosePopups(levelId) {

if (wwOpenPopup != null) {

if (wwOpenPopup.id != levelId) {

wwOpenPopup.className = '';

wwOpenPopup.style.display = 'none';

wwOpenPopup = null;

}

}

for (var i = 0; i < wwEditDivs.length; i ++) {

if (wwEditDivs[i].className == 'WWPopup') {

if (wwEditDivs[i].id != levelId) {

wwEditDivs[i].className = '';

wwEditDivs[i].style.display = 'none';

}

}

}

return;

}

//

// WWClosePopupsHandler: close all edit popups when leaving the section

//

function WWClosePopupsHandler(event) {

// MS IE compatibility fix

event = WWEvent(event);

if (event == null) {

return;

}

event.stopPropagation();

if (event.relatedTarget != null) {

if (event.relatedTarget.id != '') {

if (event.relatedTarget.id.match(/\bRCI\d+\.+/) == null) {

WWClosePopups();

}

}

}

return;

}

//

// WWEvent: MS IE compatibility fix for event object

//

function WWEvent(event) {

var eventAlt;

if (window.event != null) {

eventAlt = window.event;

if (eventAlt != null) {

event = eventAlt;

event.stopPropagation = function() {

event.cancelBubble = true;

};

event.preventDefault = function() {

event.returnValue = false;

};

event.target = event.srcElement;

if (event.type == 'mouseout') {

event.relatedTarget = event.toElement;

}

else if (event.type == 'mouseover') {

event.relatedTarget = event.fromElement;

}

}

}

return(event);

}

//

// WWAddEventListener: wrapper for addEventListener (http://ejohn.org/projects/flexible-javascript-events/)

//

function WWAddEventListener(domElement, eventType, eventHandler, useCapture) {

if (domElement != null) {

if (domElement.attachEvent != null) {

domElement['WW' + eventType + eventHandler] = eventHandler;

domElement[eventType + eventHandler] = function() {

domElement['WW' + eventType + eventHandler](window.event);

}

domElement.attachEvent('on' + eventType, domElement[eventType + eventHandler] );

}

else {

domElement.addEventListener(eventType, eventHandler, useCapture);

}

}

return;

}

//

// WWRemoveEventListener: wrapper for removeEventListener

//

function WWRemoveEventListener(domElement, eventType, eventHandler, useCapture) {

if (domElement.detachEvent != null) {

domElement.detachEvent('on' + eventType, domElement[eventType + eventHandler]);

domElement[eventType + eventHandler] = null;

}

else {

domElement.removeEventListener(eventType, eventHandler, useCapture);

}

return;

}

//

// WWStyleSheet: create a new style sheet object

//

function WWStyleSheet(contextObj) {

if (contextObj == null) {

contextObj = document;

}

this.styleElement = null;

// MS IE compatibility

if (contextObj.createStyleSheet) {

this.styleElement = contextObj.createStyleSheet();

}

// standards compliant browsers

else {

this.styleElement = contextObj.createElement('style');

this.styleElement.from = 'text/css';

var insert = contextObj.getElementsByTagName('head')[0];

if (insert != null) {

this.styleElement.appendChild(contextObj.createTextNode(''));

insert.appendChild(this.styleElement);

}

}

//

// WWStyleSheet.addRules: add all rules at once, much faster

//

this.addRules = function(rules) {

// MS IE compatibility

if (this.styleElement.innerHTML == null) {

this.styleElement.cssText = rules;

}

// via innerHTML

else {

this.styleElement.innerHTML = rules;

}

return;

}

}

//

// WWDebug: print the value of variables, use either a single value or a description followed by a value

//

function WWDebug(objectName, object) {

// create debug textarea and add to debug wrapper

if (window.WWDebugTextarea == null) {

window.WWDebugTextarea = document.createElement('textarea');

WWDebugTextarea.id = 'WWDebug';

WWDebugTextarea.className = 'WWDebug';

WWDebugTextarea.rows = 20;

WWDebugTextarea = document.body.insertBefore(WWDebugTextarea, document.body.firstChild);

}

WWDebugTextarea.style.display = 'block';

if (objectName == null) {

WWDebugTextarea.value = '';

}

else {

if (object == null) {

WWDebugTextarea.value = objectName + '\n' + WWDebugTextarea.value;

}

else {

WWDebugTextarea.value = objectName + ': ' + object + '\n' + WWDebugTextarea.value;

}

}

return;

}

var WWD = WWDebug;

// schedule setup routine

if ( (typeof(mw.config.get('wgCanonicalNamespace')) != 'undefined') && (typeof(mw.config.get('wgCanonicalSpecialPageName')) != 'undefined') ) {

if (

(mw.config.get('wgCanonicalNamespace') == 'Special') && (

(mw.config.get('wgCanonicalSpecialPageName') == 'Watchlist') ||

(mw.config.get('wgCanonicalSpecialPageName') == 'Recentchanges') ||

(mw.config.get('wgCanonicalSpecialPageName') == 'Contributions')

)

) {

WWAddEventListener(window, 'load', WWMain, false);

}

}

// close global wrapper

return;

}

// append wrapper to head if run under Greasemonkey

if (window.parent == window) {

if (typeof(GM_getValue) == 'function') {

if ( (document.getElementById('WWHeadScript') == null) && (window.wwInstalledFlag == false) ) {

window.wwInstalledFlag = true;

var script = document.createElement('script');

script.id = 'WWHeadScript';

script.type = 'text/javascript';

script.text = 'WWHeadWrapper = ' + WWWrapper.toString() + '; WWHeadWrapper();';

document.getElementsByTagName('head')[0].appendChild(script);

}

}

// otherwise run installation directly

else {

WWWrapper();

}

}

//