Untitled diff

Created Diff never expires
632 removals
Lines
Total
Removed
Words
Total
Removed
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
629 lines
692 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==
var main = function() {
// Define app namespace
var App = {};

// Place edit items here
App.items = [];

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


// Place "global" app data here
(function() {
App.globals = {};
"use strict";
function extendEditor(root) {
var App = {};


// Place "helper" functions here
// Place edit items here
App.funcs = {};
App.items = {};
App.originals = {};


//Preload icon alt
// Place selected jQuery items here
var SEETicon = new Image();
App.selections = {};


SEETicon.src = '//i.imgur.com/d5ZL09o.png';
// Place "global" app data here
App.globals = {};


// Populate global data
// Place "helper" functions here
// Get url for question id used in id and class names
App.funcs = {};
App.globals.URL = window.location.href;
// True to display rule names in Edit Summary
App.globals.showRules = false;


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


// Define variables for later use
SEETicon.src = '//i.imgur.com/d5ZL09o.png';
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.root = root;
App.globals.buttonHTML = '<div id="ToolkitButtonWrapper"><button class="wmd-button" id="ToolkitFix"></button><div id="ToolkitInfo"></div></div>';


App.globals.reasons = [];
App.globals.spacerHTML = '<li class="wmd-spacer wmd-spacer3" id="wmd-spacer3" style="left: 400px !important;"></li>';
App.globals.numReasons = 0;


App.globals.replacedStrings = {
App.globals.reasons = {};
"block": [],
"inline": []
};
App.globals.placeHolders = {
"block": "_xCodexBlockxPlacexHolderx_",
"inline": "_xCodexInlinexPlacexHolderx_"
};
App.globals.checks = {
"block": /( )+.*/gm,
"inline": /`.*`/gm
};


// Assign modules here
App.globals.replacedStrings = {
App.globals.pipeMods = {};
"auto": [],
"quote": [],
"inline": [],
"block": [],
"links": [],
"tags": []
};
App.globals.placeHolders = {
"auto": "_xAutoxInsertxTextxPlacexHolder_",
"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 = {
// https://regex101.com/r/cI6oK2/1 automatically inserted text
"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
};


// Define order in which mods affect here
// Assign modules here
App.globals.order = ["omit", "edit", "replace"];
App.globals.pipeMods = {};


// Define order in which mods affect here
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();
so: {
},
expr: /(^|\s)[Ss]tack\s*overflow|StackOverflow(.|$)/gm,
reason: 'no need to yell'
replacement: "$1Stack Overflow$2",
},
reason: "'Stack Overflow' is the legal name"
// Trademark capitalization
},
so: {
se: {
expr: /\bstack\s*overflow\b/gi,
expr: /(^|\s)[Ss]tack\s*exchange|StackExchange(.|$)/gm,
replacement: "Stack Overflow",
replacement: "$1Stack Exchange$2",
reason: "'Stack Overflow' is the legal name"
reason: "'Stack Exchange' is the legal name"
},
},
se: {
expansionSO: {
expr: /\bstack\s*exchange\b/gi,
expr: /(^|\s)SO(\s|,|\.|!|\?|;|\/|\)|$)/gm,
replacement: "Stack Exchange",
replacement: "$1Stack Overflow$2",
reason: "'Stack Exchange' is the legal name"
reason: "'SO' expansion"
},
},
expansionSO: {
expansionSE: {
expr: /([^\b\w.]|^)SO\b/g,
expr: /(^|\s)SE(\s|,|\.|!|\?|;|\/|\)|$)/gm,
replacement: "$1Stack Overflow",
replacement: "$1Stack Exchange$2",
reason: "'Stack Overflow' is the legal name"
reason: "'SE' expansion"
},
},
expansionSE: {
javascript: {
expr: /([^\b\w.]|^)SE\b/g,
expr: /(^|\s)[Jj]ava\s*[Ss]cript(.|$)/gm,
replacement: "$1Stack Exchange",
replacement: "$1JavaScript$2",
reason: "'Stack Exchange' is the legal name"
reason: "'JavaScript' is the proper capitalization"
},
},
javascript: {
jsfiddle: {
expr: /([^\b\w.]|^)(javascript|js)\b/gi,
expr: /(^|\s)[Jj][Ss]\s*[Ff]iddle(.|$)/gm,
replacement: "$1JavaScript",
replacement: "$1JSFiddle$2",
reason: "trademark capitalization"
reason: "'JSFiddle' is the currently accepted capitalization"
},
},
jsfiddle: {
caps: {
expr: /\bjsfiddle\b/gi,
expr: /^(?!https?)([a-z])/gm,
replacement: "JSFiddle",
replacement: "$1",
reason: "trademark capitalization"
reason: "copy edited"
},
},
jquery: {
jquery: {
expr: /\bjquery\b/gi,
expr: /(^|\s)[Jj][Qq]uery(.|$)/gm,
replacement: "jQuery",
replacement: "$1jQuery$2",
reason: "trademark capitalization"
reason: "'jQuery' is the proper capitalization"
},
},
angular: {
html: {
expr: /\bangular(?:js)?\b/gi,
expr: /(^|\s)[Hh]tml([5]?)\b(\S|)(?!\S)/gm,
replacement: "AngularJS",
replacement: "$1HTML$2$3",
reason: "trademark capitalization"
reason: "HTML stands for HyperText Markup Language"
},
},
html: {
css: {
expr: /([^\b\w.]|^)html(\d)?\b/gi,
expr: /(^|\s)[Cc]ss\b(\S|)(?!\S)/gm,
replacement: "$1HTML$2",
replacement: "$1CSS$2",
reason: "trademark capitalization"
reason: "CSS stands for Cascading Style Sheets"
},
},
css: {
json: {
expr: /([^\b\w.]|^)css\b/gi,
expr: /(^|\s)[Jj]son\b(\S|)(?!\S)/gm,
replacement: "$1CSS",
replacement: "$1JSON$2",
reason: "trademark capitalization"
reason: "JSON stands for JavaScript Object Notation"
},
},
json: {
ajax: {
expr: /\bjson\b/gi,
expr: /(^|\s)ajax\b(\S|)(?!\S)/gm,
replacement: "JSON",
replacement: "$1AJAX$2",
reason: "acronym capitalization"
reason: "AJAX stands for Asynchronous JavaScript and XML"
},
},
ajax: {
angular: {
expr: /\bajax\b/gi,
expr: /[Aa]ngular[Jj][Ss]/g,
replacement: "AJAX",
replacement: "AngularJS",
reason: "acronym capitalization"
reason: "'AngularJS is the proper capitalization"
},
},
php: {
thanks: {
expr: /([^\b\w.]|^)php\b/gi,
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: "$1PHP",
replacement: "",
reason: "trademark capitalization"
reason: "'$1' is unnecessary noise"
},
},
voting: {
commas: {
expr: /\b(down|up)\Wvot/gi,
expr: /,([^\s])/g,
replacement: "$1vote",
replacement: ", $1",
reason: "the proper spelling (despite the tag name) is '$1vote' (one word)"
reason: "punctuation & spacing"
},
},
c: {
php: {
expr: /\bc\b([#+]+)?/gi,
expr: /(^|\s)[Pp]hp\b(\S|)(?!\S)/gm,
replacement: "C$1",
replacement: "$1PHP$2",
reason: "trademark capitalization"
reason: "PHP stands for PHP: Hypertext Preprocessor"
},
},
java: {
hello: {
expr: /\bjava\b/gi,
expr: /(?:^|\s)(hi\s+guys|hi|hello|good\s(?:evening|morning|day|afternoon))(?:\.|!|\ )/gmi,
replacement: "Java",
replacement: "",
reason: "trademark capitalization"
reason: "greetings like '$1' are unnecessary noise"
},
},
sql: {
edit: {
expr: /([^\b\w.]|^)sql\b/gi,
expr: /(?:^\**)(edit|update):?(?:\**):?/gmi,
replacement: "$1SQL",
replacement: "",
reason: "trademark capitalization"
reason: "Stack Exchange has an advanced revision history system: 'Edit' or 'Update' is unnecessary"
},
},
sqlite: {
voting: {
expr: /\bsqlite\s*([0-9]*)\b/gi,
expr: /([Dd]own|[Uu]p)[\s*\-]vot/g,
replacement: "SQLite $2",
replacement: "$1vote",
reason: "trademark capitalization"
reason: "the proper spelling (despite the tag name) is '$1vote' (one word)"
},
},
android: {
mysite: {
expr: /\bandroid\b/gi,
expr: /mysite\./g,
replacement: "Android",
replacement: "example.",
reason: "trademark capitalization"
reason: "links to mysite.domain are not allowed: use example.domain instead"
},
},
oracle: {
c: {
expr: /\boracle\b/gi,
expr: /(^|\s)c(#|\++|\s|$)/gm,
replacement: "Oracle",
replacement: "$1C$2",
reason: "trademark capitalization"
reason: "C$2 is the proper capitalization"
},
},
windows: {
java: {
// https://regex101.com/r/jF9zK1/5
expr: /(^|\s)java\b(\S|)(?!\S)/gmi,
expr: /\b(?:win|windows)\s+(2k|[0-9.]+|ce|me|nt|xp|vista|server)|(?:win|windows)\b/gi,
replacement: "$1Java$2",
replacement: function(match, ver) {
reason: "Java should be capitalized"
ver = !ver ? '' : ver.replace(/ce/i, ' CE')
},
.replace(/me/i, ' ME')
sql: {
.replace(/nt/i, ' NT')
expr: /(^|\s)[Ss]ql\b(\S|)(?!\S)/gm,
.replace(/xp/i, ' XP')
replacement: "$1SQL$2",
.replace(/2k/i, ' 2000')
reason: "SQL is the proper capitalization"
.replace(/vista/i, ' Vista')
},
.replace(/server/i, ' Server');
sqlite: {
return 'Windows' + ver;
expr: /(^|\s)[Ss]qlite([0-9]*)\b(\S|)(?!\S)/gm,
},
replacement: "$1SQLite$2$3",
reason: "trademark capitalization"
reason: "SQLite is the proper capitalization"
},
},
linux: {
android: {
expr: /\blinux\b/gi,
expr: /(^|\s)android\b(\S|)(?!\S)/gmi,
replacement: "Linux",
replacement: "$1Android$2",
reason: "trademark capitalization"
reason: "Android should be capitalized"
},
},
wordpress: {
oracle: {
expr: /\bwordpress\b/gi,
expr: /(^|\s)oracle\b(\S|)(?!\S)/gmi,
replacement: "WordPress",
replacement: "$1Oracle$2",
reason: "trademark capitalization"
reason: "Oracle should be capitalized"
},
},
google: {
windows: {
expr: /\bgoogle\b/gi,
expr: /(win|windows(?:\ ?)(\s[0-9]+))\b(\S|)(?!\S)/igm,
replacement: "Google",
replacement: "Windows$2$3",
reason: "trademark capitalization"
reason: "Windows should be capitalized"
},
},
mysql: {
windowsXP: {
expr: /\bmysql\b/gi,
expr: /(win|windows(?:\ ?)(\sxp))\b(\S|)(?!\S)/igm,
replacement: "MySQL",
replacement: "Windows XP$3",
reason: "trademark capitalization"
reason: "Windows XP should be capitalized"
},
},
apache: {
windowsVista: {
expr: /\bapache\b/gi,
expr: /(win|windows(?:\ ?)(\svista))\b(\S|)(?!\S)/igm,
replacement: "Apache",
replacement: "Windows Vista$3",
reason: "trademark capitalization"
reason: "Windows Vista should be capitalized"
},
},
git: {
ubuntu: {
expr: /\bgit\b/gi,
expr: /(ubunto|ubunut|ubunutu|ubunu|ubntu|ubutnu|ubanto[o]+|unbuntu|ubunt|ubutu)\b(\S|)(?!\S)/igm,
replacement: "Git",
replacement: "Ubuntu$2",
reason: "trademark capitalization"
reason: "corrected Ubuntu spelling"
},
},
github: {
linux: {
expr: /\bgithub\b/gi,
expr: /(linux)\b(\S|)(?!\S)/igm,
replacement: "GitHub",
replacement: "Linux$2",
reason: "trademark capitalization"
reason: "Linux should be capitalized"
},
},
facebook: {
apostrophes: {
expr: /\bfacebook\b/gi,
expr: /(^|\s)(can|doesn|don|won|hasn|isn|didn)t(\s|$)/gmi,
replacement: "Facebook",
replacement: "$1$2't$3",
reason: "trademark capitalization"
reason: "English contractions use apostrophes"
},
},
python: {
ios: {
expr: /\bpython\b/gi,
expr: /\b(?:ios|iOs|ioS|IOS|Ios|IoS|ioS)\b(\S|)(?!\S)/gm,
replacement: "Python",
replacement: "iOS$1",
reason: "trademark capitalization"
reason: "the proper usage is 'iOS'"
},
},
urli: {
iosnum: {
expr: /\b(ur[li])\b/gi,
expr: /\b(?:ios|iOs|ioS|IOS|Ios|IoS|ioS)([0-9]?)\b(\S|)(?!\S)/gm,
replacement: function(match) {
replacement: "iOS $1$2",
return match.toUpperCase();
reason: "the proper usage is 'iOS' followed by a space and the version number"
},
},
reason: "acronym capitalization"
caps: {
},
expr: /^((?=.*[A-Z])[^a-z]*)$/g,
ios: {
replacement: "$1",
expr: /\bios\b/gi,
reason: "no need to yell"
replacement: "iOS",
},
reason: "trademark capitalization"
wordpress: {
},
expr: /[Ww]ordpress/g,
iosnum: {
replacement: "WordPress",
expr: /\bios([0-9])\b/gi,
reason: "'WordPress' is the proper capitalization"
replacement: "iOS $1",
},
reason: "trademark capitalization"
google: {
},
expr: /(google)\b(\S|)(?!\S)/igm,
ubunto: {
replacement: "Google$2",
expr: /\b[uoa]*b[uoa]*[tn][oua]*[tnu][oua]*\b/gi,
reason: "Google is the proper capitalization"
replacement: "Ubuntu",
},
reason: "trademark capitalization"
mysql: {
},
expr: /(mysql)\b(\S|)(?!\S)/igm,
vbnet: {
replacement: "MySQL$2",
expr: /(?:vb)?(?:\.net|\s?[0-9]+)\s?(?:framework|core)?/gi,
reason: "MySQL is the proper capitalization"
replacement: function(str) {
},
return str.replace(/vb/i, 'VB')
apache: {
.replace(/net/i, 'NET')
expr: /(apache)\b(\S|)(?!\S)/igm,
.replace(/framework/i, 'Framework')
replacement: "Apache$2",
.replace(/core/i, 'Core');
reason: "Apache is the proper capitalization"
},
},
reason: "trademark capitalization"
git: {
},
expr: /(^|\s)(git|GIT)\b(\S|)(?!\S)/gm,
regex: {
replacement: "$1Git$3",
expr: /\bregex(p)?/gi,
reason: "Git is the proper capitalization"
replacement: "RegEx$1",
},
reason: "trademark capitalization"
harddisk: {
},
expr: /(hdd|harddisk)\b(\S|)(?!\S)/igm,
// Noise reduction
replacement: "hard disk$2",
editupdate: {
reason: "Hard disk is the proper capitalization"
// https://regex101.com/r/tT2pK6/2
},
expr: /(?!(?:edit|update)\w*\s*[^:]*$)(?:^\**)(edit|update)\w*(\s*#?[0-9]+)?:?(?:\**):?/gmi,
github: {
replacement: "",
expr: /\b([gG]ithub|GITHUB)\b(\S|)(?!\S)/gm,
reason: "noise reduction"
replacement: "GitHub$2",
},
reason: "GitHub is the proper capitalization"
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
App.popFuncs = function() {
// This is where the magic happens: this function takes a few pieces of information and applies edits to the post with a couple exceptions
// 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.funcs.fixIt = function(input, expression, replacement, reasoning) {
App.funcs.fixIt = function(input, expression, replacement, reasoning) {
// If there is nothing to search, exit
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;
// Later, this will store what is removed for the first case
input = input.replace(expression, function() {
var phrase;
var matches = [].slice.call(arguments, 0, -2);

reasoning = reasoning.replace(/[$](\d)+/g, function() {
// Store the original input text
var phrases = [].slice.call(arguments, 0, -2);
var originalInput = input;
var phrase = matches[phrases[1]];

return phrase ? phrase : '';
// Then, perform the edits using replace()
});
// What follows is a series of exceptions, which I will explain below; I perform special actions by overriding replace()
return arguments[0].replace(expression, replacement);
// 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
return "";
});

// This is an interesting tidbit: if you want to make the edit summaries dynamic, you can keep track of a match that you receive
// from overriding the replace() function and then use that in the summary
reasoning = reasoning.replace("$1", phrase);

// This allows me to combine the upvote and downvote replacement schemes into one
} else if (replacement == "$1vote") {
input = input.replace(expression, function(data, match1) {
phrase = match1;
return phrase + "vot";
});
reasoning = reasoning.replace("$1", phrase.toLowerCase());

// Fix all caps
} else if (reasoning === "no need to yell") {
input = input.replace(expression, function(data, match1) {
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
} else if (replacement === "$1C$2") {
var newPhrase;
input = input.replace(expression, function(data, match1, match2) {
newPhrase = match2;
return match1 + "C" + match2;
});
reasoning = reasoning.replace("$2", newPhrase);

// iOS numbering/spacing fixes
} else if (replacement === "iOS $2") {
input = input.replace(expression, function(data, match1) {
if (match1.match(/\d/)) { // Is a number
return "iOS " + match1;
}

return "iOS" + match1;
});

// Default: just replace it with the indicated replacement
} else {
input = input.replace(expression, replacement);
}

// Check whether anything was changed
if (input === originalInput) {
return null;
} else {
// Return a dictionary with the reasoning for the fix and what is edited (used later to prevent duplicates in the edit summary)
return {
reason: reasoning,
fixed: input
};
}
} else {
// If nothing needs to be fixed, return null
return null;
}
};

// Omit code
App.funcs.omitCode = function(str, type) {
str = str.replace(App.globals.checks[type], function(match) {
App.globals.replacedStrings[type].push(match);
return App.globals.placeHolders[type];
});
});
return str;
if (input !== tmpinput) {
};
return {

reason: reasoning,
// Replace code
fixed: String(input).trim(),
App.funcs.replaceCode = function(str, type) {
count: count
for (var i = 0; i < App.globals.replacedStrings[type].length; i++) {
};
str = str.replace(App.globals.placeHolders[type],
} else return false;
App.globals.replacedStrings[type][i]);
}
return str;
};

// Eliminate duplicates in array (awesome method I found on SO, check it out!)
// From AstroCB: the original structure of the edit formation prevents duplicates.
// 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++) {
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
App.funcs.applyListeners = function() { // Removes default Stack Exchange listeners; see https://github.com/AstroCB/Stack-Exchange-Editor-Toolkit/issues/43
function removeEventListeners(e) {
function removeEventListeners(e) {
if (e.which === 13) {
if (e.which === 13) {
if (e.metaKey || e.ctrlKey) {
if (e.metaKey || e.ctrlKey) {
// CTRL/CMD + Enter -> Activate the auto-editor
// CTRL/CMD + Enter -> Activate the auto-editor
App.selections.buttonFix.click();
App.selections.buttonFix.click();
this.focus();
} else {
} else {
// It's impossible to remove the event listeners, so we have to clone the element without any listeners
// It's possible to remove the event listeners, because of the way outerHTML works.
var elClone = this.cloneNode(true);
this.outerHTML = this.outerHTML;
this.parentNode.replaceChild(elClone,
this);
App.selections.submitButton.click();
App.selections.submitButton.click();
}
}
}
}
}
}


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


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

// Wait for relevant dynamic content to finish loading
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
setTimeout(function() {
callback();
}, 0);
}
});
}
};
};


// Populate or refresh DOM selections
// Populate or refresh DOM selections
App.funcs.popSelections = function() {
App.funcs.popSelections = function() {
App.selections.redoButton = $('#wmd-redo-button-' + App.globals.questionNum);
App.selections.redoButton = App.globals.root.find('[id^="wmd-redo-button"]');
App.selections.bodyBox = $("#wmd-input-" + App.globals.questionNum);
App.selections.body = App.globals.root.find('[id^="wmd-input"]');
App.selections.titleBox = $(".ask-title-field");
App.selections.title = App.globals.root.find('[class*="title-field"]');
App.selections.summaryBox = $("#edit-comment-" + App.globals.questionNum);
App.selections.summary = App.globals.root.find('[id^="edit-comment"]');
App.selections.tagField = $($(".tag-editor")[0]);
App.selections.tagField = App.globals.root.find(".tag-editor");
App.selections.submitButton = $("#submit-button-" + App.globals.questionNum);
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');
};
};


// Populate edit item sets from DOM selections
// Populate edit item sets from DOM selections
App.funcs.popItems = function() {
App.funcs.popItems = function() {
App.items[0] = {
var i = App.items, s = App.selections;
title: App.selections.titleBox.val(),
['title', 'body', 'summary'].forEach(function(v) {
body: App.selections.bodyBox.val(),
i[v] = String(s[v].val()).trim();
summary: ''
});
};
};

// Populate original item sets from DOM selections
App.funcs.popOriginals = function() {
var i = App.originals, s = App.selections;
['title', 'body', 'summary'].forEach(function(v) {
i[v] = String(s[v].val()).trim();
});
};
};


// Insert editing button(s)
// Insert editing button(s)
App.funcs.createButton = function() {
App.funcs.createButton = function() {
if (!App.selections.redoButton.length) return false;

App.selections.buttonWrapper = $('<div class="ToolkitButtonWrapper"/>');
App.selections.buttonFix = $('<button class="wmd-button ToolkitFix" title="Fix the content!" />');
App.selections.buttonInfo = $('<div class="ToolkitInfo">');

// Build the button
App.selections.buttonWrapper.append(App.selections.buttonFix);
App.selections.buttonWrapper.append(App.selections.buttonInfo);

// Insert button
// Insert button
App.selections.redoButton.after(App.globals.buttonHTML);
App.selections.redoButton.after(App.selections.buttonWrapper);

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


// Add new elements to selections
// Attach the event listener to the button
App.selections.buttonWrapper = $('#ToolkitButtonWrapper');
App.selections.buttonFix.click(App.funcs.fixEvent);
App.selections.buttonFix = $('#ToolkitFix');
App.selections.buttonInfo = $('#ToolkitInfo');
};


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

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


// Listen to button click
App.funcs.makeDiffTable = function() {
App.funcs.listenButton = function() {
App.selections.diffTable = $('<table class="diffTable"/>');
App.selections.buttonFix.click(function(e) {
App.selections.editor.append(App.selections.diffTable);
e.preventDefault();
};
if (!App.globals.editsMade) {
// Refresh item population
App.funcs.popItems();


// Pipe data through editing modules
App.funcs.fixEvent = function(e) {
App.pipe(App.items, App.globals.pipeMods, App.globals.order);
if (e) e.preventDefault();
App.globals.editsMade = true;
// Refresh item population
}
App.funcs.popOriginals();
});
App.funcs.popItems();
// Pipe data through editing modules
App.pipe(App.items, App.globals.pipeMods, App.globals.order);
};
};


// Figure out the last selected element before pressing the button so we can return there after focusing the summary field
App.funcs.diff = function() {
App.funcs.setLastFocus = function() {
App.selections.diffTable.empty();
App.selections.titleBox.click(function() {
App.globals.lastSelectedElement = $(this);
});


App.selections.bodyBox.click(function() {
function maakRij(x, y, type, rij) {
App.globals.lastSelectedElement = $(this);
});


App.selections.summaryBox.click(function() {
var tr = $('<tr/>');
App.globals.lastSelectedElement = $(this);
});


App.selections.tagField.click(function() {
if (type === '+') tr.addClass('add');
App.globals.lastSelectedElement = $(this);
if (type === '-') tr.addClass('del');
});
};


// Handle pipe output
tr.append($('<td class="codekolom">' + y + '</td>'));
App.funcs.output = function(data) {
tr.append($('<td class="codekolom">' + x + '</td>'));
App.selections.titleBox.val(data[0].title);
tr.append($('<td class="bredecode">' + type + ' ' + rij.replace(/\</g, '&lt;') + '</td>'));
App.selections.bodyBox.val(data[0].body);


if (App.selections.summaryBox.val()) {
App.selections.diffTable.append(tr);
data[0].summary = " " + data[0].summary; // Add a leading space if there's something already in the box
}
}
App.selections.summaryBox.val(App.selections.summaryBox.val() + data[0].summary);


// Update the comment: focusing on the input field to remove placeholder text, but scroll back to the user's original location
function getDiff(matrix, a1, a2, x, y) {
App.globals.currentPos = document.body.scroll
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;
}
}

}

var a1 = App.originals.body.split('\n');
var a2 = App.items.body.split('\n');
var matrix = new Array(a1.length + 1);
var x, y;
for (y = 0; y < matrix.length; y++) {
matrix[y] = new Array(a2.length + 1);

for (x = 0; x < matrix[y].length; x++) {
matrix[y][x] = 0;
}
}

for (y = 1; y < matrix.length; y++) {
for (x = 1; x < matrix[y].length; x++) {
if (a1[y - 1] === a2[x - 1]) {
matrix[y][x] = 1 + ma