Diff
checker
텍스트
텍스트
이미지
문서
Excel
폴더
Legal
Enterprise
데스크톱
요금제
로그인
데스크톱 앱 다운로드
텍스트 비교
두 텍스트 파일의 차이점을 찾아보세요
도구
기록
실시간 편집
변경 없는 행 숨기기
줄바꿈 비활성화
레이아웃
나란히 보기
합쳐 보기
비교 단위
스마트
단어
글자
구문 강조
언어 선택
제외
텍스트 변환
첫 변경으로
수정
Diffchecker Desktop
가장 안전하게 Diffchecker를 사용하는 방법. 데스크톱 앱을 사용하면 비교 데이터가 외부로 전송되지 않습니다!
데스크톱 앱 받기
Untitled diff
생성일
11년 전
비교 결과 만료 없음
초기화
내보내기
공유
설명
594 삭제
행
총
삭제
글자
총
삭제
이 기능을 계속 사용하려면 업그레이드해 주세요
Diff
checker
Pro
요금제 보기
629 행
복사
666 추가
행
총
추가
글자
총
추가
이 기능을 계속 사용하려면 업그레이드해 주세요
Diff
checker
Pro
요금제 보기
633 행
복사
// ==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
mod
s affect
here
//
Assign
mod
ules
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: "
$1
Stack 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: "
$1
Stack Exchange
$2
",
reason: "'Stack Overflow' is the legal name"
reason: "'Stack Exchange' is the legal name"
},
},
se: {
expansionSO: {
expr: /
\bs
tack\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.]|^)(j
ava
s
cript
|js)\b/gi,
expr: /
(^|\s)[Jj][Ss]\s*[Ff]iddle(.|$)/gm,
replacement: "$1JavaScript
",
replacement: "
$1
JSFiddle
$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.]|^)h
tml(
\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.]|^)c
ss\b
/gi,
expr: /
(^|\s)[Jj]
son\b
(\S|)(?!\S)/gm,
replacement: "$1CSS
",
replacement: "
$1
JSON
$2
",
reason: "
trademark capitalization"
reason: "
JSON stands for JavaScript Object Notation"
},
},
json: {
ajax: {
expr: /
\bj
son\b
/gi,
expr: /
(^|\s)
ajax\b
(\S|)(?!\S)/gm,
replacement: "
JSON
",
replacement: "
$1
AJAX
$2
",
reason: "
acronym capitalization"
reason: "
AJAX stands for Asynchronous JavaScript and XML"
},
},
ajax: {
angular
: {
expr: /
\b
ajax\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):?
(?:
\**):?/gm
i,
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)/gm
i,
expr: /
\b
(?:
win|windows)\s+(2k|[0-9.]+|ce|me|nt|xp|vista|server)|
(?:
win|windows)\b/g
i,
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)/gm
i,
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)/gm
i,
replacement: "WordPress",
replacement: "
$1Oracle$2
",
reason: "trademark capitalization"
reason: "
Oracle should be
capitaliz
ed"
},
},
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
: {
apostrophe
s
: {
expr: /
\bfacebook\b/gi,
expr: /
(^|\s)(can|doesn|don|won|hasn|isn|didn)t(\s|$)/gm
i,
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/g
i,
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)?/g
i,
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)?/g
i,
reason: "Git is the proper capitalization"
replacement: "
RegEx$1
",
},
reason: "
trademark
capitaliz
ation"
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
)/g
m,
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?|kind(?: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/g
i,
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 match
es
= 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
im
possible 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.summary
Box
.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.body
Box = $("#wmd-input-" +
App.globals.
questionNum
);
App.selections.body
=
App.globals.
root.find('[id^="wmd-input"]'
);
App.selections.title
Box
=
$(".ask-
title-field"
);
App.selections.title
=
App.globals.root.find('[class*="
title-field"
]'
);
App.selections.summary
Box = $("#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.help
Button
.css(
{
App.
funcs.style
Button
= 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, '<') + '</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
저장된 비교 결과
원본
파일 열기
// ==UserScript== // @name Stack-Exchange-Editor-Toolkit // @author Cameron Bernhardt (AstroCB) // @developer Jonathan Todd (jt0dd) // @developer sathyabhat // @contributor Unihedron // @license MIT // @namespace http://github.com/AstroCB // @version 1.5.1 // @description Fix common grammar/usage annoyances on Stack Exchange posts with a click // @include *://*.stackexchange.com/questions/* // @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== 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 App.globals = {}; // Place "helper" functions here App.funcs = {}; //Preload icon alt var SEETicon = new Image(); SEETicon.src = '//i.imgur.com/d5ZL09o.png'; // Populate global data // 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.buttonHTML = '<div id="ToolkitButtonWrapper"><button class="wmd-button" id="ToolkitFix"></button><div id="ToolkitInfo"></div></div>'; App.globals.reasons = []; App.globals.numReasons = 0; App.globals.replacedStrings = { "block": [], "inline": [] }; App.globals.placeHolders = { "block": "_xCodexBlockxPlacexHolderx_", "inline": "_xCodexInlinexPlacexHolderx_" }; App.globals.checks = { "block": /( )+.*/gm, "inline": /`.*`/gm }; // Assign modules here App.globals.pipeMods = {}; // Define order in which mods affect here App.globals.order = ["omit", "edit", "replace"]; // Define edit rules App.edits = { i: { expr: /(^|\s|\()i(\s|,|\.|!|\?|;|\/|\)|'|$)/gm, replacement: "$1I$2", reason: "in English, the pronoun 'I' is capitalized" }, so: { expr: /(^|\s)[Ss]tack\s*overflow|StackOverflow(.|$)/gm, replacement: "$1Stack Overflow$2", reason: "'Stack Overflow' is the legal name" }, se: { expr: /(^|\s)[Ss]tack\s*exchange|StackExchange(.|$)/gm, replacement: "$1Stack Exchange$2", reason: "'Stack Exchange' is the legal name" }, expansionSO: { expr: /(^|\s)SO(\s|,|\.|!|\?|;|\/|\)|$)/gm, replacement: "$1Stack Overflow$2", reason: "'SO' expansion" }, expansionSE: { expr: /(^|\s)SE(\s|,|\.|!|\?|;|\/|\)|$)/gm, replacement: "$1Stack Exchange$2", reason: "'SE' expansion" }, javascript: { expr: /(^|\s)[Jj]ava\s*[Ss]cript(.|$)/gm, replacement: "$1JavaScript$2", reason: "'JavaScript' is the proper capitalization" }, jsfiddle: { expr: /(^|\s)[Jj][Ss]\s*[Ff]iddle(.|$)/gm, replacement: "$1JSFiddle$2", reason: "'JSFiddle' is the currently accepted capitalization" }, caps: { expr: /^(?!https?)([a-z])/gm, replacement: "$1", reason: "copy edited" }, jquery: { expr: /(^|\s)[Jj][Qq]uery(.|$)/gm, replacement: "$1jQuery$2", reason: "'jQuery' is the proper capitalization" }, html: { expr: /(^|\s)[Hh]tml([5]?)\b(\S|)(?!\S)/gm, replacement: "$1HTML$2$3", reason: "HTML stands for HyperText Markup Language" }, css: { expr: /(^|\s)[Cc]ss\b(\S|)(?!\S)/gm, replacement: "$1CSS$2", reason: "CSS stands for Cascading Style Sheets" }, json: { expr: /(^|\s)[Jj]son\b(\S|)(?!\S)/gm, replacement: "$1JSON$2", reason: "JSON stands for JavaScript Object Notation" }, ajax: { expr: /(^|\s)ajax\b(\S|)(?!\S)/gm, replacement: "$1AJAX$2", reason: "AJAX stands for Asynchronous JavaScript and XML" }, 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: { expr: /(^|\s)[Pp]hp\b(\S|)(?!\S)/gm, replacement: "$1PHP$2", reason: "PHP stands for PHP: Hypertext Preprocessor" }, 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: { expr: /([Dd]own|[Uu]p)[\s*\-]vot/g, replacement: "$1vote", 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: { expr: /(^|\s)c(#|\++|\s|$)/gm, replacement: "$1C$2", reason: "C$2 is the proper capitalization" }, java: { expr: /(^|\s)java\b(\S|)(?!\S)/gmi, replacement: "$1Java$2", reason: "Java should be capitalized" }, sql: { expr: /(^|\s)[Ss]ql\b(\S|)(?!\S)/gm, replacement: "$1SQL$2", reason: "SQL is the proper capitalization" }, sqlite: { expr: /(^|\s)[Ss]qlite([0-9]*)\b(\S|)(?!\S)/gm, replacement: "$1SQLite$2$3", reason: "SQLite is the proper capitalization" }, android: { expr: /(^|\s)android\b(\S|)(?!\S)/gmi, replacement: "$1Android$2", reason: "Android should be capitalized" }, oracle: { expr: /(^|\s)oracle\b(\S|)(?!\S)/gmi, replacement: "$1Oracle$2", reason: "Oracle should be capitalized" }, windows: { expr: /(win|windows(?:\ ?)(\s[0-9]+))\b(\S|)(?!\S)/igm, replacement: "Windows$2$3", reason: "Windows should be capitalized" }, windowsXP: { expr: /(win|windows(?:\ ?)(\sxp))\b(\S|)(?!\S)/igm, replacement: "Windows XP$3", reason: "Windows XP should be capitalized" }, windowsVista: { expr: /(win|windows(?:\ ?)(\svista))\b(\S|)(?!\S)/igm, replacement: "Windows Vista$3", reason: "Windows Vista should be capitalized" }, 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: { expr: /(linux)\b(\S|)(?!\S)/igm, replacement: "Linux$2", reason: "Linux should be capitalized" }, 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: { expr: /[Ww]ordpress/g, replacement: "WordPress", reason: "'WordPress' is the proper capitalization" }, google: { expr: /(google)\b(\S|)(?!\S)/igm, replacement: "Google$2", reason: "Google is the proper capitalization" }, mysql: { expr: /(mysql)\b(\S|)(?!\S)/igm, replacement: "MySQL$2", reason: "MySQL is the proper capitalization" }, apache: { expr: /(apache)\b(\S|)(?!\S)/igm, replacement: "Apache$2", reason: "Apache is the proper capitalization" }, git: { expr: /(^|\s)(git|GIT)\b(\S|)(?!\S)/gm, replacement: "$1Git$3", reason: "Git is the proper capitalization" }, harddisk: { expr: /(hdd|harddisk)\b(\S|)(?!\S)/igm, replacement: "hard disk$2", reason: "Hard disk is the proper capitalization" }, github: { expr: /\b([gG]ithub|GITHUB)\b(\S|)(?!\S)/gm, replacement: "GitHub$2", reason: "GitHub is the proper capitalization" } }; // 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 App.funcs.fixIt = function(input, expression, replacement, reasoning) { // Scan the post text using the expression to see if there are any matches var match = input.search(expression); // If so, increase the number of edits performed (used later for edit summary formation) if (match !== -1) { App.globals.editCount++; // Later, this will store what is removed for the first case var phrase; // Store the original input text var originalInput = input; // 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() // 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; }; // Replace code App.funcs.replaceCode = function(str, type) { for (var i = 0; i < App.globals.replacedStrings[type].length; i++) { str = str.replace(App.globals.placeHolders[type], 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 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 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) { 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 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() { 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.listenButton = function() { App.selections.buttonFix.click(function(e) { e.preventDefault(); if (!App.globals.editsMade) { // Refresh item population App.funcs.popItems(); // Pipe data through editing modules App.pipe(App.items, App.globals.pipeMods, App.globals.order); App.globals.editsMade = true; } }); }; // Figure out the last selected element before pressing the button so we can return there after focusing the summary field App.funcs.setLastFocus = function() { App.selections.titleBox.click(function() { App.globals.lastSelectedElement = $(this); }); App.selections.bodyBox.click(function() { App.globals.lastSelectedElement = $(this); }); App.selections.summaryBox.click(function() { App.globals.lastSelectedElement = $(this); }); App.selections.tagField.click(function() { App.globals.lastSelectedElement = $(this); }); }; // Handle pipe output App.funcs.output = function(data) { App.selections.titleBox.val(data[0].title); App.selections.bodyBox.val(data[0].body); if (App.selections.summaryBox.val()) { 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 App.globals.currentPos = document.body.scrollTop; if ($("#wmd-input")) { $("#wmd-input").focus(); $("#edit-comment").focus(); $("#wmd-input").focus(); } else { $(".wmd-input")[0].focus(); $(".edit-comment")[0].focus(); $(".wmd-input")[0].focus(); } window.scrollTo(0, App.globals.currentPos); App.globals.infoContent = App.globals.editCount + ' changes made'; App.selections.buttonInfo.text(App.globals.editCount + ' changes made'); }; }; // Pipe data through modules in proper order, returning the result App.pipe = function(data, mods, order) { var modName; for (var i in order) { if (order.hasOwnProperty(i)) { modName = order[i]; data = mods[modName](data); } } App.funcs.output(data); }; // Init app App.init = function(inline, targetID) { // Check if there was an ID passed (if not, use question ID from URL); if (!targetID) { targetID = App.globals.questionNum; } App.popFuncs(); App.funcs.dynamicDelay(function() { App.funcs.popSelections(); App.funcs.createButton(); App.funcs.styleButton(); App.funcs.popItems(); App.funcs.listenButton(); App.funcs.applyListeners(); App.funcs.setLastFocus(); }, targetID, inline); }; App.globals.pipeMods.omit = function(data) { data[0].body = App.funcs.omitCode(data[0].body, "block"); data[0].body = App.funcs.omitCode(data[0].body, "inline"); return data; }; App.globals.pipeMods.replace = function(data) { data[0].body = App.funcs.replaceCode(data[0].body, "block"); data[0].body = App.funcs.replaceCode(data[0].body, "inline"); return data; }; App.globals.pipeMods.edit = function(data) { // Visually confirm edit - SE makes it easy because the jQuery color animation plugin seems to be there by default App.selections.bodyBox.animate({ backgroundColor: '#c8ffa7' }, 10); App.selections.bodyBox.animate({ backgroundColor: '#fff' }, 1000); // Loop through all editing rules for (var j in App.edits) { if (App.edits.hasOwnProperty(j)) { // Check body var fix = App.funcs.fixIt(data[0].body, App.edits[j].expr, App.edits[j].replacement, App.edits[j].reason); if (fix) { App.globals.reasons[App.globals.numReasons] = fix.reason; data[0].body = fix.fixed; App.globals.numReasons++; App.edits[j].fixed = true; } // Check title fix = App.funcs.fixIt(data[0].title, App.edits[j].expr, App.edits[j].replacement, App.edits[j].reason); if (fix) { data[0].title = fix.fixed; if (!App.edits[j].fixed) { App.globals.reasons[App.globals.numReasons] = fix.reason; App.globals.numReasons++; App.edits[j].fixed = true; } } } // Quickly focus the summary field to show generated edit summary, and then jump back App.selections.summaryBox.focus(); // Asynchronous to get in both focuses setTimeout(function() { if (App.globals.lastSelectedElement) { App.globals.lastSelectedElement.focus(); } else { window.scrollTo(0,0); } }, 0); } // Eliminate duplicate reasons App.globals.reasons = App.funcs.eliminateDuplicates(App.globals.reasons); for (var z = 0; z < App.globals.reasons.length; z++) { // Check that summary is not getting too long if (data[0].summary.length < 200) { // Capitalize first letter if (z === 0) { data[0].summary += App.globals.reasons[z][0].toUpperCase() + App.globals.reasons[z].substring(1); // Post rest of reasons normally } else { data[0].summary += App.globals.reasons[z]; } // Not the last reason if (z !== App.globals.reasons.length - 1) { data[0].summary += "; "; // If at end, punctuate } else { data[0].summary += "."; } } } return data; }; setTimeout(function() { // Allow post to load entirely if ($(".edit-post")[0]) { // User has editing privileges; wait for button press $(".edit-post").click(function(e) { App.init(true, e.target.href.match(/\d/g).join("")); // If there are multiple posts, we need to pass the post ID }); } else if ($(".reviewable-post")[0]) { // H&I review queue App.globals.questionNum = $(".reviewable-post")[0].getAttribute("class").match(/\d/g).join(""); $($(".review-actions")[0].children[0]).click(function(e) { App.init(true, App.globals.questionNum); }); } else { // User does not have editing privileges or is editing on question page; start immediately App.init(false); } }, 1000); // Only set when running tests if (window.mocha) { window.App = App; } }; // Inject the main script var script = document.createElement('script'); script.type = "text/javascript"; script.textContent = '(' + main.toString() + ')();'; document.body.appendChild(script);
수정본
파일 열기
// ==UserScript== // @name Stack-Exchange-Editor-Toolkit // @author Cameron Bernhardt (AstroCB) // @developer Jonathan Todd (jt0dd) // @developer sathyabhat // @contributor Unihedron // @contributor Tiny Giant // @contributor Mogsdad // @grant none // @license MIT // @namespace http://github.com/AstroCB // @version 1.5.2.25 // @description Fix common grammar/usage annoyances on Stack Exchange posts with a click // @include /^https?://\w*.?(stackoverflow|stackexchange|serverfault|superuser|askubuntu|stackapps)\.com/(questions|posts|review)/(?!tagged|new).*/ // ==/UserScript== (function() { "use strict"; function extendEditor(root) { var App = {}; // Place edit items here App.items = {}; App.originals = {}; // Place selected jQuery items here App.selections = {}; // Place "global" app data here App.globals = {}; // Place "helper" functions here App.funcs = {}; // True to display rule names in Edit Summary App.globals.showRules = false; //Preload icon alt var SEETicon = new Image(); SEETicon.src = '//i.imgur.com/d5ZL09o.png'; App.globals.root = root; App.globals.spacerHTML = '<li class="wmd-spacer wmd-spacer3" id="wmd-spacer3" style="left: 400px !important;"></li>'; App.globals.reasons = {}; App.globals.replacedStrings = { "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 }; // Assign modules here App.globals.pipeMods = {}; // Define order in which mods affect here App.globals.order = ["omit", "codefix", "edit", "replace"]; // Define edit rules App.edits = { // All caps noneedtoyell: { expr: /^((?=.*[A-Z])[^a-z]*)$/g, replacement: function(input) { return input.trim().substr(0, 1).toUpperCase() + input.trim().substr(1).toLowerCase(); }, reason: 'no need to yell' }, // Trademark capitalization so: { expr: /\bstack\s*overflow\b/gi, replacement: "Stack Overflow", reason: "'Stack Overflow' is the legal name" }, se: { expr: /\bstack\s*exchange\b/gi, replacement: "Stack Exchange", reason: "'Stack Exchange' is the legal name" }, expansionSO: { expr: /([^\b\w.]|^)SO\b/g, replacement: "$1Stack Overflow", reason: "'Stack Overflow' is the legal name" }, expansionSE: { expr: /([^\b\w.]|^)SE\b/g, replacement: "$1Stack Exchange", reason: "'Stack Exchange' is the legal name" }, javascript: { expr: /([^\b\w.]|^)(javascript|js)\b/gi, replacement: "$1JavaScript", reason: "trademark capitalization" }, jsfiddle: { expr: /\bjsfiddle\b/gi, replacement: "JSFiddle", reason: "trademark capitalization" }, jquery: { expr: /\bjquery\b/gi, replacement: "jQuery", reason: "trademark capitalization" }, angular: { expr: /\bangular(?:js)?\b/gi, replacement: "AngularJS", reason: "trademark capitalization" }, html: { expr: /([^\b\w.]|^)html(\d)?\b/gi, replacement: "$1HTML$2", reason: "trademark capitalization" }, css: { expr: /([^\b\w.]|^)css\b/gi, replacement: "$1CSS", reason: "trademark capitalization" }, json: { expr: /\bjson\b/gi, replacement: "JSON", reason: "acronym capitalization" }, ajax: { expr: /\bajax\b/gi, replacement: "AJAX", reason: "acronym capitalization" }, php: { expr: /([^\b\w.]|^)php\b/gi, replacement: "$1PHP", reason: "trademark capitalization" }, voting: { expr: /\b(down|up)\Wvot/gi, replacement: "$1vote", reason: "the proper spelling (despite the tag name) is '$1vote' (one word)" }, c: { expr: /\bc\b([#+]+)?/gi, replacement: "C$1", reason: "trademark capitalization" }, java: { expr: /\bjava\b/gi, replacement: "Java", reason: "trademark capitalization" }, sql: { expr: /([^\b\w.]|^)sql\b/gi, replacement: "$1SQL", reason: "trademark capitalization" }, sqlite: { expr: /\bsqlite\s*([0-9]*)\b/gi, replacement: "SQLite $2", reason: "trademark capitalization" }, android: { expr: /\bandroid\b/gi, replacement: "Android", reason: "trademark capitalization" }, oracle: { expr: /\boracle\b/gi, replacement: "Oracle", reason: "trademark capitalization" }, windows: { // https://regex101.com/r/jF9zK1/5 expr: /\b(?:win|windows)\s+(2k|[0-9.]+|ce|me|nt|xp|vista|server)|(?:win|windows)\b/gi, 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" }, linux: { expr: /\blinux\b/gi, replacement: "Linux", reason: "trademark capitalization" }, wordpress: { expr: /\bwordpress\b/gi, replacement: "WordPress", 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" }, 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?|kind(?: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" } }; // 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) { // 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 var matches = input.match(expression); if (!matches) return false; console.log(JSON.stringify(matches)) 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; }; App.funcs.applyListeners = function() { // Removes default Stack Exchange listeners; see https://github.com/AstroCB/Stack-Exchange-Editor-Toolkit/issues/43 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(); } } } // Tags box App.selections.tagField.keydown(removeEventListeners); // Edit summary box App.selections.summary.keydown(removeEventListeners); }; // Populate or refresh DOM selections 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'); }; // Populate edit item sets from DOM selections App.funcs.popItems = function() { var i = App.items, s = App.selections; ['title', 'body', 'summary'].forEach(function(v) { i[v] = String(s[v].val()).trim(); }); }; // 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) 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 App.selections.redoButton.after(App.selections.buttonWrapper); // Insert spacer App.selections.redoButton.after(App.globals.spacerHTML); // Attach the event listener to the button App.selections.buttonFix.click(App.funcs.fixEvent); App.selections.helpButton.css({ 'padding': '0px' }); App.selections.buttonWrapper.css({ 'position': 'relative', 'left': '430px', 'padding-top': '2%' }); 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.funcs.makeDiffTable = function() { App.selections.diffTable = $('<table class="diffTable"/>'); App.selections.editor.append(App.selections.diffTable); }; App.funcs.fixEvent = function(e) { if (e) e.preventDefault(); // Refresh item population App.funcs.popOriginals(); App.funcs.popItems(); // Pipe data through editing modules App.pipe(App.items, App.globals.pipeMods, App.globals.order); }; App.funcs.diff = function() { App.selections.diffTable.empty(); function maakRij(x, y, type, rij) { var tr = $('<tr/>'); if (type === '+') tr.addClass('add'); if (type === '-') tr.addClass('del'); tr.append($('<td class="codekolom">' + y + '</td>')); tr.append($('<td class="codekolom">' + x + '</td>')); tr.append($('<td class="bredecode">' + type + ' ' + rij.replace(/\</g, '<') + '</td>')); App.selections.diffTable.append(tr); } 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; } } } 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 + matrix[y - 1][x - 1]; } else { matrix[y][x] = Math.max(matrix[y - 1][x], matrix[y][x - 1]); } } } try { getDiff(matrix, a1, a2, x - 1, y - 1); } catch (e) { alert(e); } }; // Handle pipe output App.funcs.output = function(data) { App.selections.title.val(data.title); App.selections.body.val(data.body); App.selections.summary.val(data.summary); App.selections.summary.focus(); App.selections.editor.append(App.funcs.diff()); StackExchange.MarkdownEditor.refreshAllPreviews(); App.selections.buttonInfo.text(App.globals.changes + (App.globals.changes>1 ? ' changes' : ' change')+' made'); }; // Pipe data through modules in proper order, returning the result App.pipe = function(data, mods, order) { var modName; for (var i in order) { if (order.hasOwnProperty(i)) { modName = order[i]; mods[modName](data); } } App.funcs.output(data); }; App.globals.pipeMods.omit = function(data) { if (!data.body) return false; for (var type in App.globals.checks) { data.body = data.body.replace(App.globals.checks[type], function(match) { App.globals.replacedStrings[type].push(match); return App.globals.placeHolders[type]; }); } return data; }; App.globals.pipeMods.codefix = function() { var replaced = App.globals.replacedStrings.block, str; for (var i in replaced) { // https://regex101.com/r/tX9pM3/1 https://regex101.com/r/tX9pM3/2 https://regex101.com/r/tX9pM3/3 if (/^`[^]+`$/.test(replaced[i])) replaced[i] = /(?!`)((?!`)[^])+/.exec(replaced[i])[1].replace(/(.+)/g, ' $1'); } }; App.globals.pipeMods.replace = function(data) { if (!data.body) return false; for (var type in App.globals.checks) { var i = 0; data.body = data.body.replace(App.globals.placeHolderChecks[type], function(match) { return App.globals.replacedStrings[type][i++]; }); } return data; }; App.globals.pipeMods.edit = function(data) { // Visually confirm edit - SE makes it easy because the jQuery color animation plugin seems to be there by default App.selections.body.animate({ backgroundColor: '#c8ffa7' }, 10); App.selections.body.animate({ backgroundColor: '#fff' }, 1000); // List of fields to be edited var fields = {body:'body',title:'title'}; // Loop through all editing rules for (var j in App.edits) { for (var field in fields) { if (App.edits.hasOwnProperty(j)) { var fix = App.funcs.fixIt(data[field], App.edits[j].expr, App.edits[j].replacement, App.edits[j].reason); if (fix) { // HACK ALERT if (j === 'firstcaps') fix.count = 1; if (!App.globals.reasons.hasOwnProperty(fix.reason)) { App.globals.reasons[fix.reason] = {reason:fix.reason, editId:j, count:fix.count}; } else { App.globals.reasons[fix.reason].count += fix.count; } data[field] = fix.fixed; App.edits[j].fixed = true; } } } } // If there are no reasons, exit if (App.globals.reasons == {}) return false; // We need a place to store the reasons being applied to the summary. var reasons = []; App.globals.changes = 0; for (var z in App.globals.reasons) { // For each type of change made, add a reason string with the reason text, // optionally the rule ID, and the number of repeats if 2 or more. reasons.push(App.globals.reasons[z].reason + (App.globals.showRules ? ' ['+ App.globals.reasons[z].editId +']' : '') + ((App.globals.reasons[z].count > 1) ? ' ('+App.globals.reasons[z].count+')' : '') ); App.globals.changes += App.globals.reasons[z].count; } var reasonStr = reasons.join('; ')+'.'; // Unique reasons separated by ; and terminated by . reasonStr = reasonStr.charAt(0).toUpperCase() + reasonStr.slice(1); // Cap first letter. if (!data.summaryOrig) data.summaryOrig = data.summary.trim(); // Remember original summary if (data.summaryOrig.length) data.summaryOrig = data.summaryOrig + ' '; data.summary = data.summaryOrig + reasonStr; // Limit summary to 300 chars if (data.summary.length > 300) data.summary = data.summary.substr(0,300-3) + '...'; return data; }; // Init app App.init = function() { var count = 0; var toolbarchk = setInterval(function(){ //console.log('waiting for toolbar'); if(++count === 10) clearInterval(toolbarchk) if(!App.globals.root.find('.wmd-button-row').length) return; clearInterval(toolbarchk); //console.log('found toolbar'); App.funcs.popSelections(); App.funcs.createButton(); App.funcs.applyListeners(); App.funcs.makeDiffTable(); }, 100); return App; }; return App.init(); } try { var test = window.location.href.match(/.posts.(\d+).edit/); if(test) extendEditor($('form[action^="/posts/' + test[1] + '"]')); else $(document).ajaxComplete(function() { test = arguments[2].url.match(/posts.(\d+).edit-inline/); if(!test) { test = arguments[2].url.match(/review.inline-edit-post/) if(!test) return; test = arguments[2].data.match(/id=(\d+)/); if(!test) return; } extendEditor($('form[action^="/posts/' + test[1] + '"]')); }); if($('#post-form').length) extendEditor($('#post-form')); // This is the styling for the diff output. $('body').append('<style>.diff { max-width: 100%; overflow: auto; } td.bredecode, td.codekolom { padding: 1px 2px; } td.bredecode { width: 100%; padding-left: 4px; white-space: pre-wrap; word-wrap: break-word; } td.codekolom { text-align: right; min-width: 3em; background-color: #ECECEC; border-right: 1px solid #DDD; color: #AAA; } tr.add { background: #DFD; } tr.del { background: #FDD; }</style>'); } catch (e) { console.log(e); } })();
비교하기