Untitled diff

Created Diff never expires
// ==UserScript==
// ==UserScript==
// @name Stack-Exchange-Editor-Toolkit
// @name Stack-Exchange-Editor-Toolkit
// @author Cameron Bernhardt (AstroCB)
// @author Cameron Bernhardt (AstroCB)
// @developer Jonathan Todd (jt0dd)
// @developer Jonathan Todd (jt0dd)
// @developer sathyabhat
// @developer sathyabhat
// @contributor Unihedron
// @contributor Unihedron
// @contributor Tiny Giant
// @contributor Mogsdad
// @grant none
// @license MIT
// @license MIT
// @namespace http://github.com/AstroCB
// @namespace http://github.com/AstroCB
// @version 1.5.1
// @version 1.5.2.25
// @description Fix common grammar/usage annoyances on Stack Exchange posts with a click
// @description Fix common grammar/usage annoyances on Stack Exchange posts with a click
// @include *://*.stackexchange.com/questions/*
// @include /^https?://\w*.?(stackoverflow|stackexchange|serverfault|superuser|askubuntu|stackapps)\.com/(questions|posts|review)/(?!tagged|new).*/
// @include *://stackoverflow.com/questions/*
// @include *://stackoverflow.com/review/helper/*
// @include *://meta.stackoverflow.com/questions/*
// @include *://serverfault.com/questions/*
// @include *://meta.serverfault.com/questions/*
// @include *://superuser.com/questions/*
// @include *://meta.superuser.com/questions/*
// @include *://askubuntu.com/questions/*
// @include *://meta.askubuntu.com/questions/*
// @include *://stackapps.com/questions/*
// @include *://*.stackexchange.com/posts/*
// @include *://stackoverflow.com/posts/*
// @include *://meta.stackoverflow.com/posts/*
// @include *://serverfault.com/posts/*
// @include *://meta.serverfault.com/posts/*
// @include *://superuser.com/posts/*
// @include *://meta.superuser.com/posts/*
// @include *://askubuntu.com/posts/*
// @include *://meta.askubuntu.com/posts/*
// @include *://stackapps.com/posts/*
// @exclude *://*.stackexchange.com/questions/tagged/*
// @exclude *://stackoverflow.com/questions/tagged/*
// @exclude *://meta.stackoverflow.com/questions/tagged/*
// @exclude *://serverfault.com/questions/tagged/*
// @exclude *://meta.serverfault.com/questions/*
// @exclude *://superuser.com/questions/tagged/*
// @exclude *://meta.superuser.com/questions/tagged/*
// @exclude *://askubuntu.com/questions/tagged/*
// @exclude *://meta.askubuntu.com/questions/tagged/*
// @exclude *://stackapps.com/questions/tagged/*
// ==/UserScript==
// ==/UserScript==


(function(){
(function() {
"use strict";
"use strict";
var main = function() {
function extendEditor(root) {
// Define app namespace
var App = {};
var App = {};


// Place edit items here
// Place edit items here
App.items = [];
App.items = {};
App.originals = {};


// Place selected jQuery items here
// Place selected jQuery items here
App.selections = {};
App.selections = {};


// Place "global" app data here
// Place "global" app data here
App.globals = {};
App.globals = {};


// Place "helper" functions here
// Place "helper" functions here
App.funcs = {};
App.funcs = {};
// True to display rule names in Edit Summary
App.globals.showRules = false;


//Preload icon alt
//Preload icon alt
var SEETicon = new Image();
var SEETicon = new Image();


SEETicon.src = '//i.imgur.com/d5ZL09o.png';
SEETicon.src = '//i.imgur.com/d5ZL09o.png';


// Populate global data
App.globals.root = root;
// Get url for question id used in id and class names
App.globals.URL = window.location.href;

// Get question num from URL
App.globals.questionNum = App.globals.URL.match(/\/(\d+)\//g);
if (App.globals.questionNum) {
App.globals.questionNum = App.globals.questionNum[0].split("/").join("");
}

// Define variables for later use
App.globals.barReady = false;
App.globals.editsMade = false;
App.globals.editCount = 0;
App.globals.infoContent = '';


App.globals.spacerHTML = '<li class="wmd-spacer wmd-spacer3" id="wmd-spacer3-' + App.globals.questionNum + '" style="left: 400px !important;"></li>';
App.globals.spacerHTML = '<li class="wmd-spacer wmd-spacer3" id="wmd-spacer3" style="left: 400px !important;"></li>';
App.globals.buttonHTML = '<div id="ToolkitButtonWrapper"><button class="wmd-button" id="ToolkitFix"></button><div id="ToolkitInfo"></div></div>';


App.globals.reasons = [];
App.globals.reasons = {};
App.globals.numReasons = 0;


App.globals.replacedStrings = {
App.globals.replacedStrings = {
"block": [],
"auto": [],
"inline": []
"quote": [],
"inline": [],
"block": [],
"links": [],
"tags": []
};
};
App.globals.placeHolders = {
App.globals.placeHolders = {
"block": "_xCodexBlockxPlacexHolderx_",
"auto": "_xAutoxInsertxTextxPlacexHolder_",
"inline": "_xCodexInlinexPlacexHolderx_"
"quote": "_xBlockxQuotexPlacexHolderx_",
"inline": "_xCodexInlinexPlacexHolderx_",
"block": "_xCodexBlockxPlacexHolderx_",
"links": "_xLinkxPlacexHolderx_",
"tags": "_xTagxPlacexHolderx_"
};
App.globals.placeHolderChecks = {
"auto": /_xAutoxInsertxTextxPlacexHolder_/gi,
"quote": /_xBlockxQuotexPlacexHolderx_/gi,
"inline": /_xCodexInlinexPlacexHolderx_/gi,
"block": /_xCodexBlockxPlacexHolderx_/gi,
"links": /_xLinkxPlacexHolderx_/gi,
"tags": /_xTagxPlacexHolderx_/gi
};
};
App.globals.checks = {
App.globals.checks = {
"block": /( )+.*/gm,
// https://regex101.com/r/cI6oK2/1 automatically inserted text
"inline": /`.*`/gm
"auto": /[^]*\<\!\-\- End of automatically inserted text \-\-\>/g,
// https://regex101.com/r/fU5lE6/1 blockquotes
"quote": /^\>(?:(?!\n\n)[^])+/gm,
// https://regex101.com/r/lL6fH3/1 single-line inline code
"inline": /`[^`\n]+`/g,
// https://regex101.com/r/eC7mF7/1 code blocks and multiline inline code.
"block": /`[^`]+`|(?:(?:[ ]{4}|[ ]{0,3}\t).+(?:[\r\n]?(?!\n\S)(?:[ ]+\n)*)+)+/g,
// https://regex101.com/r/tZ4eY3/5 links and link-sections
"links": /\[[^\]\n]+\](?:\([^\)\n]+\)|\[[^\]\n]+\])|(?: (?:\[\d\]): \w*:+\/\/.*\n*)+|(?!.net)(?:\/\w+|.:\\|\.[^ \n\r.]+|\w+:\/\/)[^\s)]*/g,
// tags and html comments TODO: needs test
"tags": /\<[\/a-z]+\>|\<\!\-\-[^>]+\-\-\>/g
};
};


// Assign modules here
// Assign modules here
App.globals.pipeMods = {};
App.globals.pipeMods = {};


// Define order in which mods affect here
// Define order in which mods affect here
App.globals.order = ["omit", "edit", "replace"];
App.globals.order = ["omit", "codefix", "edit", "replace"];



// Define edit rules
// Define edit rules
App.edits = {
App.edits = {
i: {
// All caps
expr: /(^|\s|\()i(\s|,|\.|!|\?|;|\/|\)|'|$)/gm,
noneedtoyell: {
replacement: "$1I$2",
expr: /^((?=.*[A-Z])[^a-z]*)$/g,
reason: "in English, the pronoun 'I' is capitalized"
replacement: function(input) {
return input.trim().substr(0, 1).toUpperCase() + input.trim().substr(1).toLowerCase();
},
reason: 'no need to yell'
},
},
// Trademark capitalization
so: {
so: {
expr: /(^|\s)[Ss]tack\s*overflow|StackOverflow(.|$)/gm,
expr: /\bstack\s*overflow\b/gi,
replacement: "$1Stack Overflow$2",
replacement: "Stack Overflow",
reason: "'Stack Overflow' is the legal name"
reason: "'Stack Overflow' is the legal name"
},
},
se: {
se: {
expr: /(^|\s)[Ss]tack\s*exchange|StackExchange(.|$)/gm,
expr: /\bstack\s*exchange\b/gi,
replacement: "$1Stack Exchange$2",
replacement: "Stack Exchange",
reason: "'Stack Exchange' is the legal name"
reason: "'Stack Exchange' is the legal name"
},
},
expansionSO: {
expansionSO: {
expr: /(^|\s)SO(\s|,|\.|!|\?|;|\/|\)|$)/gm,
expr: /([^\b\w.]|^)SO\b/g,
replacement: "$1Stack Overflow$2",
replacement: "$1Stack Overflow",
reason: "'SO' expansion"
reason: "'Stack Overflow' is the legal name"
},
},
expansionSE: {
expansionSE: {
expr: /(^|\s)SE(\s|,|\.|!|\?|;|\/|\)|$)/gm,
expr: /([^\b\w.]|^)SE\b/g,
replacement: "$1Stack Exchange$2",
replacement: "$1Stack Exchange",
reason: "'SE' expansion"
reason: "'Stack Exchange' is the legal name"
},
},
javascript: {
javascript: {
expr: /(^|\s)[Jj]ava\s*[Ss]cript(.|$)/gm,
expr: /([^\b\w.]|^)(javascript|js)\b/gi,
replacement: "$1JavaScript$2",
replacement: "$1JavaScript",
reason: "'JavaScript' is the proper capitalization"
reason: "trademark capitalization"
},
},
jsfiddle: {
jsfiddle: {
expr: /(^|\s)[Jj][Ss]\s*[Ff]iddle(.|$)/gm,
expr: /\bjsfiddle\b/gi,
replacement: "$1JSFiddle$2",
replacement: "JSFiddle",
reason: "'JSFiddle' is the currently accepted capitalization"
reason: "trademark capitalization"
},
},
caps: {
jquery: {
expr: /^(?!https?)([a-z])/gm,
expr: /\bjquery\b/gi,
replacement: "$1",
replacement: "jQuery",
reason: "copy edited"
reason: "trademark capitalization"
},
},
jquery: {
angular: {
expr: /(^|\s)[Jj][Qq]uery(.|$)/gm,
expr: /\bangular(?:js)?\b/gi,
replacement: "$1jQuery$2",
replacement: "AngularJS",
reason: "'jQuery' is the proper capitalization"
reason: "trademark capitalization"
},
},
html: {
html: {
expr: /(^|\s)[Hh]tml([5]?)\b(\S|)(?!\S)/gm,
expr: /([^\b\w.]|^)html(\d)?\b/gi,
replacement: "$1HTML$2$3",
replacement: "$1HTML$2",
reason: "HTML stands for HyperText Markup Language"
reason: "trademark capitalization"
},
},
css: {
css: {
expr: /(^|\s)[Cc]ss\b(\S|)(?!\S)/gm,
expr: /([^\b\w.]|^)css\b/gi,
replacement: "$1CSS$2",
replacement: "$1CSS",
reason: "CSS stands for Cascading Style Sheets"
reason: "trademark capitalization"
},
},
json: {
json: {
expr: /(^|\s)[Jj]son\b(\S|)(?!\S)/gm,
expr: /\bjson\b/gi,
replacement: "$1JSON$2",
replacement: "JSON",
reason: "JSON stands for JavaScript Object Notation"
reason: "acronym capitalization"
},
},
ajax: {
ajax: {
expr: /(^|\s)ajax\b(\S|)(?!\S)/gm,
expr: /\bajax\b/gi,
replacement: "$1AJAX$2",
replacement: "AJAX",
reason: "AJAX stands for Asynchronous JavaScript and XML"
reason: "acronym capitalization"
},
angular: {
expr: /[Aa]ngular[Jj][Ss]/g,
replacement: "AngularJS",
reason: "'AngularJS is the proper capitalization"
},
thanks: {
expr: /(thanks|pl(?:ease|z|s)\s+h[ea]lp|cheers|regards|thx|thank\s+you|my\s+first\s+question|kindly\shelp).*$/gmi,
replacement: "",
reason: "'$1' is unnecessary noise"
},
commas: {
expr: /,([^\s])/g,
replacement: ", $1",
reason: "punctuation & spacing"
},
},
php: {
php: {
expr: /(^|\s)[Pp]hp\b(\S|)(?!\S)/gm,
expr: /([^\b\w.]|^)php\b/gi,
replacement: "$1PHP$2",
replacement: "$1PHP",
reason: "PHP stands for PHP: Hypertext Preprocessor"
reason: "trademark capitalization"
},
hello: {
expr: /(?:^|\s)(hi\s+guys|hi|hello|good\s(?:evening|morning|day|afternoon))(?:\.|!|\ )/gmi,
replacement: "",
reason: "greetings like '$1' are unnecessary noise"
},
edit: {
expr: /(?:^\**)(edit|update):?(?:\**):?/gmi,
replacement: "",
reason: "Stack Exchange has an advanced revision history system: 'Edit' or 'Update' is unnecessary"
},
},
voting: {
voting: {
expr: /([Dd]own|[Uu]p)[\s*\-]vot/g,
expr: /\b(down|up)\Wvot/gi,
replacement: "$1vote",
replacement: "$1vote",
reason: "the proper spelling (despite the tag name) is '$1vote' (one word)"
reason: "the proper spelling (despite the tag name) is '$1vote' (one word)"
},
},
mysite: {
expr: /mysite\./g,
replacement: "example.",
reason: "links to mysite.domain are not allowed: use example.domain instead"
},
c: {
c: {
expr: /(^|\s)c(#|\++|\s|$)/gm,
expr: /\bc\b([#+]+)?/gi,
replacement: "$1C$2",
replacement: "C$1",
reason: "C$2 is the proper capitalization"
reason: "trademark capitalization"
},
},
java: {
java: {
expr: /(^|\s)java\b(\S|)(?!\S)/gmi,
expr: /\bjava\b/gi,
replacement: "$1Java$2",
replacement: "Java",
reason: "Java should be capitalized"
reason: "trademark capitalization"
},
},
sql: {
sql: {
expr: /(^|\s)[Ss]ql\b(\S|)(?!\S)/gm,
expr: /([^\b\w.]|^)sql\b/gi,
replacement: "$1SQL$2",
replacement: "$1SQL",
reason: "SQL is the proper capitalization"
reason: "trademark capitalization"
},
},
sqlite: {
sqlite: {
expr: /(^|\s)[Ss]qlite([0-9]*)\b(\S|)(?!\S)/gm,
expr: /\bsqlite\s*([0-9]*)\b/gi,
replacement: "$1SQLite$2$3",
replacement: "SQLite $2",
reason: "SQLite is the proper capitalization"
reason: "trademark capitalization"
},
},
android: {
android: {
expr: /(^|\s)android\b(\S|)(?!\S)/gmi,
expr: /\bandroid\b/gi,
replacement: "$1Android$2",
replacement: "Android",
reason: "Android should be capitalized"
reason: "trademark capitalization"
},
},
oracle: {
oracle: {
expr: /(^|\s)oracle\b(\S|)(?!\S)/gmi,
expr: /\boracle\b/gi,
replacement: "$1Oracle$2",
replacement: "Oracle",
reason: "Oracle should be capitalized"
reason: "trademark capitalization"
},
},
windows: {
windows: {
expr: /(win|windows(?:\ ?)(\s[0-9]+))\b(\S|)(?!\S)/igm,
// https://regex101.com/r/jF9zK1/5
replacement: "Windows$2$3",
expr: /\b(?:win|windows)\s+(2k|[0-9.]+|ce|me|nt|xp|vista|server)|(?:win|windows)\b/gi,
reason: "Windows should be capitalized"
replacement: function(match, ver) {
ver = !ver ? '' : ver.replace(/ce/i, ' CE')
.replace(/me/i, ' ME')
.replace(/nt/i, ' NT')
.replace(/xp/i, ' XP')
.replace(/2k/i, ' 2000')
.replace(/vista/i, ' Vista')
.replace(/server/i, ' Server');
return 'Windows' + ver;
},
reason: "trademark capitalization"
},
},
windowsXP: {
linux: {
expr: /(win|windows(?:\ ?)(\sxp))\b(\S|)(?!\S)/igm,
expr: /\blinux\b/gi,
replacement: "Windows XP$3",
replacement: "Linux",
reason: "Windows XP should be capitalized"
reason: "trademark capitalization"
},
},
windowsVista: {
wordpress: {
expr: /(win|windows(?:\ ?)(\svista))\b(\S|)(?!\S)/igm,
expr: /\bwordpress\b/gi,
replacement: "Windows Vista$3",
replacement: "WordPress",
reason: "Windows Vista should be capitalized"
reason: "trademark capitalization"
},
google: {
expr: /\bgoogle\b/gi,
replacement: "Google",
reason: "trademark capitalization"
},
mysql: {
expr: /\bmysql\b/gi,
replacement: "MySQL",
reason: "trademark capitalization"
},
apache: {
expr: /\bapache\b/gi,
replacement: "Apache",
reason: "trademark capitalization"
},
git: {
expr: /\bgit\b/gi,
replacement: "Git",
reason: "trademark capitalization"
},
github: {
expr: /\bgithub\b/gi,
replacement: "GitHub",
reason: "trademark capitalization"
},
},
ubuntu: {
facebook: {
expr: /(ubunto|ubunut|ubunutu|ubunu|ubntu|ubutnu|ubanto[o]+|unbuntu|ubunt|ubutu)\b(\S|)(?!\S)/igm,
expr: /\bfacebook\b/gi,
replacement: "Ubuntu$2",
replacement: "Facebook",
reason: "corrected Ubuntu spelling"
reason: "trademark capitalization"
},
},
linux: {
python: {
expr: /(linux)\b(\S|)(?!\S)/igm,
expr: /\bpython\b/gi,
replacement: "Linux$2",
replacement: "Python",
reason: "Linux should be capitalized"
reason: "trademark capitalization"
},
},
apostrophes: {
urli: {
expr: /(^|\s)(can|doesn|don|won|hasn|isn|didn)t(\s|$)/gmi,
expr: /\b(ur[li])\b/gi,
replacement: "$1$2't$3",
replacement: function(match) {
reason: "English contractions use apostrophes"
return match.toUpperCase();
},
reason: "acronym capitalization"
},
},
ios: {
ios: {
expr: /\b(?:ios|iOs|ioS|IOS|Ios|IoS|ioS)\b(\S|)(?!\S)/gm,
expr: /\bios\b/gi,
replacement: "iOS$1",
replacement: "iOS",
reason: "the proper usage is 'iOS'"
reason: "trademark capitalization"
},
},
iosnum: {
iosnum: {
expr: /\b(?:ios|iOs|ioS|IOS|Ios|IoS|ioS)([0-9]?)\b(\S|)(?!\S)/gm,
expr: /\bios([0-9])\b/gi,
replacement: "iOS $1$2",
replacement: "iOS $1",
reason: "the proper usage is 'iOS' followed by a space and the version number"
reason: "trademark capitalization"
},
ubunto: {
expr: /\b[uoa]*b[uoa]*[tn][oua]*[tnu][oua]*\b/gi,
replacement: "Ubuntu",
reason: "trademark capitalization"
},
vbnet: {
expr: /(?:vb)?(?:\.net|\s?[0-9]+)\s?(?:framework|core)?/gi,
replacement: function(str) {
return str.replace(/vb/i, 'VB')
.replace(/net/i, 'NET')
.replace(/framework/i, 'Framework')
.replace(/core/i, 'Core');
},
reason: "trademark capitalization"
},
regex: {
expr: /\bregex(p)?/gi,
replacement: "RegEx$1",
reason: "trademark capitalization"
},
// Noise reduction
editupdate: {
// https://regex101.com/r/tT2pK6/2
expr: /(?!(?:edit|update)\w*\s*[^:]*$)(?:^\**)(edit|update)\w*(\s*#?[0-9]+)?:?(?:\**):?/gmi,
replacement: "",
reason: "noise reduction"
},
hello: { // TODO: Update badsentences (new) to catch everything hello (old) did.
expr: /(?:^|\s)(hi\s+guys|hi|hello|good\s(?:evening|morning|day|afternoon))(?:\.|!|\ )/gmi,
replacement: "",
reason: "noise reduction"
},
badwords: {
expr: /[^\n.!?:]*\b(?:th?anks?|th(?:an)?x|tanx|folks?|ki‌nd(‌?:est|ly)|first\s*question)\b[^,.!?\n]*[,.!?]*/gi,
replacement: "",
reason: "noise reduction"
},
},
caps: {
badphrases: {
expr: /^((?=.*[A-Z])[^a-z]*)$/g,
expr: /[^\n.!?:]*(?:h[ea]lp|hope|appreciate|pl(?:ease|z|s))[^.!?\n]*(?:helps?|appreciated?)[^,.!?\n]*[,.!?]*/gi,
replacement: "$1",
replacement: "",
reason: "no need to yell"
reason: "noise reduction"
},
imnew: {
expr: /(?! )[\w\s]*\bi[' ]?a?m +(?:kinda|really) *new\w* +(?:to|in) *\w* *(?:and|[;,.!?])? */gi,
replacement: "",
reason: "noise reduction"
},
},
wordpress: {
salutations: {
expr: /[Ww]ordpress/g,
expr: /[\r\n]*(regards|cheers?),?[\t\f ]*[\r\n]?\w*\.?/gi,
replacement: "WordPress",
replacement: "",
reason: "'WordPress' is the proper capitalization"
reason: "noise reduction"
},
},
google: {
// Grammar and spelling
expr: /(google)\b(\S|)(?!\S)/igm,
apostrophe_d: {
replacement: "Google$2",
expr: /\b(he|she|who|you)[^\w']*(d)\b/gi,
reason: "Google is the proper capitalization"
replacement: "$1'$2",
reason: "grammar and spelling"
},
},
mysql: {
apostrophe_ll: {
expr: /(mysql)\b(\S|)(?!\S)/igm,
expr: /\b(they|what|who|you)[^\w']*(ll)\b/gi,
replacement: "MySQL$2",
replacement: "$1'$2",
reason: "MySQL is the proper capitalization"
reason: "grammar and spelling"
},
},
apache: {
apostrophe_re: {
expr: /(apache)\b(\S|)(?!\S)/igm,
expr: /\b(they|what|you)[^\w']*(re)\b/gi,
replacement: "Apache$2",
replacement: "$1'$2",
reason: "Apache is the proper capitalization"
reason: "grammar and spelling"
},
},
git: {
apostrophe_s: {
expr: /(^|\s)(git|GIT)\b(\S|)(?!\S)/gm,
expr: /\b(he|she|that|there|what|where)[^\w']*(s)\b/gi,
replacement: "$1Git$3",
replacement: "$1'$2",
reason: "Git is the proper capitalization"
reason: "grammar and spelling"
},
},
harddisk: {
apostrophe_t: {
expr: /(hdd|harddisk)\b(\S|)(?!\S)/igm,
expr: /\b(aren|can|didn|doesn|don|hasn|haven|isn|mightn|mustn|shan|shouldn|won|wouldn)[^\w']*(t)\b/gi,
replacement: "hard disk$2",
replacement: "$1'$2",
reason: "Hard disk is the proper capitalization"
reason: "grammar and spelling"
},
},
github: {
prolly: {
expr: /\b([gG]ithub|GITHUB)\b(\S|)(?!\S)/gm,
expr: /\bproll?y\b/gi,
replacement: "GitHub$2",
replacement: "probably",
reason: "GitHub is the proper capitalization"
reason: "grammar and spelling"
},
i: {
expr: /\bi('|\b)/g, // i or i-apostrophe
replacement: "I",
reason: "grammar and spelling"
},
im: {
expr: /\bim\b/gi,
replacement: "I'm",
reason: "grammar and spelling"
},
ive: {
expr: /\bive\b/gi,
replacement: "I've",
reason: "grammar and spelling"
},
ur: {
expr: /\bur\b/gi,
replacement: "your", // May also be "you are", but less common on SO
reason: "grammar and spelling"
},
u: {
expr: /\bu\b/gi,
replacement: "you",
reason: "grammar and spelling"
},
gr8: {
expr: /\bgr8\b/gi,
replacement: "great",
reason: "grammar and spelling"
},
allways: {
expr: /\b(a)llways\b/gi,
replacement: "$1lways",
reason: "grammar and spelling"
},
// Punctuation & Spacing come last
firstcaps: {
// https://regex101.com/r/qR5fO9/12
// This doesn't work quite right, because is finds all sentences, not just ones needing caps.
//expr: /(?:(?!\n\n)[^\s.!?]+[ ]*)+([.!?])*[ ]*/g,
expr: /((?!\n\n)(?:[^?.!])*([?.!]|\n\n)?\)*)/gm,
replacement: function(str, endpunc) {
if (str === "undefined") return '';
console.log('str: '+str);
// https://regex101.com/r/bL9xD7/1 find and capitalize first letter
return str.replace(/^(\W*)([a-z])(.*)/g, function(sentence, pre, first, post) {
if (!pre) pre = '';
if (!post) post = '';
console.log('sentence ('+sentence+') pre ('+pre+') first ('+first+') post ('+post+') endpunc ('+endpunc+')');
var update = pre + first.toUpperCase() + post// + (!endpunc && /\w/.test(post.substr(-1)) ? '.' : '');
console.log('update ('+update+')');
return update;
});
},
reason: "Caps at start of sentences"
},
multiplesymbols: {
// https://regex101.com/r/bE9zM6/1
expr: /([^\w\s*#.\-_])\1{1,}/g,
replacement: "$1",
reason: "punctuation & spacing"
},
spacesbeforesymbols: {
expr: /\s+([.,!?;:])(?!\w)/g,
replacement: "$1",
reason: "punctuation & spacing"
},
multiplespaces: {
// https://regex101.com/r/hY9hQ3/1
expr: /[ ]{2,}(?!$)/g,
replacement: " ",
reason: "punctuation & spacing"
}
}
};
};


// Populate funcs
// This is where the magic happens: this function takes a few pieces of information and applies edits to the post with a couple exceptions
App.popFuncs = function() {
App.funcs.fixIt = function(input, expression, replacement, reasoning) {
// This is where the magic happens: this function takes a few pieces of information and applies edits to the post with a couple exceptions
// If there is nothing to search, exit
App.funcs.fixIt = function(input, expression, replacement, reasoning) {
if (!input) return false;
// Scan the post text using the expression to see if there are any matches
// Scan the post text using the expression to see if there are any matches
var match = input.search(expression);
var matches = input.match(expression);
// If so, increase the number of edits performed (used later for edit summary formation)
if (!matches) return false;
if (match !== -1) {
console.log(JSON.stringify(matches))
App.globals.editCount++;
var count = matches.length; // # replacements to do
var tmpinput = input;
input = input.replace(expression, function() {
var matches = [].slice.call(arguments, 0, -2);
reasoning = reasoning.replace(/[$](\d)+/g, function() {
var phrases = [].slice.call(arguments, 0, -2);
var phrase = matches[phrases[1]];
return phrase ? phrase : '';
});
return arguments[0].replace(expression, replacement);
});
if (input !== tmpinput) {
return {
reason: reasoning,
fixed: String(input).trim(),
count: count
};
} else return false;
};


// Later, this will store what is removed for the first case
App.funcs.applyListeners = function() { // Removes default Stack Exchange listeners; see https://github.com/AstroCB/Stack-Exchange-Editor-Toolkit/issues/43
var phrase;
function removeEventListeners(e) {
if (e.which === 13) {
if (e.metaKey || e.ctrlKey) {
// CTRL/CMD + Enter -> Activate the auto-editor
App.selections.buttonFix.click();
} else {
// It's possible to remove the event listeners, because of the way outerHTML works.
this.outerHTML = this.outerHTML;
App.selections.submitButton.click();
}
}
}


// Store the original input text
// Tags box
var originalInput = input;
App.selections.tagField.keydown(removeEventListeners);


// Then, perform the edits using replace()
// Edit summary box
// What follows is a series of exceptions, which I will explain below; I perform special actions by overriding replace()
App.selections.summary.keydown(removeEventListeners);
// This is used for removing things entirely without giving a replacement; it matches the expression and then replaces it with nothing
};
if (replacement === "") {
input = input.replace(expression, function(data, match1) {
// Save what is removed for the edit summary (see below)
phrase = match1;


// Replace with nothing
// Populate or refresh DOM selections
return "";
App.funcs.popSelections = function() {
});
App.selections.redoButton = App.globals.root.find('[id^="wmd-redo-button"]');
App.selections.body = App.globals.root.find('[id^="wmd-input"]');
App.selections.title = App.globals.root.find('[class*="title-field"]');
App.selections.summary = App.globals.root.find('[id^="edit-comment"]');
App.selections.tagField = App.globals.root.find(".tag-editor");
App.selections.submitButton = App.globals.root.find('[id^="submit-button"]');
App.selections.helpButton = App.globals.root.find('[id^="wmd-help-button"]');
App.selections.editor = App.globals.root.find('.post-editor');
};


// This is an interesting tidbit: if you want to make the edit summaries dynamic, you can keep track of a match that you receive
// Populate edit item sets from DOM selections
// from overriding the replace() function and then use that in the summary
App.funcs.popItems = function() {
reasoning = reasoning.replace("$1", phrase);
var i = App.items, s = App.selections;
['title', 'body', 'summary'].forEach(function(v) {
i[v] = String(s[v].val()).trim();
});
};


// This allows me to combine the upvote and downvote replacement schemes into one
// Populate original item sets from DOM selections
} else if (replacement == "$1vote") {
App.funcs.popOriginals = function() {
input = input.replace(expression, function(data, match1) {
var i = App.originals, s = App.selections;
phrase = match1;
['title', 'body', 'summary'].forEach(function(v) {
return phrase + "vot";
i[v] = String(s[v].val()).trim();
});
});
reasoning = reasoning.replace("$1", phrase.toLowerCase());
};


// Fix all caps
// Insert editing button(s)
} else if (reasoning === "no need to yell") {
App.funcs.createButton = function() {
input = input.replace(expression, function(data, match1) {
if (!App.selections.redoButton.length) return false;
return match1.substring(0, 1).toUpperCase() + match1.substring(1).toLowerCase();
});
// This is used to capitalize letters; it merely takes what is matched, uppercases it, and replaces what was matched with the uppercased version
} else if (replacement === "$1") {
input = input.replace(expression, function(data, match1) {
return match1.toUpperCase();
});


// I can use C, C#, and C++ capitalization in one rule
App.selections.buttonWrapper = $('<div class="ToolkitButtonWrapper"/>');
} else if (replacement === "$1C$2") {
App.selections.buttonFix = $('<button class="wmd-button ToolkitFix" title="Fix the content!" />');
var newPhrase;
App.selections.buttonInfo = $('<div class="ToolkitInfo">');
input = input.replace(expression, function(data, match1, match2) {
newPhrase = match2;
return match1 + "C" + match2;
});
reasoning = reasoning.replace("$2", newPhrase);


// iOS numbering/spacing fixes
// Build the button
} else if (replacement === "iOS $2") {
App.selections.buttonWrapper.append(App.selections.buttonFix);
input = input.replace(expression, function(data, match1) {
App.selections.buttonWrapper.append(App.selections.buttonInfo);
if (match1.match(/\d/)) { // Is a number
return "iOS " + match1;
}


return "iOS" + match1;
// Insert button
});
App.selections.redoButton.after(App.selections.buttonWrapper);
// Insert spacer
App.selections.redoButton.after(App.globals.spacerHTML);


// Default: just replace it with the indicated replacement
// Attach the event listener to the button
} else {
App.selections.buttonFix.click(App.funcs.fixEvent);
input = input.replace(expression, replacement);
}


// Check whether anything was changed
App.selections.helpButton.css({
if (input === originalInput) {
'padding': '0px'
return null;
});
} else {
App.selections.buttonWrapper.css({
// Return a dictionary with the reasoning for the fix and what is edited (used later to prevent duplicates in the edit summary)
'position': 'relative',
return {
'left': '430px',
reason: reasoning,
'padding-top': '2%'
fixed: input
});
};
App.selections.buttonFix.css({
}
'position': 'static',
} else {
'float': 'left',
// If nothing needs to be fixed, return null
'border-width': '0px',
return null;
'background-color': 'white',
}
'background-image': 'url("//i.imgur.com/79qYzkQ.png")',
};
'background-size': '100% 100%',
'width': '18px',
'height': '18px',
'outline': 'none',
'box-shadow': 'none'
});
App.selections.buttonInfo.css({
'position': 'static',
'float': 'left',
'margin-left': '5px',
'font-size': '12px',
'color': '#424242',
'line-height': '19px'
});
};


// Omit code
App.funcs.makeDiffTable = function() {
App.funcs.omitCode = function(str, type) {
App.selections.diffTable = $('<table class="diffTable"/>');
str = str.replace(App.globals.checks[type], function(match) {
App.selections.editor.append(App.selections.diffTable);
App.globals.replacedStrings[type].push(match);
};
return App.globals.placeHolders[type];
});
return str;
};


// Replace code
App.funcs.fixEvent = function(e) {
App.funcs.replaceCode = function(str, type) {
if (e) e.preventDefault();
for (var i = 0; i < App.globals.replacedStrings[type].length; i++) {
// Refresh item population
str = str.replace(App.globals.placeHolders[type],
App.funcs.popOriginals();
App.globals.replacedStrings[type][i]);
App.funcs.popItems();
}
// Pipe data through editing modules
return str;
App.pipe(App.items, App.globals.pipeMods, App.globals.order);
};
};


// Eliminate duplicates in array (awesome method I found on SO, check it out!)
App.funcs.diff = function() {
// From AstroCB: the original structure of the edit formation prevents duplicates.
App.selections.diffTable.empty();
// Unless you changed that structure somehow, this shouldn't be needed.
App.funcs.eliminateDuplicates = function(arr) {
var i, len = arr.length,
out = [],
obj = {};


for (i = 0; i < len; i++) {
function maakRij(x, y, type, rij) {
obj[arr[i]] = 0;
}
for (i in obj) {
if (obj.hasOwnProperty(i)) { // Prevents messiness of for..in statements
out.push(i);
}
}
return out;
};


App.funcs.applyListeners = function() { // Removes default Stack Exchange listeners; see https://github.com/AstroCB/Stack-Exchange-Editor-Toolkit/issues/43
var tr = $('<tr/>');
function removeEventListeners(e) {
if (e.which === 13) {
if (e.metaKey || e.ctrlKey) {
// CTRL/CMD + Enter -> Activate the auto-editor
App.selections.buttonFix.click();
this.focus();
} else {
// It's impossible to remove the event listeners, so we have to clone the element without any listeners
var elClone = this.cloneNode(true);
this.parentNode.replaceChild(elClone,
this);
App.selections.submitButton.click();
}
}
}


// Tags box
if (type === '+') tr.addClass('add');
App.selections.tagField.keydown(removeEventListeners);
if (type === '-') tr.addClass('del');


// Edit summary box
tr.append($('<td class="codekolom">' + y + '</td>'));
App.selections.summaryBox.keydown(removeEventListeners);
tr.append($('<td class="codekolom">' + x + '</td>'));
};
tr.append($('<td class="bredecode">' + type + ' ' + rij.replace(/\</g, '&lt;') + '</td>'));


// Wait for relevant dynamic content to finish loading
App.selections.diffTable.append(tr);
App.funcs.dynamicDelay = function(callback, id, inline) {
}
if (inline) { // Inline editing
setTimeout(function() {
App.selections.buttonBar = $('#wmd-button-bar-' + id);
App.selections.buttonBar.unbind();
setTimeout(function() {
callback();
}, 0);
}, 500);
} else { // Question page editing
App.selections.buttonBar = $('#wmd-button-bar-' + id);
// When button bar updates, dynamic DOM is ready for selection
App.selections.buttonBar.unbind().on('DOMSubtreeModified', function() {
// Avoid running it more than once
if (!App.globals.barReady) {
App.globals.barReady = true;


// Run asynchronously - this lets the bar finish updating before continuing
function getDiff(matrix, a1, a2, x, y) {
setTimeout(function() {
if (x > 0 && y > 0 && a1[y - 1] === a2[x - 1]) {
callback();
getDiff(matrix, a1, a2, x - 1, y - 1);
}, 0);
maakRij(x, y, ' ', a1[y - 1]);
}
} else {
});
if (x > 0 && (y === 0 || matrix[y][x - 1] >= matrix[y - 1][x])) {
getDiff(matrix, a1, a2, x - 1, y);
maakRij(x, '', '+', a2[x - 1]);
} else if (y > 0 && (x === 0 || matrix[y][x - 1] < matrix[y - 1][x])) {
getDiff(matrix, a1, a2, x, y - 1);
maakRij('', y, '-', a1[y - 1], '');
} else {
return;
}
}
}
};


// Populate or refresh DOM selections
}
App.funcs.popSelections = function() {
App.selections.redoButton = $('#wmd-redo-button-' + App.globals.questionNum);
App.selections.bodyBox = $("#wmd-input-" + App.globals.questionNum);
App.selections.titleBox = $(".ask-title-field");
App.selections.summaryBox = $("#edit-comment-" + App.globals.questionNum);
App.selections.tagField = $($(".tag-editor")[0]);
App.selections.submitButton = $("#submit-button-" + App.globals.questionNum);
};


// Populate edit item sets from DOM selections
App.funcs.popItems = function() {
var a1 = App.originals.body.split('\n');
App.items[0] = {
var a2 = App.items.body.split('\n');
title: App.selections.titleBox.val(),
body: App.selections.bodyBox.val(),
var matrix = new Array(a1.length + 1);
summary: ''
var x, y;
};
for (y = 0; y < matrix.length; y++) {
};
matrix[y] = new Array(a2.length + 1);


// Insert editing button(s)
for (x = 0; x < matrix[y].length; x++) {
App.funcs.createButton = function() {
matrix[y][x] = 0;
// Insert button
}
App.selections.redoButton.after(App.globals.buttonHTML);
}


// Insert spacer
for (y = 1; y < matrix.length; y++) {
App.selections.redoButton.after(App.globals.spacerHTML);
for (x = 1; x < matrix[y].length; x++) {

if (a1[y - 1] === a2[x - 1]) {
// Add new elements to selections
matrix[y][x] = 1 + ma
App.selections.buttonWrapper = $('#ToolkitButtonWrapper');
App.selections.buttonFix = $('#ToolkitFix');
App.selections.buttonInfo = $('#ToolkitInfo');
};

// Style button
App.funcs.styleButton = function() {
var buttonCSS = {
'position': 'relative',
'left': '430px',
'padding-top': '2%'
};
$("#wmd-help-button-" + App.globals.questionNum).css({
'padding': '0px'
});
App.selections.buttonWrapper.css(buttonCSS);

App.selections.buttonFix.css({
'position': 'static',
'float': 'left',
'border-width': '0px',
'background-color': 'white',
'background-image': 'url("//i.imgur.com/79qYzkQ.png")',
'background-size': '100% 100%',
'width': '18px',
'height': '18px',
'outline': 'none',
'box-shadow': 'none'
});
App.selections.buttonInfo.css({
'position': 'static',
'float': 'left',
'margin-left': '5px',
'font-size': '12px',
'color': '#424242',
'line-height': '19px'
});

App.selections.buttonFix.hover(function() {
App.globals.infoContent = App.selections.buttonInfo.text();
App.selections.buttonInfo.text('Fix the content!');
App.selections.buttonFix.css({
'background-image': 'url("//i.imgur.com/d5ZL09o.png")'
});
}, function() {
App.selections.buttonInfo.text(App.globals.infoContent);
App.s