User:Danski454/stubsearch.js

Source: Wikipedia, the free encyclopedia.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// <nowiki>
//from a request by Insertcleverphrasehere
//based on [[User:Ais523/stubtagtab2.js]], which is in turn based on [[User:MC10/stubtagtab.js]]

//variables
var SSunfilteredArray = ["stub"];//does not contain parent
var stubTags = {"stub":{display:"{{Stub}}", parents:["stubs"], children:null, descendants:null, template:"Stub"},"stubs":{display:"Stubs", parents:null, children:[], descendants:["stub"], template:null}};//generic stub is not loaded by the normal method
var SSfilteredArray = ["stub"];
var stublists = ['Architecture', 'Commerce', 'Culture', 'Education', 'Geography', 'Government, law, and politics',
			'History', 'Leisure', 'Military and weaponry', 'Organizations', 'People',
			'Religion, mythology, faiths, and beliefs', 'Science', 'Sports', 'Technology', 'Transport',
			'Miscellaneous'];
			
var stubTemplates = ["{{stub}}"];

var SSactive = false;

var SScurrent = stubTags["stubs"];
var SSoldid = null;
var SSnewid = null;
var SSpagesLoaded = 0;
var SSextraChildren = [{parent:"education stubs", children:["school stubs", "university stubs"]}, 
	{parent:"company stubs", children:["Aeronautical company stubs", "Airline stubs", "Animation studio stubs", "Brand name products stubs", "Chemical company stubs", 
		"Disney stubs", "Energy company stubs", "Fashion company stubs", "Film company stubs", "Financial services company stubs", "Food company stubs", "Hotel stubs", 
		"Industrial company stubs", "Information technology company stubs", "Leisure company stubs", "Manufacturing company stubs", "Media company stubs", 
		"Motor vehicle company stubs", "Music industry company stubs", "Musical instrument company stubs", "Petroleum company stubs", "Publishing company stubs", 
		"Restaurant stubs", "Retail company stubs", "Service company stub", "Technological company stubs", "Telecommunications company stubs", "Toy company stubs", 
		"Transport company stubs", "Video game company stubs", "African company stubs", "Asian company stubs", "European company stubs", "Canadian company stubs", 
		"Mexican company stubs", "United States company stubs", "Australian company stubs", "South American company stubs"]}, 
	{parent:"politics stubs", children:["Asia politics stubs", "Europe politics stubs", "Canada politics stubs", "United States politics stubs", "Mexican politics stubs", 
		"New Zealand politics stubs", "South America politics stubs", "Political party stubs", "Political people stubs", "Politician stubs"]}, 
	{parent:"politician stubs", children:["African politician stubs", "Asian politician stubs", "European politician stubs", "North American politician stubs", 
		"Oceanian politician stubs", "South American politician stubs"]}, 
	{parent:"history stubs", children:["American Old West stubs", "Ancient Near East stubs", "Ancient Egypt stubs", "Ancient Greece stubs", "Ancient Rome stubs", 
		"Byzantine Empire stubs", "Kingdom of France stubs", "Mesoamerica stubs", "Nazi Germany stubs", "Netherlands Antilles stubs", "Ottoman Empire stubs", 
		"Pre-Columbian era stubs", "Soviet Union stubs", "Yugoslavia stubs", "African diaspora stubs", "East Slavic history stubs", "Jewish history stubs", 
		"African history stubs", "Asian history stubs", "European history stubs", "Canadian history stubs", "Mexican history stubs", "United States history stubs", 
		"South American history stubs", "Military history stubs"]}, 
	{parent:"people stubs", children:["Academic biography stubs", "Artist stubs", "Astronaut stubs", "Business biography stubs", "Entertainer stubs", "Explorer stubs", 
		"Fashion biography stubs", "Graphic designer stubs", "Journalist stubs", "Medical biography stubs", "Model stubs", "Paramilitary biography stubs", "Piracy stubs", 
		"Religious biography stubs", "Scientist stubs", "Writer stubs", "African people stubs", "Asian people stubs", "European people stubs", "North American people stubs",
		"Caribbean people stubs", "Central American people stubs", "Oceanian people stubs", "South American people stubs", "Nobility stubs", "Royalty stubs"]}, 
	{parent:"scientist stubs", children:["Asian scientist stubs", "African scientist stubs", "Australian scientist stubs", "European scientist stubs", "North American scientist stubs", 
		"South American scientist stubs"]}, 
	{parent:"science stubs", children:["scientist stubs", "Biology stubs", "Botany stubs", "Animal stubs", "Astronomy stubs", "Chemistry stubs", "Atmospheric science stubs", 
		"Geology stubs", "Oceanography stubs", "Physics stubs", "Health stubs", "Anthropology stubs", "Library and information science stubs", "Sociology stubs", "Natural science stubs"]}, 
	{parent:"chordate stubs", children:["Amphibian stubs", "Bird stubs", "Fish stubs", "Mammal stubs", "Reptile stubs"]}, //Amphibians, Birds, Fish, Mammals, and Reptiles
	{parent:"protostome stubs", children:["Mollusc stubs"]}, 
	{parent:"arthropod stubs", children:["insect stubs", "Arachnid stubs"]}, 
	{parent:"technology stubs", children:["Computing stubs", "Software stubs", "Telecommunications stubs", "Automotive part stubs", "Automotive technology stubs", "Electronics stubs", 
		"Energy stubs", "Engineering stubs", "Industry stubs", "Metalworking stubs", "Robotics stubs", "Sound technology stubs", "Tool stubs", "Woodworking stubs"]}, 
	{parent:"transport stubs", children:["Africa transport stubs", "Asia transport stubs", "Europe transport stubs", "North America transportation stubs", "Oceania transport stubs", 
		"South America transport stubs", "Space stubs", "Vehicle stubs", "Water transport stubs", "Aviation stubs", "Rail transport stubs", "Road stubs"]}, 
	{parent:"military stubs", children:["African military stubs", "Argentine military stubs", "Asian military stubs", "Australian military stubs", "Canadian military stubs", 
		"European military stubs", "Mexican military stubs", "United States military stubs", "Military personnel stubs"]}, 
	{parent:"organization stubs", children:["Trade union stubs", "Art organization stubs", "Automotive organization stubs", "Business organization stubs", 
		"Criminal organization stubs", "Education organization stubs", "Environmental organization stubs", "Film organization stubs", "Fraternity and sorority stubs",
		"Freemasonry stubs", "International organization stubs", "Lesbian, gay, bisexual, transgender (LGBT) organization stubs", "Linguistics organization stubs",
		"Medical organization stubs", "Music organization stubs", "Non-profit organization stubs", "Paramilitary organization stubs", "Political organisation stubs", 
		"Professional association stubs", "Religious organization stubs", "Scientific organization stubs", "Youth organization stubs", "African organization stubs", 
		"Asian organization stubs", "Australian organisation stubs", "Canadian organization stubs", "European organisation stubs", "Mexican organization stubs", 
		"New Zealand organisation stubs", "South American organization stubs", "United States organization stubs"]}];

//functions
function SSloadDiff(){
	window.location.href = mw.config.get('wgScript') + '?diff=' + SSnewid + '&oldid=' + SSoldid;
}

function SSreduceArray(unreducedArray){
	var seen = {};
    return unreducedArray.filter(function(item) {
        return seen.hasOwnProperty(item) ? false : (seen[item] = true);
    });
}

function SSescape(text, html){
	if (html){
		return text
			.replace('"', "&quot;")
			.replace("&", "&amp;")
			.replace("'", "&#039;")
			.replace("<", "&lt;")
			.replace(">", "&gt;");
		
	} else {
		return text
			.replace('"', "\\x22")
			.replace("&", "\\x26")
			.replace("'", "\\x27")
			.replace("<", "\\x3c")
			.replace(">", "\\x3e");
	}
}

function SSgenerateDescendants(start){
	if (stubTags[start].descendants !== null && stubTags[start].descendants.length !== 0){//already generated descendants
		return stubTags[start].descendants.concat([start]);
	}
	if (stubTags[start].children === null || stubTags[start].children.length === 0){//no descendants
		return [start];//for the rest, we are the only descendant
	}
	stubTags[start].children.forEach( function (value, index, array) {
		stubTags[start].descendants = stubTags[start].descendants.concat(SSgenerateDescendants(value));//add children's descendants to our own
	});
	stubTags[start].descendants = SSreduceArray(stubTags[start].descendants);//remove duplicates
	return stubTags[start].descendants.concat([start]);
}

function SSaddchild(parent, child){
	if (stubTags.hasOwnProperty(parent)){//check parent exists
		if (stubTags[parent].template !== null){//check if parent is a template, if so call this on its parents
			stubTags[parent].parents.forEach(function (value, index, array) { SSaddchild(value, child); });
			return;
		}
		//check if parent already has child
		if (stubTags[parent].children.indexOf(child) !== -1){
			return;
		}
		stubTags[parent].children.push(child);//add child to parents children
	} else {//create a parent
		stubTags[parent] = {display:parent, parents:[], children:[child], descendants:[], template:null};
		stubTags.stubs.descendants.push(parent);//add to stubs' descendants
	}
	if (stubTags.hasOwnProperty(child)){//check if child exists
		stubTags[child].parents.push(parent);//add parent to child
	} else {//create child
		stubTags[child] = {display:child, parents:[parent], children:[], descendants:[], template:null};
		stubTags.stubs.descendants.push(child);//add to stubs' descendants
	}
}

function SSconvertToTemplate(parent, template){
	if (stubTags[parent].children === null){//already a template
		return;
	}
	stubTags[parent].template = template;//set up template and display
	stubTags[parent].display = '{{'+ template +'}}';
	stubTemplates.push(('{{'+ template +'}}'));
	stubTags[parent].children.forEach( function (value, index, array) {//remove all children's parent reference
		index = stubTags[value].parents.indexOf(parent);//don't need index
		if (index > -1) {
			stubTags[value].parents.splice(index, 1);
		}
		SSaddchild(parent, value);//try to readd this as a child of parent
	});
	stubTags[parent].children = null;//clear the array
}

function SSdoExtraChildren(){
	$.each(SSextraChildren, function (){ //do children in array
		var parent = this.parent;
		this.children.forEach(function (value, index, array){
			SSaddchild(parent, value.toLowerCase());
		});
	});
	addStubType("Unlisted stubs", "cat", "stubs");//special case for unlisted stubs
}

function stubFilter(){
	filterString = document.getElementById("stub-search-search").value;
	//Special case: no filterString then only show direct children
	if (filterString.length === 0){
		SSfilteredArray = SScurrent.children;
		SSrerenderList();
		return;
	}
	
	var filteringArray = SSunfilteredArray; //copy unfiltered array
	filterString = filterString.toLowerCase(); //force lowercase
	var filterArray = filterString.split(" "); //split string
	
	for (var filterIndex = 0; filterIndex < filterArray.length; filterIndex++) {
		SSfilteredArray = [];//clear the filtered output
		filterString = filterArray[filterIndex];
		for (var i = 0; i < filteringArray.length; i++) {
			var testString = filteringArray[i];
			if (testString.indexOf(filterString) > -1){
				SSfilteredArray.push(testString);
			}
		}
		filteringArray = SSfilteredArray;
	}
	SSrerenderList();
}

function loadStubs(){
	for (var i = 0; i < stublists.length; i++) {
		var stubType = stublists[i];
		$.ajax({
			url: mw.config.get("wgServer") + mw.config.get("wgScriptPath") + '/api.php?action=parse&prop=text&text=' + encodeURIComponent('__NOTOC____NOEDITSECTION__\{\{Wikipedia:WikiProject Stub sorting/Stub types/' + stubType + '}}') + '&format=json',
			dataType: "json",
			success: stubSearchLoadSome,
			error: function() { 
				SSpagesLoaded++;//increment loaded pages on failure so the menu auto loads
				console.log("Stub search failed to load a stub list.");
			}
		});
	}
	//special case for unlisted stubs
	$.ajax({
		url: mw.config.get("wgServer") + mw.config.get("wgScriptPath") + '/api.php?action=parse&prop=text&text=' + encodeURIComponent('__NOTOC____NOEDITSECTION__\{\{User:Danski454/unlisted stubs}}') + '&format=json',
		dataType: "json",
		success: stubSearchLoadSome,
		error: function() { 
			SSpagesLoaded++;//increment loaded pages on failure so the menu auto loads
			console.log("Stub search failed to load a stub list.");
		}
	});
	SSdoExtraChildren();
}

function stubSearchLoadSome(data) {
	var parseData = $(data.parse.text["*"]);
	var parent = "stubs";
	if (parseData.find("h1, h2").length){
		switch (parseData.find("h1, h2").first().text().toLowerCase()){
			case "sports":
				parent = "sports stubs";
				break;
			case "culture":
				parent = "culture stubs";
				break;
			case "contents":
				parent = "architecture stubs";
				break;
		}
	} else if (parseData.find("h3").length === 0) {//special case for unlisted stubs
		parent = "unlisted stubs";
	}
	parseData.children("ul").children('li').each( function () {
		var val = $(this);//'this' must be converted to jQuery
		while (val.find("small").length !== 0) {//remove small tags (copied from toggleSmall.js)
			var tag = val.find("small").first();
			tag.before(tag.html());
			tag.remove();
		}
		$('dl').remove();//remove description lists
		generateStubData(val, parent);
	});
	if (parent !== "stubs" && parent !== "unlisted stubs"){
		stubTags[parent].children.shift();//remove from its own children
		stubTags[parent].parents = [];//remove from its own parents
		SSaddchild("stubs", parent);//make a child of stubs
	}
	SSpagesLoaded++;
	if (SSpagesLoaded == stublists.length + 1){//one list is not listed in the array
		stubFilter();
	}
}

function addStubType(obj, type, parent){
	var name = obj.toLowerCase();
	SSaddchild(parent, name);//this creates the object
	if (type === "template"){
		SSconvertToTemplate(name, obj);
	} else {
		stubTags[name].display = obj;//set display name
	}
	SSunfilteredArray.push(name);
	SSfilteredArray.push(name);
}

function generateStubData(data, parent){
	var cat;
	cat = data.children("b").first().text();//categories are in bold
	if (cat !== ""){
		addStubType(cat, "cat", parent);
		parent = cat.toLowerCase();
	}
	data.children("ul").children('li').each( function () {
			generateStubData($(this), parent);
	});
	
	data.children("a").each( function () {//template links are not wrapped
		var tag = $(this);
		if (tag[0].previousSibling !== null && tag[0].previousSibling.nodeValue.endsWith("as a child of {{")){//get text before element and make sure it does not say x is a child of y
			return true;
		}
		if (tag[0].previousSibling !== null && tag[0].previousSibling.nodeValue.endsWith("child of {{")){//get text before element and handle next element is child of x
			var next = tag.next();//get next list item
			if (next.is("ul")){//if next is a list get the first item of it
				next = next.children("li").first();
			}
			SSaddchild(tag.text().toLowerCase(), next.children('b').first().text().toLowerCase());//make next link a child of us
			return true;
		}
		if (tag.attr("href").search(/(wiki\/|=)Template:/) === -1){//must link to a template
			return true;
		}
		addStubType(tag.text(), "template", parent);
	});
}

function SSrerenderList(){
	$('#stub-search-scroll').empty();
	SSfilteredArray = SSreduceArray(SSfilteredArray); //remove duplicates
	SSfilteredArray.forEach(function (value, index, array) {
		var tag = stubTags[value];
		if (index > 0){
			$('#stub-search-scroll').append("•");
		}
		$('#stub-search-scroll').append('<input type="button" value="'+ tag.display +'" onclick="selectStub(\''+ SSescape(value, false) +'\');" style="background:none!important;color:inherit;border:none;cursor:pointer;" class="stub-search-type" />');
	});
}

function SSrenderTemplate(){
	$('#stub-search-scroll').empty();
	$('#stub-search-scroll').append('<span id="stub-preview">Preview loading</span>');
	$.ajax({
		url: mw.config.get("wgServer") + mw.config.get("wgScriptPath") + '/api.php?action=parse&prop=text&text=' + encodeURIComponent('__NOTOC____NOEDITSECTION__\{\{' + SScurrent.template + '}}') + '&format=json',
		dataType: "json",
		success: function (data){
			$('#stub-preview').before('<input type="button" value="Add {{'+ SSescape(SScurrent.template, true) +'}} to this article" onclick="addStub();" id="stub-search-confirm" />  <input type="button" value="Replace old stub templates with {{'+ SSescape(SScurrent.template, true) +'}}" onclick="replaceStub(false);" id="stub-search-confirm-replace" />  <input type="checkbox" value="load diff" id="stub-search-load-diff" checked /> Load changes<br />');
			$('#stub-preview').html($(data.parse.text["*"]));
		}
	});
}

function stubSearchUp(){
	var stub = $('#stub-search-parent-select option:selected').val();
	selectStub(stub);
}

function loadStubSearch(){
	if (SSactive){
		return;
	}
	SSactive = true;
	switch ( mw.config.get('skin') ){
		case 'cologneblue':
			$('#mw-content-text').before('<div id="stub-search-box" style="background-color:LightBlue;height:200px;border:1px solid black;padding:15px;">Search: <input type="text" id="stub-search-search"><input id="stub-search-button" type="button" value="Search" onclick="stubFilter();" /> Current selection: <span id="stub-search-cat">Stubs</span> <input id="stub-search-up" type="button" value="Go up to" onclick="stubSearchUp();" /><select id="stub-search-parent-select"></select> <input id="stub-search-diff-button" type="button" value="Load changes" onclick="SSloadDiff();" /><br /><div style="max-height:175px;overflow:auto;" id="stub-search-scroll">Loading stub templates: Please wait</div></div>');
			break;
		case 'timeless':
			$('#bodyContent').before('<div id="stub-search-box" style="background-color:LightBlue;height:200px;border:1px solid black;padding:15px;">Search: <input type="text" id="stub-search-search"><input id="stub-search-button" type="button" value="Search" onclick="stubFilter();" /> Current selection: <span id="stub-search-cat">Stubs</span> <input id="stub-search-up" type="button" value="Go up to" onclick="stubSearchUp();" /><select id="stub-search-parent-select"></select> <input id="stub-search-diff-button" type="button" value="Load changes" onclick="SSloadDiff();" /><br /><div style="max-height:150px;overflow:auto;" id="stub-search-scroll">Loading stub templates: Please wait</div></div>');
			break;
		default:
			$('#bodyContent').before('<div id="stub-search-box" style="background-color:LightBlue;height:200px;border:1px solid black;padding:15px;">Search: <input type="text" id="stub-search-search"><input id="stub-search-button" type="button" value="Search" onclick="stubFilter();" /> Current selection: <span id="stub-search-cat">Stubs</span> <input id="stub-search-up" type="button" value="Go up to" onclick="stubSearchUp();" /><select id="stub-search-parent-select"></select> <input id="stub-search-diff-button" type="button" value="Load changes" onclick="SSloadDiff();" /><br /><div style="max-height:175px;overflow:auto;" id="stub-search-scroll">Loading stub templates: Please wait</div></div>');
	}
	$('#stub-search-up, #stub-search-parent-select, #stub-search-diff-button').hide();//up button, drop down and load diff button default to hidden
	loadStubs();
}

function selectStub(name){
	SSgenerateDescendants(name);//generate the descendants, if they have not already been.
	SScurrent = stubTags[name];
	$('#stub-search-cat').text(SScurrent.display);
	document.getElementById("stub-search-search").value = "";//clear search
	$('#stub-search-parent-select').empty();//clear old parents selector
	if (SScurrent.parents !== null){
		$('#stub-search-up, #stub-search-parent-select').show();//show parent selector and button
		if (SScurrent.parents[0] === "unlisted stubs") {//special case
			$('#stub-search-parent-select').append('<option value="'+ SSescape("stubs", true) +'">'+ SSescape(stubTags["stubs"].display, true) +'</option>')
		}
		SScurrent.parents.forEach(function (value, index, array){
			$('#stub-search-parent-select').append('<option value="'+ SSescape(value, true) +'">'+ SSescape(stubTags[value].display, true) +'</option>')
		});
	} else {
		$('#stub-search-up, #stub-search-parent-select').hide();//hide parent selector and button
	}
	if (SScurrent.template === null){
		SSunfilteredArray = SScurrent.descendants;
		stubFilter();
	} else {
		SSrenderTemplate();
	}
}

function addStub(){
	replaceStub(true);//functions merged
}

function replaceStub(keepOld){//adds a stub template, optionally keeping old ones, this is copied from [[WP:US/G]]
	$.getJSON(
		mw.util.wikiScript('api'),
		{
			format: 'json',
			action: 'query',
			prop: 'revisions',
			rvprop: 'content',
			rvlimit: 1,
			titles: mw.config.get( 'wgPageName' )
		}
	)
		.done(function ( data ) {
			var page, wikitext;
			try {
				for ( page in data.query.pages ) {
					wikitext = data.query.pages[page].revisions[0]['*'];
					replaceStubWikitext( wikitext, keepOld );
				}
			} catch ( e ) {
				console.log(1);
				alert( 'Edit failed' );
			}
		})
		.fail( function() {
			console.log(2);
			alert( 'Edit failed' );
		});
}

function replaceStubWikitext(data, keepOld){
	added_text = '\n\n\n{{'+ SScurrent.template +'}}';
	stubTemplates.forEach(function (value, index, array){
		var re = new RegExp(value, "gi");
		if (keepOld && data.match(re) !== null){
			added_text = added_text + '\n' + value;//readd stub template to bottom
		}
		data = data.replace(re, "");//replace stubs with nothing using unescaped regex
	});
	data = data.replace(/\s+$/g, "");//remove trailing whitepace, do not use m flag
	data = data + added_text;
	console.log(data);
	var summary = 'Replaced stub templates with {{'+ SScurrent.template +'}} using [[User:Danski454/stubsearch|a tool]]';
	if (keepOld){
		summary = 'Added {{'+ SScurrent.template +'}} using [[User:Danski454/stubsearch|a tool]]';
	}
	//save data
	$.ajax({
		url: mw.util.wikiScript( 'api' ),
		type: 'POST',
		dataType: 'json',
		data: {
			format: 'json',
			action: 'edit',
			title: mw.config.get( 'wgPageName' ),
			text: data, 
			summary: summary,
			token: mw.user.tokens.get( 'csrfToken' )
		}
	})
	.done (function( data ) {
		if ( data && data.edit && data.edit.result && data.edit.result == 'Success') {
			if (SSoldid === null){
				if (data.edit.oldrevid !== undefined){
					SSoldid = data.edit.oldrevid;
				} else {
					alert("Unable to determine oldid");
				}
			}
			if ($('#stub-search-load-diff').is(':checked') && data.edit.newrevid !== undefined){
				if (SSoldid !== null && data.edit.oldrevid !== undefined){
					window.location.href = mw.config.get('wgScript') + '?diff=' + data.edit.newrevid + '&oldid=' + SSoldid;//load diff
				} else {
					alert("Unable to load diff");
				}
			} else {
				if (keepOld){
					alert( 'Successfully added {{'+ SScurrent.template +'}} to the article' );
				} else {
					alert( 'Successfully replaced stubs with {{'+ SScurrent.template +'}}' );
				}
				if (data.edit.newrevid !== undefined){
					SSnewid = data.edit.newrevid;
				} else {
					alert("Unable to determine newid");
				}
				if (SSoldid !== null && SSnewid !== null && $('#stub-search-diff-button').is(':hidden')){
					$('#stub-search-diff-button').show();//add a load diff button if we have not loaded the diff
				}
				selectStub('stubs');//reset the UI, user probably wans to add another template
			}
		} else {
			console.log(3);
			alert( 'Edit failed' );
		}
	})
	.fail ( function() {
		console.log(4);
		alert( 'Edit failed' );
	});
}

$(document).ready(function(){
	if ( mw.config.get( 'wgNamespaceNumber' ) === 0 || mw.config.get( 'wgPageName' ) === "Wikipedia:Sandbox" ) {//usable on articles and the sandbox (for testing and practice)
		var portletLink = mw.util.addPortletLink('p-cactions', '#', 'Stub search', 'ca-stub-search', 'Tag this article as a stub', '');
		$(portletLink).click(loadStubSearch);
	}
});
// </nowiki>