Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add insertAfterTemplates to Morebits.wikitext.page, respect MOS:ORDER when tagging, update hatnotes #1022

Merged
merged 4 commits into from Jul 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
50 changes: 16 additions & 34 deletions modules/friendlytag.js
Expand Up @@ -1516,40 +1516,22 @@ Twinkle.tag.callbacks = {
var addUngroupedTags = function() {
$.each(tags, addTag);

// Smartly insert the new tags after any hatnotes or
// afd, csd, or prod templates or hatnotes. Regex is
// extra complicated to allow for templates with
// parameters and to handle whitespace properly.
pageText = pageText.replace(
new RegExp(
// leading whitespace
'^\\s*' +
// capture template(s)
'(?:((?:\\s*' +
// AfD is special, as the tag includes html comments before and after the actual template
'(?:<!--.*AfD.*\\n\\{\\{(?:Article for deletion\\/dated|AfDM).*\\}\\}\\n<!--.*(?:\\n<!--.*)?AfD.*(?:\\s*\\n))?|' + // trailing whitespace/newline needed since this subst's a newline
// begin template format
'\\{\\{\\s*(?:' +
// CSD
'db|delete|db-.*?|speedy deletion-.*?|' +
// PROD
'(?:proposed deletion|prod blp)\\/dated(?:\\s*\\|(?:concern|user|timestamp|help).*)+|' +
// various hatnote templates
'about|correct title|dablink|distinguish|for|other\\s?(?:hurricane(?: use)?s|people|persons|places|uses(?:of)?)|redirect(?:-acronym)?|see\\s?(?:also|wiktionary)|selfref|short description|the' +
// not a hatnote, but sometimes under a CSD or AfD
'|salt|proposed deletion endorsed' +
// end main template name, optionally with a number (such as redirect2)
')\\d*\\s*' +
// template parameters
'(\\|(?:\\{\\{[^{}]*\\}\\}|[^{}])*)?' +
// end template format
'\\}\\})+' +
// end capture
'(?:\\s*\\n)?)' +
// trailing whitespace
'\\s*)?',
'i'), '$1' + tagText
);
// Insert tag after short description or any hatnotes,
// as well as deletion/protection-related templates
var wikipage = new Morebits.wikitext.page(pageText);
var templatesAfter = Twinkle.hatnoteRegex +
// Protection templates
'pp|pp-.*?|' +
// CSD
'db|delete|db-.*?|speedy deletion-.*?|' +
// PROD
'(?:proposed deletion|prod blp)\\/dated(?:\\s*\\|(?:concern|user|timestamp|help).*)+|' +
// not a hatnote, but sometimes under a CSD or AfD
'salt|proposed deletion endorsed';
// AfD is special, as the tag includes html comments before and after the actual template
// trailing whitespace/newline needed since this subst's a newline
var afdRegex = '(?:<!--.*AfD.*\\n\\{\\{(?:Article for deletion\\/dated|AfDM).*\\}\\}\\n<!--.*(?:\\n<!--.*)?AfD.*(?:\\s*\\n))?';
pageText = wikipage.insertAfterTemplates(tagText, templatesAfter, null, afdRegex).getText();

removeTags();
};
Expand Down
6 changes: 2 additions & 4 deletions modules/twinklebatchdelete.js
Expand Up @@ -648,9 +648,8 @@ Twinkle.batchdelete.callbacks = {
}
var old_text = text;
var wikiPage = new Morebits.wikitext.page(text);
wikiPage.removeLink(params.page);
text = wikiPage.removeLink(params.page).getText();

text = wikiPage.getText();
Twinkle.batchdelete.unlinkCache[params.title] = text;
if (text === old_text) {
// Nothing to do, return
Expand Down Expand Up @@ -701,9 +700,8 @@ Twinkle.batchdelete.callbacks = {
}
var old_text = text;
var wikiPage = new Morebits.wikitext.page(text);
wikiPage.commentOutImage(image, 'Commented out because image was deleted');
text = wikiPage.commentOutImage(image, 'Commented out because image was deleted').getText();

text = wikiPage.getText();
Twinkle.batchdelete.unlinkCache[params.title] = text;
if (text === old_text) {
pageobj.getStatusElement().error('failed to unlink image ' + image + ' from ' + pageobj.getPageName());
Expand Down
11 changes: 8 additions & 3 deletions modules/twinkleprod.js
Expand Up @@ -308,17 +308,22 @@ Twinkle.prod.callbacks = {
} else if (Twinkle.getPref('logProdPages')) { // If not notifying, log this PROD
Twinkle.prod.callbacks.addToLog(params);
}
var tag;
if (params.blp) {
summaryText = 'Proposing article for deletion per [[WP:BLPPROD]].';
text = '{{subst:prod blp' + (params.usertalk ? '|help=off' : '') + '}}\n' + text;
tag = '{{subst:prod blp' + (params.usertalk ? '|help=off' : '') + '}}';
} else if (params.book) {
summaryText = 'Proposing book for deletion per [[WP:BOOKPROD]].';
text = '{{subst:book-prod|1=' + Morebits.string.formatReasonText(params.reason) + (params.usertalk ? '|help=off' : '') + '}}\n' + text;
tag = '{{subst:book-prod|1=' + Morebits.string.formatReasonText(params.reason) + (params.usertalk ? '|help=off' : '') + '}}';
} else {
summaryText = 'Proposing ' + namespace + ' for deletion per [[WP:PROD]].';
text = '{{subst:prod|1=' + Morebits.string.formatReasonText(params.reason) + (params.usertalk ? '|help=off' : '') + '}}\n' + text;
tag = '{{subst:prod|1=' + Morebits.string.formatReasonText(params.reason) + (params.usertalk ? '|help=off' : '') + '}}';
}

// Insert tag after short description or any hatnotes
var wikipage = new Morebits.wikitext.page(text);
text = wikipage.insertAfterTemplates(tag + '\n', Twinkle.hatnoteRegex).getText();

// Add {{Old prod}} to the talk page
if (!params.oldProdPresent) {
var oldprodfull = '{{Old prod|nom=' + mw.config.get('wgUserName') + '|nomdate={{subst:#time: Y-m-d}}}}\n';
Expand Down
12 changes: 9 additions & 3 deletions modules/twinkleprotect.js
Expand Up @@ -1438,10 +1438,16 @@ Twinkle.protect.callbacks = {
Morebits.status.info('Redirect category shell present', 'nothing to do');
return;
}
} else if (params.noinclude) {
text = '<noinclude>{{' + tag + '}}</noinclude>' + text;
} else {
text = '{{' + tag + '}}\n' + text;
if (params.noinclude) {
tag = '<noinclude>{{' + tag + '}}</noinclude>';
} else {
tag = '{{' + tag + '}}\n';
}

// Insert tag after short description or any hatnotes
var wikipage = new Morebits.wikitext.page(text);
text = wikipage.insertAfterTemplates(tag, Twinkle.hatnoteRegex).getText();
}
summary = 'Adding {{' + params.tag + '}}' + Twinkle.getPref('summaryAd');
}
Expand Down
13 changes: 12 additions & 1 deletion modules/twinklespeedy.js
Expand Up @@ -1545,7 +1545,18 @@ Twinkle.speedy.callbacks = {
code = code.replace('$TIMESTAMP', pageobj.getLastEditTime());
}

pageobj.setPageText(code + (params.normalizeds.indexOf('g10') !== -1 ? '' : '\n' + text)); // cause attack pages to be blanked

// Blank attack pages
if (params.normalizeds.indexOf('g10') !== -1) {
text = code;
} else {
// Insert tag after short description or any hatnotes
var wikipage = new Morebits.wikitext.page(text);
text = wikipage.insertAfterTemplates(code + '\n', Twinkle.hatnoteRegex).getText();
}


pageobj.setPageText(text);
pageobj.setEditSummary(editsummary + Twinkle.getPref('summaryAd'));
pageobj.setWatchlist(params.watch);
if (params.scribunto) {
Expand Down
6 changes: 2 additions & 4 deletions modules/twinkleunlink.js
Expand Up @@ -255,8 +255,7 @@ Twinkle.unlink.callbacks = {

// remove image usages
if (params.doImageusage) {
wikiPage.commentOutImage(mw.config.get('wgTitle'), 'Commented out');
text = wikiPage.getText();
text = wikiPage.commentOutImage(mw.config.get('wgTitle'), 'Commented out').getText();
// did we actually make any changes?
if (text === oldtext) {
warningString = 'file usages';
Expand All @@ -268,8 +267,7 @@ Twinkle.unlink.callbacks = {

// remove backlinks
if (params.doBacklinks) {
wikiPage.removeLink(Morebits.pageNameNorm);
text = wikiPage.getText();
text = wikiPage.removeLink(Morebits.pageNameNorm).getText();
// did we actually make any changes?
if (text === oldtext) {
warningString = warningString ? 'backlinks or file usages' : 'backlinks';
Expand Down
10 changes: 8 additions & 2 deletions modules/twinklexfd.js
Expand Up @@ -925,8 +925,14 @@ Twinkle.xfd.callbacks = {
text = textNoSd;
}

pageobj.setPageText((params.noinclude ? '<noinclude>{{' : '{{') + (params.number === '' ? 'subst:afd|help=off' : 'subst:afdx|' +
params.number + '|help=off') + (params.noinclude ? '}}</noinclude>\n' : '}}\n') + text);
var tag = (params.noinclude ? '<noinclude>{{' : '{{') + (params.number === '' ? 'subst:afd|help=off' : 'subst:afdx|' +
params.number + '|help=off') + (params.noinclude ? '}}</noinclude>\n' : '}}\n');

// Insert tag after short description or any hatnotes
var wikipage = new Morebits.wikitext.page(text);
text = wikipage.insertAfterTemplates(tag, Twinkle.hatnoteRegex).getText();

pageobj.setPageText(text);
pageobj.setEditSummary('Nominated for deletion; see [[:' + params.discussionpage + ']].' + Twinkle.getPref('summaryAd'));
Twinkle.xfd.setWatchPref(pageobj, Twinkle.getPref('xfdWatchPage'));
pageobj.setCreateOption('nocreate');
Expand Down
78 changes: 78 additions & 0 deletions morebits.js
Expand Up @@ -3894,6 +3894,8 @@ Morebits.wikitext.page.prototype = {
/**
* Removes links to `link_target` from the page text.
* @param {string} link_target
*
* @returns {Morebits.wikitext.page}
*/
removeLink: function(link_target) {
var first_char = link_target.substr(0, 1);
Expand All @@ -3907,13 +3909,16 @@ Morebits.wikitext.page.prototype = {
var link_simple_re = new RegExp('\\[\\[' + colon + '(' + link_re_string + ')\\]\\]', 'g');
var link_named_re = new RegExp('\\[\\[' + colon + link_re_string + '\\|(.+?)\\]\\]', 'g');
this.text = this.text.replace(link_simple_re, '$1').replace(link_named_re, '$1');
return this;
},

/**
* Comments out images from page text. If used in a gallery, deletes the whole line.
* If used as a template argument (not necessarily with File: prefix), the template parameter is commented out.
* @param {string} image - Image name without File: prefix
* @param {string} reason - Reason to be included in comment, alongside the commented-out image
*
* @returns {Morebits.wikitext.page}
*/
commentOutImage: function(image, reason) {
var unbinder = new Morebits.unbinder(this.text);
Expand Down Expand Up @@ -3951,12 +3956,15 @@ Morebits.wikitext.page.prototype = {
unbinder.content = unbinder.content.replace(free_image_re, '<!-- ' + reason + '$1 -->');
// Rebind the content now, we are done!
this.text = unbinder.rebind();
return this;
},

/**
* Converts first usage of [[File:`image`]] to [[File:`image`|`data`]]
* @param {string} image - Image name without File: prefix
* @param {string} data
*
* @returns {Morebits.wikitext.page}
*/
addToImageComment: function(image, data) {
var first_char = image.substr(0, 1);
Expand All @@ -3978,12 +3986,15 @@ Morebits.wikitext.page.prototype = {
var gallery_re = new RegExp('^(\\s*' + image_re_string + '.*?)\\|?(.*?)$', 'mg');
var newtext = '$1|$2 ' + data;
this.text = this.text.replace(gallery_re, newtext);
return this;
},

/**
* Removes transclusions of template from page text
* @param {string} template - Page name whose transclusions are to be removed,
* include namespace prefix only if not in template namespace
*
* @returns {Morebits.wikitext.page}
*/
removeTemplate: function(template) {
var first_char = template.substr(0, 1);
Expand All @@ -3995,6 +4006,73 @@ Morebits.wikitext.page.prototype = {
this.text = this.text.replace(allTemplates[i], '', 'g');
}
}
return this;
},

/**
* Smartly insert a tag atop page text but after specified templates,
* such as hatnotes, short description, or deletion and protection templates.
* Notably, does *not* insert a newline after the tag
*
* @param {string} tag - The tag to be inserted
* @param {string|string[]} regex - Templates after which to insert tag,
* given as either as a (regex-valid) string or an array to be joined by pipes
* @param {string} [flags=i] - Regex flags to apply. Optional, defaults to /i
* @param {string|string[]} preRegex - Optional regex string or array to match
* before any template matches (i.e. before `{{`), such as html comments
*
* @returns {Morebits.wikitext.page}
*/
insertAfterTemplates: function(tag, regex, flags, preRegex) {
if (!tag) {
throw new Error('No tag provided');
}

// .length is only a property of strings and arrays so we
// shouldn't need to check type
if (!regex || !regex.length) {
throw new Error('No regex provided');
} else if (Array.isArray(regex)) {
regex = regex.join('|');
}

flags = flags || 'i';

if (!preRegex || !preRegex.length) {
preRegex = '';
} else if (Array.isArray(preRegex)) {
preRegex = preRegex.join('|');
}


// Regex is extra complicated to allow for templates with
// parameters and to handle whitespace properly
this.text = this.text.replace(
new RegExp(
// leading whitespace
'^\\s*' +
// capture template(s)
'(?:((?:\\s*' +
// Pre-template regex, such as leading html comments
preRegex + '|' +
// begin template format
'\\{\\{\\s*(?:' +
// Template regex
regex +
// end main template name, optionally with a number
// Probably remove the (?:) though
')\\d*\\s*' +
// template parameters
'(\\|(?:\\{\\{[^{}]*\\}\\}|[^{}])*)?' +
// end template format
'\\}\\})+' +
// end capture
'(?:\\s*\\n)?)' +
// trailing whitespace
'\\s*)?',
flags), '$1' + tag
);
return this;
},

/** @returns {string} */
Expand Down
10 changes: 9 additions & 1 deletion twinkle.js
Expand Up @@ -27,8 +27,16 @@ if (!Morebits.userIsInGroup('autoconfirmed') && !Morebits.userIsInGroup('confirm
var Twinkle = {};
window.Twinkle = Twinkle; // allow global access

Twinkle.initCallbacks = [];
/**
* Twinkle-specific data shared by multiple modules
* Likely customized per installation
*/
// Various hatnote templates, used when tagging (csd/xfd/tag/prod/protect) to
// ensure MOS:ORDER
Twinkle.hatnoteRegex = 'short description|hatnote|main|correct title|dablink|distinguish|for|further|selfref|year dab|similar names|highway detail hatnote|broader|about(?:-distinguish| other people)?|other\\s?(?:hurricane(?: use)?s|people|persons|places|ships|uses(?: of)?)|redirect(?:-(?:distinguish|synonym|multi))?|see\\s?(?:wiktionary|also(?: if exists)?)';


Twinkle.initCallbacks = [];
/**
* Adds a callback to execute when Twinkle has loaded.
* @param {function} func
Expand Down