User:Interaccoonale/ToolsRedirect/tools-redirect.js

// /* vim:set noexpandtab ft=javascript ts=4 sw=4: */

( function ( $ ) {	"use strict";

var _TR, origPageName = mw.config.get( "wgPageName" ), scriptPath = mw.config.get( "wgScriptPath" ), nsNumber = mw.config.get( "wgNamespaceNumber" ), isCategory = nsNumber === 14, _findRedirectCallbacks = [], _pageWithRedirectTextSuffix = {}, _redirectExcludes = {}, SUFFIX_APPEND = 0, SUFFIX_REPLACE = 1, SUFFIX_SETDEFAULT = 2, _nsCanonPrefix = origPageName.split( ":" )[ 0 ] + ":", _nsPrefixPattern = $.map(			mw.config.get( "wgNamespaceIds" ), function ( nsid, text ) {				return nsid === nsNumber ? text : null;			} ).join( "|" ); _nsPrefixPattern = new RegExp( "^(" + _nsPrefixPattern + "):", "i" );

if ( nsNumber === 0 ) { // articles _nsCanonPrefix = ""; _nsPrefixPattern = /^/; }

function fixNamespace( title ) { if ( nsNumber === 0 ) { // do nothing if it's articles return title; } else if ( _nsPrefixPattern.test( title ) ) { // canonize the namespace return title.replace( _nsPrefixPattern, _nsCanonPrefix ); } else { // don't have a namespace return _nsCanonPrefix + title; }	}

mw.toolsRedirect = {

SUFFIX_APPEND: SUFFIX_APPEND, SUFFIX_REPLACE: SUFFIX_REPLACE, SUFFIX_SETDEFAULT: SUFFIX_SETDEFAULT,

findRedirectCallback: function ( callback ) { /* Add new custom callback for finding new * potential redirect titles. *			 * @param {function} callback( pagename, $content, titles ) -> title list */			if ( arguments.length === 1 ) { _findRedirectCallbacks.push( callback ); } else { $.merge( _findRedirectCallbacks, arguments ); }			return this; },

findRedirectBySelector: function ( selector ) { /* A shortcut to add CSS selectors as rule to find new potential * redirect titles. *			 * @param {string} selector */			_findRedirectCallbacks.push( function {				return $( selector ).map( function  { var title = $( this ).text; return title || null; } );			} );			return this; },

setRedirectTextSuffix: function ( title, suffix, flag ) { var flag_set = false, flag_append = false; flag = flag || SUFFIX_APPEND; // default append flag_set = flag === SUFFIX_REPLACE; title = fixNamespace( title ); if ( title in _pageWithRedirectTextSuffix ) { flag_append = flag === SUFFIX_APPEND; } else { // if not exist, every flag can set flag_set = true; }

if ( flag_set ) { _pageWithRedirectTextSuffix[ title ] = suffix; } else if ( flag_append ) { _pageWithRedirectTextSuffix[ title ] = _pageWithRedirectTextSuffix[ title ] + suffix; }		}

};

_TR = { msg: null, tabselem: null, tagselem: null, variants: [ "zh-hans", "zh-hant", "zh-cn", "zh-hk", "zh-mo", "zh-sg", "zh-tw" ],		init: function { if ( !this.msg ) { return; } // not setup correctly var self = this, btn = $(					" " +					this.msg.btntitle + " " ); btn.click( function ( evt ) {				evt.preventDefault;				self.dialog;			} ); $( "li#ca-history" ).after( btn ); },		dialog: function { var dlg = $( "" ) .dialog( {					bgiframe: true, resizable: false, modal: true, width: 600 } ); this.tabselem = $( "" ).appendTo( dlg ); this.tagselem = $( "" ).appendTo( this.tabselem ); this.addTabs; this.tabselem.tabs; },		addTabs: function { for ( var kname in this.tabs ) { if ( this.tabs[ kname ] === null ) { this.tabs[ kname ] = this[ "_initTab" + kname[ 0 ].charAt( 0 ).toUpperCase + kname.slice( 1 ) ]; }				var tab = this.tabs[ kname ]; this.tagselem.append( tab.tag ); this.tabselem.append( tab.cont ); }			// default tab, autoload when dialog initiate this.loadView; },		createTab: function ( tabname, tabtitle, onClick ) { var self = this, tag = $( "" + tabtitle + "" ), cont = $( "" ); $( "a", tag ).on( "click", function {				onClick.call( self );			} ); return { tag: tag, cont: cont, loaded: false }; },		_initTabView: function { return this.createTab( "view", this.msg.tabviewtitle, this.loadView ); },		_initTabCreate: function { return this.createTab( "create", this.msg.tabcreatetitle, this.loadCreate ); },		tabs: { view: null, create: null },		fix: function ( pagenames ) { var self = this; $( "p.desc", this.tabs.view.cont ).text( this.msg.fixloading ); $( "p[class!=desc]", this.tabs.view.cont ).remove; this.loading( this.tabs.view.cont ); this .bulkEdit(					pagenames,					this.msg.fixtext.replace( "$1", origPageName ),					this.msg.fixsummary				) .done( function {				// delay load before the asynchronous tasks on server finished					setTimeout( function  { self.loaded( self.tabs.view.cont ); self.loadView( true ); }, 3000 );				} );		},		create: function ( pagenames ) { var self = this; $( "p.desc", this.tabs.create.cont ).text( this.msg.createloading ); $( "p[class!=desc]", this.tabs.create.cont ).remove; this.loading( this.tabs.create.cont ); this .bulkEdit(					pagenames,					this.msg.createtext.replace( "$1", origPageName ),					this.msg.createsummary.replace( "$1", origPageName )				) .done( function {				// delay load before the asynchronous tasks on server finished					setTimeout( function  { self.loaded( self.tabs.create.cont ); self.tabs.view.loaded = false; self.loadCreate( true ); }, 500 );				} );		},		addRedirectTextSuffix: function ( title, text ) { if ( title in _pageWithRedirectTextSuffix ) { text = text + _pageWithRedirectTextSuffix[ title ]; }			return text; },		bulkEdit: function ( titles, text, summary ) { var self = this; titles = titles.filter( function ( v, i, arr ) {				return arr.indexOf( v ) === i;			} ); titles = titles.join( "|" );

return $.ajax( this.buildQuery( { action: "query", prop: "info", titles: titles, meta: "tokens" } ) ) .then( function ( data ) {					var deferreds = [];					$.each( data.query.pages, function ( idx, page ) { deferreds.push( $.ajax( self.buildQuery( {							action: "edit",							title: page.title,							token: data.query.tokens.csrftoken,							text: self.addRedirectTextSuffix( page.title, text ),							summary: summary,							// tags: "ToolsRedirect"						} ) ) ); } );					return $.when.apply( $, deferreds );				} ); },		loadTabCont: function ( tabname, callback, reload ) { var self = this, tab = this.tabs[ tabname ]; if ( reload ) { tab.loaded = false; }			if ( !tab.loaded ) { tab.cont.html( "" ); var $desc = $( "" +					this.msg.rediloading + " " ).appendTo( tab.cont ), $text = $desc.find( "> .desc-text" ); callback.apply( this ) .done( function { $text.text( self.msg[ "tab" + tabname + "desc" ] ); } ) .fail( function { $text.text( self.msg[ "tab" + tabname + "notfound" ] ); } ) .always( function {						self.addMethods( $desc, [ { href: "#refresh", title: self.msg.refresh, click: function ( evt ) { evt.preventDefault; self.loadTabCont( tabname, callback, true ); }						} ] );					} );				tab.loaded = true; }		},		loading: function ( container ) { if ( container.prop( "tagName" ).toLowerCase === "span" ) { container.addClass( "mw-ajax-loader" ); } else if ( $( "span.mw-ajax-loader", container ).length === 0 ) { $( " " ).appendTo( container ); }		},		loaded: function ( container ) { if ( container.prop( "tagName" ).toLowerCase === "span" ) { container.removeClass( "mw-ajax-loader" ); } else { $( "span.mw-ajax-loader", container ).remove; }		},		selectAll: function ( cont ) { $( "input[type=checkbox]:not(:disabled)", cont ) .prop( "checked", true ); },		selectInverse: function ( cont ) { $( "input[type=checkbox]:not(:disabled)", cont ) .each( function {					var e = $( this );					e.prop( "checked", !e.prop( "checked" ) );				} ); },		selectAction: function ( cont, cb ) { var pagenames = []; $( "input[type=checkbox]:checked", cont ) .each( function {					pagenames.push( $( this ).data( "page-title" ) );				} ); if ( pagenames.length ) { cb.call( this, pagenames ); }		},		clickAction: function ( cont, cb ) { var pagename = $( "input[type=\"checkbox\"]", cont ).data( "page-title" ); cb.call( this, [ pagename ] ); },		buildLink: function ( attr ) { var a = $(				"" + attr.title + "</a>" ); if ( attr.click ) { a.on( "click", attr.click ); }			if ( attr.classname ) { a.addClass( attr.classname ); }			return $( "<span class=\"tools-redirect_link\">" ).append( a ); },		addMethods: function ( $parent, methods ) { var self = this, $container = $parent.find( "> .tools-redirect_methods" );

function methodExist( method ) { return $container.find(					"a[href=" + JSON.stringify( method.href ) + "]" ).length > 0; }

if ( $container.length === 0 ) { $container = $( "<span class=\"tools-redirect_methods\">" ).appendTo( $parent ); }

$.each( methods, function ( idx, method ) {				if ( !methodExist( method ) ) {					self.buildLink( method ).appendTo( $container );				}			} ); },		buildSelection: function ( main, metd, mt, dsab ) { var cont = $( " " ), sele = $( "<input type=\"checkbox\"/>" ).appendTo( cont ); this.buildLink( main ).appendTo( cont ); this.addMethods( cont, metd ); sele.data( "page-title", mt ); if ( dsab ) { sele.attr( "disabled", true ); }			return cont; },		loadView: function ( reload ) { var $container = this.tabs.view.cont; this.loadTabCont( "view", function {				return this.loadRedirect( origPageName, $container, 0 );			}, reload ); },		loadCreate: function ( reload ) { this.loadTabCont( "create", function {				return this.findRedirect( origPageName );			}, reload ); },		loadRedirect: function ( pagename, container, deep, loaded ) { this.loading( container ); var self = this, deferObj = $.Deferred, top = deep ? $( "<dl>" ).appendTo( container ) : container;

if ( !loaded ) { loaded = {}; loaded[ pagename ] = true; }

function onClickFix( evt ) { /* jshint validthis: true */ var entry = $( this ).parents( "dd, p" ).first; evt.preventDefault; self.clickAction( entry, self.fix ); }

$.ajax( this.buildQuery( { action: "query", prop: "redirects", titles: pagename, rdlimit: "max" } ) ) .done( function ( data ) {					self.loaded( container );					var has_redirect = false,						desc = $( "p.desc", self.tabs.view.cont ),						maximumRedirectDepth = mw.config.get( "toolsRedirectMaximumRedirectDepth", 10 );

$.each( data.query.pages, function ( _, page ) {						if ( !( "redirects" in page ) ) {							return;						}						$.each( page.redirects, function ( _, rdpage ) { var $container, isCycleRedirect, rdtitle = rdpage.title, ultitle = rdtitle.replace( / /g, "_" ), baseuri = scriptPath + "/index.php?title=" + encodeURIComponent( ultitle ), entry = ( deep ? $( "<dd>" ) : $( " " ) ).appendTo( top ), methods = [ { href: baseuri + "&action=edit", title: self.msg.rediedit } ];							isCycleRedirect = rdtitle in loaded; loaded[ rdtitle ] = true; if ( !isCycleRedirect && deep ) { methods.push( {									href: "#fix-redirect",									title: self.msg.tabviewfix,									click: onClickFix								} ); }							$container = self.buildSelection(								{ href: baseuri + "&redirect=no", title: rdtitle },								methods, ultitle, !deep ).appendTo( entry ); if ( isCycleRedirect ) { $container.append(									"<span class=\"error\">" + self.msg.errcycleredirect + " " ); } else if ( deep < maximumRedirectDepth ) { deferObj.done( function {									return self.loadRedirect( rdtitle, entry, deep + 1, loaded );								} ); }							has_redirect = true; } );					} );

if ( has_redirect && deep === 1 ) { self.addMethods( desc, [ {							href: "#select-all",							title: self.msg.selectall,							click: function ( evt ) {								evt.preventDefault;								self.selectAll( self.tabs.view.cont );							}						}, {							href: "#select-inverse",							title: self.msg.selectinverse,							click: function ( evt ) {								evt.preventDefault;								self.selectInverse( self.tabs.view.cont );							}						}, {							href: "#fix-selected",							title: self.msg.tabviewfix,							click: function ( evt ) {								evt.preventDefault;								self.selectAction( self.tabs.view.cont, self.fix );							}						} ] ); }

if ( has_redirect ) { deferObj.resolveWith( self ); } else { deferObj.rejectWith( self ); }

} );

return deferObj.promise; },		findVariants: function (pagename, titles) { var self = this, suffixReg = /^.+?( \(.+?\))$/, retTitles = [], deferreds = []; // Function to convert the first character of each word to uppercase function convertFirstCharToUpperCase(input) { return input.replace(/\b\w/g, function (txt) {		           return txt.toUpperCase;		        }); }		   $.each(this.variants, function (_, variant) {		        var xhr = $.ajax(self.buildQuery({		            action: "parse",		            page: pagename,		            prop: "displaytitle",		            variant: variant		        }))		        .then(function (data) { var title = fixNamespace(data.parse.displaytitle); // Check if the variant is English if (variant === 'en') { // Convert the first character of each word to uppercase title = convertFirstCharToUpperCase(title); }		           return title; });		       deferreds.push(xhr);		    }); return $.when.apply($, deferreds) .then(function {		            var suffixes = [];		            $.each(arguments, function  { var suffix, title = this; // find title suffix, // for example " (济南市)" to "市中区 (济南市)" suffix = suffixReg.exec(title); if (suffix && suffix.length === 2) { suffix = suffix[1]; } else { suffix = ""; }		               retTitles.push(title); suffixes.push(suffix); });		           // append suffixes		            $.each($.uniqueSort(suffixes), function (_, suffix) { $.merge(retTitles, $.map(titles, function (title) { title = fixNamespace(title); return suffixReg.test(title) ? title : title + suffix; }));		           });		            return self.findNotExists($.uniqueSort(retTitles));		        }); },		findNotExists: function (titles) { var self = this, deferreds = [], alltitles = [], excludes = ["用字模式"]; titles = titles.join("|"); // Regular expression to match different case variations of the English title var caseVariantsReg = /^(.)(.*)$/; $.each(["zh-hans", "zh-hant"], function (idx, variant) {		       deferreds.push($.ajax(self.buildQuery({ action: "parse", text: titles, prop: "text", variant: variant })));		   });		    return $.when.apply($, deferreds) .then(function {		            $.each(arguments, function  { alltitles = alltitles.concat(		                   $(this[0].parse.text["*"])		                        .text.replace(/(^\s*|\s*$)/g, "").split("|")		                ); });		           alltitles = alltitles.filter(function (v, i, arr) { return arr.indexOf(v) === i;		           });		            alltitles = alltitles.join("|");		            // Regular expression to match different case variations of the English title		            var caseVariantMatch = caseVariantsReg.exec(alltitles);		            if (caseVariantMatch) {		                var firstCharUpperCase = caseVariantMatch[1].toUpperCase;		                var restOfString = caseVariantMatch[2].toLowerCase;		                alltitles = firstCharUpperCase + restOfString;		            }		            return $.ajax(self.buildQuery({		                action: "query",		                prop: "info",		                titles: alltitles		            }))		            .then(function (data) { titles = []; $.each(data.query.pages, function (pageid, page) {		                   var title = page.title;		                    if (pageid < 0 && excludes.indexOf(title) === -1) {		                        if (title in _redirectExcludes) {		                            // exclude special titles		                            return;		                        }		                        titles.push(title);		                        if (isCategory) {		                            var target = origPageName.replace(/^Category:/, "");		                            mw.toolsRedirect.setRedirectTextSuffix( title, "\n".replace("$1", target) );		                       }		                        // only set default suffix		                        mw.toolsRedirect.setRedirectTextSuffix( title, "\n", SUFFIX_SETDEFAULT );		                   }		                });		                return titles; });		       });		},

findRedirect: function ( pagename ) { var self = this, titles = [], frcDeferreds = [], container = this.tabs.create.cont, $content = $( "#mw-content-text > div.mw-parser-output" ), deferObj = $.Deferred; this.loading( container );

$.each( _findRedirectCallbacks, function ( _, callback ) {				var ret = callback( pagename, $content, titles );				if ( typeof ret === "string" ) {					titles.push( ret );				} else if ( "done" in ret ) {					// is Deferred					frcDeferreds.push( ret );				} else {					$.merge( titles, ret );				}			} );

// remove all empty titles titles = $.map( titles, function ( title ) {				return title || null;			} );

function onClickCreate( evt ) { /* jshint validthis: true */ var entry = $( this ).parents( "p:first" ); evt.preventDefault; self.clickAction( entry, self.create ); }

// handles the deferred callbacks $.when.apply( $, frcDeferreds ) .then( function {					$.each( arguments, function ( _, ret ) { if ( typeof ret === "string" ) { titles.push( ret ); } else { $.merge( titles, ret ); }					} );					return self.findVariants( pagename, titles );				} ) .done( function ( titles ) {				// build HTML					self.loaded( container );					$.each( titles, function ( _, title ) { var ultitle = title.replace( " ", "_" ), baseuri = scriptPath + "/index.php?title=" + encodeURIComponent( ultitle ), entry = $( " " ).appendTo( container ); self.buildSelection(							{								href: baseuri + "&action=edit&redlink=1",								title: title,								classname: "new"							},							[ {								href: "#create-redirect",								title: self.msg.tabcreatetitle,								click: onClickCreate							} ],							ultitle, false						).appendTo( entry ); } );					var desc = $( "p.desc", container );					if ( titles.length > 0 ) {						self							.addMethods( desc, [ { href: "#select-all", title: self.msg.selectall, click: function ( evt ) { evt.preventDefault; self.selectAll( container ); }							}, {								href: "#select-inverse", title: self.msg.selectinverse, click: function ( evt ) { evt.preventDefault; self.selectInverse( container ); }							}, {								href: "#create-selected", title: self.msg.tabcreatetitle, click: function ( evt ) { evt.preventDefault; self.selectAction( container, self.create ); }							} ] );						deferObj.resolveWith( self, [ titles ] );					} else {						deferObj.rejectWith( self, [ titles ] );					}				} );

return deferObj.promise; },		buildQuery: function ( data ) { var query = { url: scriptPath + "/api.php", dataType: "json", type: "POST" }; query.data = data; query.data.format = "json"; return query; }	};

$.when(		mw.loader.getScript( "/w/index.php?title=User:Interaccoonale/ToolsRedirect/tools-redirect-msg-en.js&action=raw&ctype=text/javascript" ),		$.ready	).done( function {		_TR.msg = window.tools_redirect_msg;		_TR.init;		mw.toolsRedirect.findRedirectBySelector( 'div#mw-content-text p > b' );		mw.toolsRedirect.findRedirectBySelector( 'div#mw-content-text p > i > b' );	} ); }( jQuery ) ); //