Untitled diff

Created Diff never expires
494 removals
Lines
Total
Removed
Words
Total
Removed
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
581 lines
545 additions
Lines
Total
Added
Words
Total
Added
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
633 lines
// ==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: {
expr: /^(?!https?)([a-z])/gm,
replacement: "$1",
reason: "copy edited"
},
},
jquery: {
jquery: {
expr: /(^|\s)[Jj][Qq]uery(.|$)/gm,
expr: /\bjquery\b/gi,
replacement: "$1jQuery$2",
replacement: "jQuery",
reason: "'jQuery' is the proper capitalization"
reason: "trademark capitalization"
},
angular: {
expr: /\bangular(?:js)?\b/gi,
replacement: "AngularJS",
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')
windowsXP: {
.replace(/me/i, ' ME')
expr: /(win|windows(?:\ ?)(\sxp))\b(\S|)(?!\S)/igm,
.replace(/nt/i, ' NT')
replacement: "Windows XP$3",
.replace(/xp/i, ' XP')
reason: "Windows XP should be capitalized"
.replace(/2k/i, ' 2000')
},
.replace(/vista/i, ' Vista')
windowsVista: {
.replace(/server/i, ' Server');
expr: /(win|windows(?:\ ?)(\svista))\b(\S|)(?!\S)/igm,
return 'Windows' + ver;
replacement: "Windows Vista$3",
},
reason: "Windows Vista should be capitalized"
reason: "trademark capitalization"
},
ubuntu: {
expr: /(ubunto|ubunut|ubunutu|ubunu|ubntu|ubutnu|ubanto[o]+|unbuntu|ubunt|ubutu)\b(\S|)(?!\S)/igm,
replacement: "Ubuntu$2",
reason: "corrected Ubuntu spelling"
},
},
linux: {
linux: {
expr: /(linux)\b(\S|)(?!\S)/igm,
expr: /\blinux\b/gi,
replacement: "Linux$2",
replacement: "Linux",
reason: "Linux should be capitalized"
reason: "trademark capitalization"
},
apostrophes: {
expr: /(^|\s)(can|doesn|don|won|hasn|isn|didn)t(\s|$)/gmi,
replacement: "$1$2't$3",
reason: "English contractions use apostrophes"
},
ios: {
expr: /\b(?:ios|iOs|ioS|IOS|Ios|IoS|ioS)\b(\S|)(?!\S)/gm,
replacement: "iOS$1",
reason: "the proper usage is 'iOS'"
},
iosnum: {
expr: /\b(?:ios|iOs|ioS|IOS|Ios|IoS|ioS)([0-9]?)\b(\S|)(?!\S)/gm,
replacement: "iOS $1$2",
reason: "the proper usage is 'iOS' followed by a space and the version number"
},
caps: {
expr: /^((?=.*[A-Z])[^a-z]*)$/g,
replacement: "$1",
reason: "no need to yell"
},
},
wordpress: {
wordpress: {
expr: /[Ww]ordpress/g,
expr: /\bwordpress\b/gi,
replacement: "WordPress",
replacement: "WordPress",
reason: "'WordPress' is the proper capitalization"
reason: "trademark capitalization"
},
},
google: {
google: {
expr: /(google)\b(\S|)(?!\S)/igm,
expr: /\bgoogle\b/gi,
replacement: "Google$2",
replacement: "Google",
reason: "Google is the proper capitalization"
reason: "trademark capitalization"
},
},
mysql: {
mysql: {
expr: /(mysql)\b(\S|)(?!\S)/igm,
expr: /\bmysql\b/gi,
replacement: "MySQL$2",
replacement: "MySQL",
reason: "MySQL is the proper capitalization"
reason: "trademark capitalization"
},
},
apache: {
apache: {
expr: /(apache)\b(\S|)(?!\S)/igm,
expr: /\bapache\b/gi,
replacement: "Apache$2",
replacement: "Apache",
reason: "Apache is the proper capitalization"
reason: "trademark capitalization"
},
},
git: {
git: {
expr: /(^|\s)(git|GIT)\b(\S|)(?!\S)/gm,
expr: /\bgit\b/gi,
replacement: "$1Git$3",
replacement: "Git",
reason: "Git is the proper capitalization"
reason: "trademark capitalization"
},
harddisk: {
expr: /(hdd|harddisk)\b(\S|)(?!\S)/igm,
replacement: "hard disk$2",
reason: "Hard disk is the proper capitalization"
},
},
github: {
github: {
expr: /\b([gG]ithub|GITHUB)\b(\S|)(?!\S)/gm,
expr: /\bgithub\b/gi,
replacement: "GitHub$2",
replacement: "GitHub",
reason: "GitHub is the proper capitalization"
reason: "trademark capitalization"
},
facebook: {
expr: /\bfacebook\b/gi,
replacement: "Facebook",
reason: "trademark capitalization"
},
python: {
expr: /\bpython\b/gi,
replacement: "Python",
reason: "trademark capitalization"
},
urli: {
expr: /\b(ur[li])\b/gi,
replacement: function(match) {
return match.toUpperCase();
},
reason: "acronym capitalization"
},
ios: {
expr: /\bios\b/gi,
replacement: "iOS",
reason: "trademark capitalization"
},
iosnum: {
expr: /\bios([0-9])\b/gi,
replacement: "iOS $1",
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"
},
badphrases: {
expr: /[^\n.!?:]*(?:h[ea]lp|hope|appreciate|pl(?:ease|z|s))[^.!?\n]*(?:helps?|appreciated?)[^,.!?\n]*[,.!?]*/gi,
replacement: "",
reason: "noise reduction"
},
imnew: {
expr: /(?! )[\w\s]*\bi[' ]?a?m +(?:kinda|really) *new\w* +(?:to|in) *\w* *(?:and|[;,.!?])? */gi,
replacement: "",
reason: "noise reduction"
},
salutations: {
expr: /[\r\n]*(regards|cheers?),?[\t\f ]*[\r\n]?\w*\.?/gi,
replacement: "",
reason: "noise reduction"
},
// Grammar and spelling
apostrophe_d: {
expr: /\b(he|she|who|you)[^\w']*(d)\b/gi,
replacement: "$1'$2",
reason: "grammar and spelling"
},
apostrophe_ll: {
expr: /\b(they|what|who|you)[^\w']*(ll)\b/gi,
replacement: "$1'$2",
reason: "grammar and spelling"
},
apostrophe_re: {
expr: /\b(they|what|you)[^\w']*(re)\b/gi,
replacement: "$1'$2",
reason: "grammar and spelling"
},
apostrophe_s: {
expr: /\b(he|she|that|there|what|where)[^\w']*(s)\b/gi,
replacement: "$1'$2",
reason: "grammar and spelling"
},
apostrophe_t: {
expr: /\b(aren|can|didn|doesn|don|hasn|haven|isn|mightn|mustn|shan|shouldn|won|wouldn)[^\w']*(t)\b/gi,
replacement: "$1'$2",
reason: "grammar and spelling"
},
prolly: {
expr: /\bproll?y\b/gi,
replacement: "probably",
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 (type === '+') tr.addClass('add');
if (e.metaKey || e.ctrlKey) {
if (type === '-') tr.addClass('del');
// CTRL/CMD + Enter -> Activate the auto-editor

App.selections.buttonFix.click();
tr.append($('<td class="codekolom">' + y + '</td>'));
this.focus();
tr.append($('<td class="codekolom">' + x + '</td>'));
} else {
tr.append($('<td class="bredecode">' + type + ' ' + rij.replace(/\</g, '&lt;') + '</td>'));
// It's impossible to remove the event listeners, so we have to clone the element without any listeners

var elClone = this.cloneNode(true);
App.selections.diffTable.append(tr);
this.parentNode.replaceChild(elClone,
}
this);

App.selections.submitButton.click();
function getDiff(matrix, a1, a2, x, y) {
}
if (x > 0 && y > 0 && a1[y - 1] === a2[x - 1]) {
getDiff(matrix, a1, a2, x - 1, y - 1);
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;
}
}
}
}


// Tags box
}
App.selections.tagField.keydown(removeEventListeners);

// Edit summary box
App.selections.summaryBox.keydown(removeEventListeners);
};


// Wait for relevant dynamic content to finish loading
App.funcs.dynamicDelay = function(callback, id, inline) {
var a1 = App.originals.body.split('\n');
if (inline) { // Inline editing
var a2 = App.items.body.split('\n');
setTimeout(function() {
App.selections.buttonBar = $('#wmd-button-bar-' + id);
var matrix = new Array(a1.length + 1);
App.selections.buttonBar.unbind();
var x, y;
setTimeout(function() {
for (y = 0; y < matrix.length; y++) {
callback();
matrix[y] = new Array(a2.length + 1);
}, 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
for (x = 0; x < matrix[y].length; x++) {
setTimeout(function() {
matrix[y][x] = 0;
callback();
}, 0);
}
});
}
}
};
}

// 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() {
App.items[0] = {
title: App.selections.titleBox.val(),
body: App.selections.bodyBox.val(),
summary: ''
};
};

// Insert editing button(s)
App.funcs.createButton = function() {
// Insert button
App.selections.redoButton.after(App.globals.buttonHTML);

// Insert spacer
App.selections.redoButton.after(App.globals.spacerHTML);

// Add new elements to selections
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() {
for (y = 1; y < matrix.length; y++) {
App.globals.infoContent = App.selections.buttonInfo.text();
for (x = 1; x < matrix[y].length; x++) {
App.selections.buttonInfo.text('Fix the content!');
if (a1[y - 1] === a2[x - 1]) {
App.selections.buttonFix.css({
matrix[y][x] = 1 + ma
'background-image': 'url("//i.imgur.com/d5ZL09o.png")'
});
}, function() {
App.selections.buttonInfo.text(App.globals.infoContent);
App.s