/*
General fixer for simple lint errors
Currently attempts to fix:
- Font tags (converts all parameters to their style= versions)
- Strike tags (replaces them with <s>) and tt tags (replaces them with <kbd>)
- Center tags (replaces with the center template for text and style="margin:1em auto" for wikitables)
Nothing else beyond some obselete tags is fixed as of now
A "Fix Lint" button will be added to the More tab - use this to apply fixes
For a variety of reasons, all edits should be supervised and checked after the script has ran
I don't often use JS so expect a wild number of bad practices
Inspired by, and built with help from, [[User:ಮಲ್ನಾಡಾಚ್ ಕೊಂಕ್ಣೊ/center.js]]
*/
// <nowiki>
var ISDEV = (getURIArg('fixlintdev') == 'true');
var fontColours = ['aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgrey', 'darkgreen', 'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray', 'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dimgrey', 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'grey', 'green', 'greenyellow', 'honeydew', 'hotpink', 'indianred', 'indigo', 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgray', 'lightgrey', 'lightgreen', 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategray', 'lightslategrey', 'lightsteelblue', 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'rebeccapurple', 'red', 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue', 'slategray', 'slategrey', 'snow', 'springgreen', 'steelblue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen']; //Compiled from https://www.w3schools.com/colors/colors_names.asp
function getURIArg(arg) {
var re = RegExp('[&?]' + arg + '=([^&]*)');
var matches = re.exec(document.location);
if (matches) {
try {
return decodeURI(matches[1]);
} catch (e) { }
}
return null;
}
function ProcessTag(tag) { //Turns a tag into a more usable table, hopefully
var tagData = {};
var tagArgs = {};
tagData.args = tagArgs;
var fspace = tag.search(" ");
if (fspace == -1)
tagData.name = tag.substring(1,tag.length-1);
else
tagData.name = tag.substring(1,fspace);
var tempData = tag;
while (true) {
var argName;
var argData;
var tagArg = RegExp("(\\w+) ?= ?(?:'([^']*)'|\"([^\"]*)\")").exec(tempData);
if (!tagArg) {
tagArg = RegExp("(\\w+) ?= ?([^ >]+)").exec(tempData);
if (!tagArg) {
break;
} else {
argName = tagArg[1].toLowerCase();
argData = tagArg[2];
}
} else {
argName = tagArg[1].toLowerCase();
argData = tagArg[2] || tagArg[3];
}
if (ISDEV)
console.log("tagArg",tempData,tagArg);
tagArgs[argName] = argData;
tempData = tempData.replace(tagArg[0],"");
}
return tagData;
}
function StringifyTag(tagData) { //The inverse of the above
var final = "<" + tagData.name;
for (var argName in tagData.args) {
var argData = tagData.args[argName];
if (argData != null) {
final = final + " " + argName + "=\"" + argData + "\"";
}
}
return final + ">";
}
//Add a tab to activate
if(mw.config.get('wgArticleId') != 0 ) {
section = getURIArg("section");
mw.util.addPortletLink(
'p-cactions',
mw.util.getUrl(null,{action:'edit',section:section,fixlint:true}),
'Fix Lint',
'ca-fixlint',
'Fix basic Lint Errors'
);
}
function notify(title,message,type,autohide,autohidetime) {
if (mw.notification) {
console.log("notify",title,message);
autohide = (autohide==null && true) || autohide;
autohidetime = (autohidetime==null && "short") || autohidetime;
if (!mw.notification.autoHideSeconds[autohidetime])
mw.notification.autoHideSeconds[autohidetime] = autohidetime; //Enable custom arbitrary lengths
mw.notification.notify(message,{autoHide:autohide,autoHideSeconds:autohidetime,title:title,type:type});
}
}
function modifyPageContent(notifyOfEvents) {
function notifyIfAllowed(title,message,type,autohide,autohidetime) {
if (notifyOfEvents) {
notify(title,message,type,autohide,autohidetime);
}
}
var myContent = document.getElementById('wpTextbox1').value;
var noIssues = true;
//Remove unprocessed content (content inside nowiki or syntaxhighlight)
//This content is never actively displayed and we should therefore never judge it
var removedText = {};
var tag;
var i;
var unprocessedTags = ["[Ss][Yy][Nn][Tt][Aa][Xx][Hh][Ii][Gg][Hh][Ll][Ii][Gg][Hh][Tt]","[Nn][Oo][Ww][Ii][Kk][Ii]","[Pp][Rr][Ee]"];
for (tag in unprocessedTags) {
tag = unprocessedTags[tag];
i = Object.keys(removedText).length;
removedText[i] = [];
if (ISDEV)
console.log("Handling",tag,i,removedText[i]);
while (true) {
var foundTag = RegExp('<'+tag+'[^>]*>[\\s\\S]+?</'+tag+'>').exec(myContent);
if (!foundTag) {
break;
} else {
foundTag = foundTag[0];
removedText[i][removedText[i].length] = foundTag;
myContent = myContent.replace(foundTag,"REMOVED_TAG_"+i+"_"+(removedText[i].length-1));
}
}
}
if (ISDEV)
console.log("removed content:",removedText);
// Fix font tags - START
var fontTagBalance = myContent.split(/<font/gi).length-myContent.split(/<\/font/gi).length;
if (fontTagBalance > 0) {
notifyIfAllowed("Lint Fixer","There were "+fontTagBalance+" too many opening font tags","warn",false);
noIssues = false;
} else if (fontTagBalance < 0) {
notifyIfAllowed("Lint Fixer","There were "+(-fontTagBalance)+" too many closing font tags","warn",false);
noIssues = false;
}
while (true) {
var fontTag = RegExp('<[Ff][Oo][Nn][Tt][^>]*>').exec(myContent);
if (!fontTag) { //Out of font tags, we are done here
break;
} else {
fontTag = fontTag[0];
}
if (ISDEV)
console.log("fontTag",fontTag);
var tagData = ProcessTag(fontTag);
var style;
var fontColour = tagData.args.color;
if (fontColour) {
style = tagData.args.style || "";
if (style.length > 0 && style.substring(style.length-1) != ";") {
style = style + ";";
}
if (fontColour.substring(0,1) != "#" && !isNaN(parseInt(fontColour,16)) && !fontColours.includes(fontColour.toLowerCase())) {
fontColour = "#" + fontColour;
}
style = style + "color:" + fontColour;
tagData.args.style = style;
tagData.args.color = null;
}
var fontFace = tagData.args.face;
if (fontFace) {
style = tagData.args.style || "";
if (style.length > 0 && style.substring(style.length-1) != ";") {
style = style + ";";
}
style = style + "font-family:" + fontFace;
tagData.args.style = style;
tagData.args.face = null;
}
var fontSize = tagData.args.size;
if (fontSize) { //This requires manual conversion, as the metric is a little different
//Logic is based off of the exact px provided by [[mw:Help:Lint errors/obsolete-tag]]
//Reinforced by the behaviour seen in [[Special:Permalink/1118333555]]
var sizes = {1:"x-small",2:"small",3:"medium",4:"large",5:"x-large",6:"xx-large",7:"xxx-large"};
style = tagData.args.style || "";
if (style.length > 0 && style.substring(style.length-1) != ";") {
style = style + ";";
}
if (fontSize.substring(fontSize.length-2) == "px") {
fontSize = fontSize.substring(0,fontSize.length-2);
}
if (fontSize.substring(0,1) == "+") {
fontSize = 3 + (parseInt(fontSize.substring(1)));
} else if (fontSize.substring(0,1) == "-") {
fontSize = 3 - (parseInt(fontSize.substring(1)));
}
fontSize = Math.max(1,Math.min(7,fontSize)); //Limit 1 -> 7
if (!sizes[fontSize]) {
notifyIfAllowed("Lint Fixer","Unable to recognise font size of "+fontSize+" ("+tagData.args.size+")","warn",false);
noIssues = false;
} else {
style = style + "font-size:" + sizes[fontSize];
tagData.args.style = style;
tagData.args.size = null;
}
}
tagData.name = "span";
myContent = myContent.replace(fontTag,StringifyTag(tagData));
}
myContent = myContent.replaceAll(/<\/font>/gi,"</span>"); //Don't overengineer, this'll do
// Fix font tags - END
// Fix strike tags - START
myContent = myContent.replaceAll(/<strike>/gi,"<s>"); //Simple approach does the job
myContent = myContent.replaceAll(/<\/strike>/gi,"</s>");
// Fix strike tags - END
// Fix tt tags - START
myContent = myContent.replaceAll(/<tt>/gi,"<kbd>");
myContent = myContent.replaceAll(/<\/tt>/gi,"</kbd>");
// Fix tt tags - END
// Fix center tags - START
while (true) {
var centerTag = RegExp('<[Cc][Ee][Nn][Tt][Ee][Rr]>([\\s\\S]+?)</[Cc][Ee][Nn][Tt][Ee][Rr]>').exec(myContent);
var centerContent;
if (!centerTag) {
break;
} else {
centerContent = centerTag[1];
centerTag = centerTag[0];
}
if (centerContent.search("{\\|") > -1) {
wikitableStyle = RegExp('(\n{\\|.*?)\n').exec(centerContent)[1];
styleTag = RegExp("(style=['\"].*?);?['\"]").exec(wikitableStyle);
if (styleTag) { //These are messy lines, but it's supervised so it's good enough
centerContent = centerContent.replace(wikitableStyle,wikitableStyle.replace(styleTag[0],styleTag[1]+";margin:1em auto\""));
} else {
centerContent = centerContent.replace(wikitableStyle,wikitableStyle+" style=\"margin:1em auto\"");
}
if (centerContent.substring(0,1) == "\n") {
centerContent = centerContent.substring(1);
}
if (centerContent.substring(centerContent.length-1) == "\n") {
centerContent = centerContent.substring(0,centerContent.length-1);
}
myContent = myContent.replace(centerTag,centerContent);
} else {
if (centerContent.search("=") > -1 && centerContent.search("{") == -1) { //Definite fix needed
myContent = myContent.replace(centerTag,"{{center|1="+centerContent+"}}");
} else { //May still need fix, but who knows :)
myContent = myContent.replace(centerTag,"{{center|"+centerContent+"}}");
}
}
}
// Fix center tags - END
//Bring back unprocessed content in the reverse order to avoid bad nesting fails
for (i=Object.keys(removedText).length-1; i>=0; i--) {
var tags = removedText[i];
for (i2=0; i2<tags.length; i2++) {
myContent = myContent.replace("REMOVED_TAG_"+i+"_"+i2,tags[i2]);
}
}
return {myContent:myContent, wasChanged:document.getElementById('wpTextbox1').value != myContent, noIssues:noIssues};
}
if(mw.config.get('wgAction') == 'edit') {
if (getURIArg('fixlint') == 'true' || ISDEV) {
data = modifyPageContent(true);
document.getElementById('wpTextbox1').value = data.myContent;
if (data.wasChanged) {
document.getElementById('wpSummary').value = '[[User:Aidan9382/scripts/fixlint.js|→]]Fix [[WP:Linter|Lint]] Errors';
document.getElementById('wpMinoredit').checked = true;
//document.getElementById('wpWatchthis').checked = true;
//document.getElementById('wpWatchlistExpiry').selectedIndex = 2;
if (!ISDEV && data.noIssues)
document.forms.editform.wpDiff.click();
else if (!data.noIssues)
notify("Lint Fixer","There were some potential issues found during fixing","warn",false);
} else {
notify("Lint Fixer","No lint errors were found","success");
}
} else {
data = modifyPageContent(false);
if (data.wasChanged) {
if (data.noIssues) {
notify("Lint Fixer","Fixable lint errors were found on this page");
} else {
notify("Lint Fixer","Fixable lint errors were found on this page (supervision required)","warn");
}
} else {
//notify("Lint Fixer","No lint errors were found","success");
}
}
}
// </nowiki>