// Fork of [[User:Writ Keeper/Scripts/autoCloser.js]]
//<nowiki>
mw.loader.using(["oojs-ui-core", "oojs-ui-windows", "oojs-ui-widgets"], function () {
function escapeRegexp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
function parseHTML(html) {
// Create a temporary div to parse the HTML
var tempDiv = $('<div>').html(html);
// Find all li elements
var liElements = tempDiv.find('li');
// Array to store extracted hrefs
var hrefs = [];
let existinghrefRegexp = /^https:\/\/en\.wikipedia.org\/wiki\/(Category:[^?&]+?)$/;
let nonexistinghrefRegexp = /^https:\/\/en\.wikipedia\.org\/w\/index\.php\?title=(Category:[^&?]+?)&action=edit&redlink=1$/;
// Iterate through each li element
liElements.each(function () {
// Find all anchor (a) elements within the current li
let hrefline = [];
var anchorElements = $(this).find('a');
// Extract href attribute from each anchor element
anchorElements.each(function () {
var href = $(this).attr('href');
if (href) {
var existingMatch = existinghrefRegexp.exec(href);
var nonexistingMatch = nonexistinghrefRegexp.exec(href);
if (existingMatch) {
hrefline.push(decodeURIComponent(existingMatch[1]).replaceAll('_', ' '));
}
if (nonexistingMatch) {
hrefline.push(decodeURIComponent(nonexistingMatch[1]).replaceAll('_', ' '));
}
}
});
hrefs.push(hrefline);
});
return hrefs;
}
function handlepaste(widget, e) {
var types, pastedData, parsedData;
// Browsers that support the 'text/html' type in the Clipboard API (Chrome, Firefox 22+)
if (e && e.clipboardData && e.clipboardData.types && e.clipboardData.getData) {
// Check for 'text/html' in types list
types = e.clipboardData.types;
if (((types instanceof DOMStringList) && types.contains("text/html")) ||
($.inArray && $.inArray('text/html', types) !== -1)) {
// Extract data and pass it to callback
pastedData = e.clipboardData.getData('text/html');
parsedData = parseHTML(pastedData);
// Check if it's an empty array
if (!parsedData || parsedData.length === 0) {
// Allow the paste event to propagate for plain text or empty array
return true;
}
let confirmed = confirm( 'You have pasted formatted text. Do you want this to be converted into wikitext?' );
if (!confirmed) return true;
processPaste(widget, pastedData);
// Stop the data from actually being pasted
e.stopPropagation();
e.preventDefault();
return false;
}
}
// Allow the paste event to propagate for plain text
return true;
}
function waitForPastedData(widget, savedContent) {
// If data has been processed by the browser, process it
if (widget.getValue() !== savedContent) {
// Retrieve pasted content via widget's getValue()
var pastedData = widget.getValue();
// Restore saved content
widget.setValue(savedContent);
// Call callback
processPaste(widget, pastedData);
}
// Else wait 20ms and try again
else {
setTimeout(function () {
waitForPastedData(widget, savedContent);
}, 20);
}
}
function processPaste(widget, pastedData) {
// Parse the HTML
var parsedArray = parseHTML(pastedData);
let stringOutput = '';
for (const cats of parsedArray) {
if (cats.length === 1) stringOutput += `* [[:${cats[0]}]]\n`;
if (cats.length === 2) stringOutput += `* [[:${cats[0]}]] to [[:${cats[1]}]]\n`;
if (cats.length === 3) stringOutput += `* [[:${cats[0]}]] to [[:${cats[1]}]] and [[:${cats[2]}]]\n`;
if (cats.length > 3) {
let firstCat = cats.pop(0);
let lastCat = cats.pop(0);
stringOutput += `* [[:${firstCat}}]] to [[:${cats.join(']], [[:')}]] and [[:${lastCat}]]\n`;
}
}
widget.insertContent(stringOutput);
}
// Code from https://doc.wikimedia.org/oojs-ui/master/js/#!/api/OO.ui.Dialog
// Add interface shell
function CfdDialog( config ) {
CfdDialog.super.call( this, config );
this.InputText = config.InputText;
this.sectionIndex = config.sectionIndex || null;
CfdDialog.static.title = config.dialogTitle;
CfdDialog.static.actions = config.dialogActions;
}
OO.inheritClass( CfdDialog, OO.ui.ProcessDialog );
CfdDialog.static.name = 'CfdDialog';
CfdDialog.prototype.initialize = function () {
CfdDialog.super.prototype.initialize.call( this );
this.content = new OO.ui.PanelLayout( { padded: false, expanded: false } );
this.content.$element.append( '<p style="padding-left: 5px">Make any changes necessary:</p>' );
CfdDialog.prototype.cfdlisterTextBox = new OO.ui.MultilineTextInputWidget( {
autosize: true,
value: this.InputText,
id: "CFD-lister-text",
rows: Math.min((this.InputText.match(/\n/g)||[]).length+2, 10),
maxrows: 25
} );
let textInputElement = CfdDialog.prototype.cfdlisterTextBox.$element.get(0);
let handler = handlepaste.bind(this, CfdDialog.prototype.cfdlisterTextBox);
// Modern browsers. Note: 3rd argument is required for Firefox <= 6
if (textInputElement.addEventListener) {
textInputElement.addEventListener('paste', handler, false);
}
// IE <= 8
else {
textInputElement.attachEvent('onpaste', handler);
}
mw.loader.using('ext.wikiEditor', function() {
mw.addWikiEditor(CfdDialog.prototype.cfdlisterTextBox.$input);
});
CfdDialog.prototype.cfdlisterTextBox.$input.on('input', () => this.updateSize() );
this.content.$element.append(CfdDialog.prototype.cfdlisterTextBox.$element);
this.$body.append( this.content.$element );
};
CfdDialog.prototype.getActionProcess = function ( action ) {
var dialog = this;
if ( action ) {
return new OO.ui.Process( function () {
dialog.close( { text: '\n\n'+CfdDialog.prototype.cfdlisterTextBox.value, sectionIndex: this.sectionIndex } );
} );
}
return CfdDialog.super.prototype.getActionProcess.call( this, action );
};
function editDiscussions() {
var text = localStorage.getItem('CFDNAClist');
if (!text) {
mw.notify('Error, no discussions listed yet.', {type:'error'});
return;
}
var windowManager = new OO.ui.WindowManager();
var cfdDialog = new CfdDialog(
{
InputText: text.trim(), // newlines will be stripped here and added back implicitly in the closing call
dialogTitle: 'Edit listed discussions',
dialogActions: [
{ action: 'add', label: 'Save', flags: ['primary', 'progressive'] },
{ label: 'Cancel', flags: ['destructive', 'safe'] }
]
});
windowManager.defaultSize = 'full';
$( document.body ).append( windowManager.$element );
windowManager.addWindows( [ cfdDialog ] );
windowManager.openWindow( cfdDialog );
windowManager.on('closing', (win, closing, data) => {
if (!data) return;
if (!data.text.trim()) {
OO.ui.confirm('Are you sure you want to delete all listed discussions?').done((response) => {
if (response) {
localStorage.setItem('CFDNAClist', '');
localStorage.setItem('CFDNAClist-count', '');
} else mw.notify('Aborted changes to listed discussions.');
});
} else {
localStorage.setItem('CFDNAClist', data.text);
mw.notify('Listed discussions updated.');
}
});
}
var editDiscussionslink = mw.util.addPortletLink( 'p-cactions', '#','Edit discussions', 'pt-cfdnaceditlist', 'Edit listed discussions', null, listDiscussionslink);
$( editDiscussionslink ).click( function ( event ) {
event.preventDefault();
editDiscussions();
});
function quickClose(option, editLink, headerElement) {
if (typeof editLink !== "undefined") {
var regexResults = /title=([^&]+).*§ion=[\D]*(\d+)/.exec(editLink.href);
if(regexResults === null)
{
return false;
}
var pageTitle = regexResults[1].replaceAll('_', ' '); // prettify
var sectionIndex = regexResults[2];
const params = {
action: "parse",
format: "json",
page: pageTitle,
prop: "wikitext|sections",
section: sectionIndex
};
const api = new mw.Api();
api.get(params).done(data => {
sectionTitle = data.parse.sections[0].line.replaceAll('_', ' ');
wikitext = data.parse.wikitext["*"];
const closedRegexp = /<div class="boilerplate cfd vfd xfd-closed mw-archivedtalk"/i;
if ( closedRegexp.test(wikitext) ) { // already closed
mw.notify('Discussion already closed, aborted closure.', {type:'error'});
return;
}
const newWikitext = `\n${wikitext.replace(/====.+====/, `$&\n{{subst:cfd top|'''${option}'''}}${mw.config.get('wgUserGroups').includes('sysop') ? '' : ' {{subst:nac}}'} ~~~~\n`)}\n{{subst:cfd bottom}}`;
var requestData =
{
action: "edit",
title: pageTitle,
format: "json",
section: sectionIndex,
text: newWikitext,
summary: `/* ${sectionTitle} */ Quick close as ${option} via [[User:Qwerfjkl/scripts/CFDlister.js|script]]`,
notminor: 1,
nocreate: 1,
token: mw.user.tokens.get( 'csrfToken' )
};
$.ajax({
url: mw.util.wikiScript( 'api' ),
type: 'POST',
dataType: 'json',
data: requestData
})
.then (function( data ) {
if ( data && data.edit && data.edit.result && data.edit.result == 'Success' ) {
mw.notify( `Discussion closed as ${option}.` );
// Now use wikitext from before, don't bother refetching
let result = option;
const categoryRegex = /====.+====\s+([\s\S]+)\s+[:\*]* *'''(?:Nominator'?s )?rationale?/i;
if ( categoryRegex.test(wikitext) ) { // correctly formatted
wikitext = wikitext.match(categoryRegex)[1];
} else {
alert("This nomination is missing a '''Nominator's rationale''': and so the script cannot recognise the categories nominated. Please manually fix this by adding '''Nominator's rationale''': just before the nominator's rationale.");
return;
}
// Cleanup
wikitext = wikitext.replace(/\{\{(?:lc|cl|cls|lcs|clc)\|(.+?)\}\}/gi, '[[:Category:$1]]'); // fix category templates
wikitext = wikitext.replaceAll(':Category:Category:', ':Category:'); // fix double category - can be caused by above
wikitext = wikitext.replace(/'''(propose|delet|renam|split|(?:up)?merg|container).*?'''/gi, '');
wikitext = wikitext.replace(/^ *[:#\*]* */gm, '* '); // fix indentation
wikitext = wikitext.replace(/^\s*[\*\: ]*\s*\n/gm, ''); // remove lines with just *, : and whitespace
wikitext = wikitext.replace(/\n?\s*\*\s*$/, ''); // remove trailing asterisk
wikitext = wikitext.replace(/<br ?\/?>/g, ''); // remove br tags
wikitext = `* [[${pageTitle}#${sectionTitle}]]\nResult: '''${result}'''\n<syntaxhighlight lang="wikitext">\n; [[${pageTitle}]]\n${wikitext}\n</syntaxhighlight>`;
var incorrectOptionRegexp;
switch (option) {
case 'rename':
case 'merge':
incorrectOptionRegexp = /\* *\[\[:Category:[^\]\n]+?\]\]$/gim;
if ( incorrectOptionRegexp.test(wikitext) ) {
mw.notify('Error parsing nomination. Please click "list discussion" to list it manually.', {type:'error'});
return;
}
break;
case 'delete':
incorrectOptionRegexp = /\* *\[\[:Category:[^\]\n]+?\]\].+\[\[:Category:/gim;
if ( incorrectOptionRegexp.test(wikitext) ) {
mw.notify('Error parsing nomination. Please click "list discussion" to list it manually.', {type:'error'});
return;
}
break;
default: // shouldn't happen unless the user has modified their html
mw.notify(`Error handling closure: ${option}. Please click "list discussion" to list it manually.`, {type:'error'});
return;
}
if ( wikitext.includes('<s>') || wikitext.includes('{{') || wikitext.includes('<!--')) {
// something probably needs manual review
mw.notify('Error parsing nomination. Please click "list discussion" to list it manually.', {type:'error'});
return;
}
addCFD(null, null, {text: '\n\n'+wikitext, sectionIndex: sectionIndex});
} else {
console.error( 'The edit query returned an error. =(\n\n' + JSON.stringify(data) );
mw.notify('Error closing discussion, see console for details', {type:'error'});
}
})
.catch ( function(e) {
console.error( 'The ajax request failed.\n\n' + JSON.stringify(e) );
});
});
}
}
function listDiscussion() {
editLink = $(this).siblings("a.sectionEditLink")[0];
if (typeof editLink !== "undefined") {
var regexResults = /title=([^&]+).*§ion=[\D]*(\d+)/.exec(editLink.href);
if(regexResults === null)
{
return false;
}
var pageTitle = regexResults[1].replaceAll('_', ' ');
var sectionIndex = regexResults[2];
const params = {
action: "parse",
format: "json",
page: pageTitle,
prop: "wikitext|sections",
section: sectionIndex
};
const api = new mw.Api();
api.get(params).done(data => {
sectionTitle = data.parse.sections[0].line.replaceAll('_', ' ');
wikitext = data.parse.wikitext["*"];
resultRegexp = /<div class="boilerplate cfd vfd xfd-closed(?: mw-archivedtalk)?" style="background:#bff9fc; margin:0 auto; padding:0 10px 0 10px; border:1px solid #AAAAAA;">\n:''The following is an archived discussion concerning one or more categories\. <span style="color:red"\>'''Please do not modify it\.'''<\/span> Subsequent comments should be made on an appropriate discussion page \(such as the category's \[\[Help:Using talk pages\|talk page\]\] or in a \[\[Wikipedia:Deletion review\|deletion review\]\]\)\. No further edits should be made to this section\.''\n\n:''The result of the discussion was:'' ?(?:<!-- ?Template:Cfd top ?-->)? ?'''(.+?)'''/i;
if ( resultRegexp.test(wikitext) ) { // match
result = wikitext.match(resultRegexp)[1];
} else {
result = 'RESULT';
}
wikitext = wikitext.replace(new RegExp(resultRegexp.source + '.+', 'i'), ''); // remove closure text, unneeded
const categoryRegex = /====.+====\s+([\s\S]+)\s+[:\*]* *'''(?:Nominator'?s )?rationale?/i;
if ( categoryRegex.test(wikitext) ) { // correctly formatted
wikitext = wikitext.match(categoryRegex)[1];
} else {
alert("This nomination is missing a '''Nominator's rationale''': and so the script cannot recognise the categories nominated. Please manually fix this by adding '''Nominator's rationale''': just before the nominator's rationale.");
return;
}
// Cleanup
wikitext = wikitext.replace(/\{\{(?:lc|cl|cls|lcs|clc)\|(.+?)\}\}/gi, '[[:Category:$1]]'); // fix category templates
wikitext = wikitext.replaceAll(':Category:Category:', ':Category:'); // fix double category - can be caused by above
wikitext = wikitext.replace(/'''(propose|delet|renam|split|(?:up)?merg|container).*?'''/gi, '');
wikitext = wikitext.replace(/^ *[:#\*]* */gm, '* '); // fix indentation
wikitext = wikitext.replace(/^\s*[\*\: ]*\s*\n/gm, ''); // remove lines with just *, : and whitespace
wikitext = wikitext.replace(/\n?\s*\*\s*$/, ''); // remove trailing asterisk
wikitext = wikitext.replace(/<br ?\/?>/g, ''); // remove br tags
wikitext = "* [["+pageTitle+"#"+sectionTitle+"]]\nResult: '''"+result+"'''\n<syntaxhighlight lang=\"wikitext\">\n; [["+pageTitle+"]]\n"+wikitext+"\n</syntaxhighlight>";
var windowManager = new OO.ui.WindowManager();
var cfdDialog = new CfdDialog(
{
InputText: wikitext,
sectionIndex: sectionIndex,
dialogTitle: 'List discussion for processing',
dialogActions: [
{ action: 'add', label: 'Add', flags: ['primary', 'progressive'] },
{ label: 'Cancel', flags: ['destructive', 'safe'] }
]
});
windowManager.defaultSize = 'full';
$( document.body ).append( windowManager.$element );
windowManager.addWindows( [ cfdDialog ] );
windowManager.openWindow( cfdDialog );
windowManager.on('closing', addCFD);
});
}
}
function addCFD(win, closing, data) {
if ( data == null || data === undefined || data.text == '' || !data.text ) {
return;
}
var wikitext = data.text;
var text = localStorage.getItem('CFDNAClist');
var count = localStorage.getItem('CFDNAClist-count') || 0;
if (text == '' || text == null) {
localStorage.setItem('CFDNAClist', wikitext);
}
else {
localStorage.setItem('CFDNAClist', text+wikitext);
}
localStorage.setItem('CFDNAClist-count', Number(count)+1);
mw.notify('Added discussion');
// double strike through handled sections
if (data.sectionIndex) {
// apply styles to show discussion has been closed (like XFDCloser)
$(`h4:nth-of-type(${data.sectionIndex-1})`).find('.mw-headline').css({'text-decoration': 'line-through', 'text-decoration-style': 'double'});
var startH4 = document.querySelector(`h4:nth-of-type(${data.sectionIndex-1})`);
if (startH4) {
var elementsBetweenH4 = [];
var currentElement = startH4.nextElementSibling;
while (currentElement) {
if (currentElement.tagName.toLowerCase() === 'h4') {
break;
}
elementsBetweenH4.push(currentElement);
currentElement = currentElement.nextElementSibling;
}
elementsBetweenH4.forEach(function(element) {
$(element).css('opacity', '50%');
});
}
}
}
function discussionListerSetup() {
function createDropdownLink(text, clickHandler) {
var link = document.createElement("a");
link.href = "#";
link.innerHTML = text;
link.onclick = function (event) {
event.preventDefault();
clickHandler();
};
return link;
}
var sectionHeaders = $("h4 .mw-editsection"); // where parent is h4
$('.mw-heading, h1, h2, h3, h4, h5, h6').css('overflow', 'visible'); // allow for dropdown, no idea what damage it could do
sectionHeaders.each(function (index, element) {
var editLink = $(element).children("a")[0];
if (typeof editLink !== "undefined" && /§ion=[\D]*(\d+)/.exec(editLink.href)) {
$(editLink).addClass("sectionEditLink");
var discussionLister = $("<a>List discussion</a>");
discussionLister.click(listDiscussion);
// Create dropdown elements
var dropdownContainer = $(`<span class='dropdown-container' style="position:relative; display:inline-block; "></span>`);
var dropdownTrigger = $(`<a class='dropdown-trigger' style="color: #0645AD; text-decoration: none; cursor: pointer">One click close</a>`);
var dropdownMenu = $(`<span class='dropdown-menu' style="display: none; position: absolute; background-color: #fff; border: 1px solid #ddd; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); padding: 5px; min-width: 6em; z-index: 1; left: 0px; top: 0.8em;" ></span>`);
var actions = ['delete', 'rename', 'merge']; // needs to correspond with switch statement in quickClose()
for (var i = 0; i < actions.length; i++) {
const action = actions[i];
var menuItem = $(`<a style="display: block; color: #0645AD; text-decoration: none; padding: 10px; margin-top: 5px; font-size: 150%" class='dropdown-item'>${action}</a>`);
menuItem.click(function () {
quickClose(action, editLink, element);
dropdownMenu.hide();
});
// make red on hover
menuItem.on( "mouseenter", function () {$(this).css('color', 'red')} ).on( "mouseleave", function () {$(this).css('color', 'color: #0645AD')} )
dropdownMenu.append(menuItem);
}
dropdownTrigger.click(function () {
dropdownMenu.toggle();
});
// Append elements to the existing element
dropdownContainer.append(dropdownTrigger, dropdownMenu);
// Close the dropdown if the user clicks outside of it
$(document).click(function (event) {
if (!$(event.target).closest('.dropdown-container').length) {
dropdownMenu.hide();
}
});
let bracket = $(element).find('.mw-editsection-bracket:last-child');
$(bracket).before(' | ', discussionLister, " | ", dropdownContainer);
}
});
}
if (mw.config.get("wgPageName").match('Wikipedia:Categories_for_discussion')) $(document).ready(discussionListerSetup);
});
function listPages() {
var text = localStorage.getItem('CFDNAClist');
var count = localStorage.getItem('CFDNAClist-count');
if (text == '' || text == null) {
mw.notify('No discussions to list, aborting');
return;
}
text = "\n\nPlease can an admin add the following:"+text+"\n~~~~";
var requestData =
{
action: "edit",
title: "Wikipedia talk:Categories for discussion/Working",
format: "json",
//section: "new",
//sectiontitle: "NAC request (~~~~~)",
appendtext: text,
summary: "Add NAC request ("+count+" dicussions listed) via [[User:Qwerfjkl/scripts/CFDlister.js|script]]",
notminor: 1,
nocreate: 1,
redirect: 1,
token: mw.user.tokens.get( 'csrfToken' )
};
mw.notify('Editing WT:CFDW...', {tag:'CFDListerEdit'});
$.ajax({
url: mw.util.wikiScript( 'api' ),
type: 'POST',
dataType: 'json',
data: requestData
})
.then (function( data ) {
if ( data && data.edit && data.edit.result && data.edit.result == 'Success' ) {
mw.notify( 'Discussions listed.', {tag:'CFDListerEdit'});
localStorage.setItem('CFDNAClist', '');
localStorage.setItem('CFDNAClist-count', 0);
window.location.href = "https://en.wikipedia.org/wiki/Wikipedia_talk:Categories_for_discussion/Working#footer"; // redirect
} else {
alert( 'The edit query returned an error. =(' );
}
})
.catch ( function() {
alert( 'The ajax request failed.' );
});
}
var listDiscussionslink = mw.util.addPortletLink( 'p-cactions', '#','List discussions', 'pt-cfdnaclist', 'List closed discussions for admins to handle');
$( listDiscussionslink ).click( function ( event ) {
event.preventDefault();
listPages();
});
//</nowiki>