User:Cameltrader/A.js
From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. In Internet Explorer and Firefox, hold down the Ctrl key and click the Refresh or Reload button. Opera users have to clear their caches through Tools→Preferences, see the instructions for Opera. Konqueror and Safari users can just click the Reload button.
/* <nowiki> The content has been copied to User:Cameltrader/Advisor.js This file will soon be deleted. Please update your monobook.js The code is divided into the following sections: * Global constants and variables - all of them prefixed "ct" for "Cameltrader". The intended types are marked as comments (if only JS were a strongly typed language...) * Rules - a library of substitution rules - functions which return an array of Suggestion objects, given a certain piece of text. This is where other developers are most likely to contribute. Contributing rules as separate .js files is fine, as long as they are loaded after this .js file. * RegExp utility functions - extensions of the RegExp prototype: ** re.getAllMatches(s) // : Match[] Pretty much what you expect. A Match is the result of the standard re.exec(..) method, with additional m.start and m.end properties. ** re.fix() // : RegExp Makes some adjustments to the RegExp syntax, allowing pseudo-character classes, such as {letter} (note that /\w/ matches only [A-Za-z_], ignoring other Unicode letters). * DOM utility functions - most of them self-documenting one-liners, their names start with $ * Core - a couple of functions which query the rules and do the user interface rendering work. --Cameltrader */ // <code><nowiki> /* Global constants and variables */ var CT_DEFAULT_MAX_SUGGESTIONS = 8; var ctMaxSuggestions = CT_DEFAULT_MAX_SUGGESTIONS; var ctSuggestions; // : Suggestion[] var ctTop; // : Element; that's where suggestions are rendered var ctTextbox1; // : Element; same as wpTextbox1 var CT_RULES = []; // : Function[]; a constant ...or a variable, if you prefer var CT_LANGUAGE_MAP = { // : Hashtable<String, String> Arabic: "ar", Bulgarian: "bg", English: "en", French: "fr", German: "de", Greek: "el", Hebrew: "he", Italian: "it", Macedonian: "mk", Polish: "pl", Portuguese: "pt", Russian: "ru", Romanian: "ro", Spanish: "es", Serbian: "sr", Turkish: "tr" }; var CT_REG_EXP_REPLACEMENTS = { letter: // all Unicode letters // http://www.codeproject.com/dotnet/UnicodeCharCatHelper.asp "\\u0041-\\u005a\\u0061-\\u007a\\u00aa" + "\\u00b5\\u00ba\\u00c0-\\u00d6" + "\\u00d8-\\u00f6\\u00f8-\\u01ba\\u01bc-\\u01bf" + "\\u01c4-\\u02ad\\u0386\\u0388-\\u0481\\u048c-\\u0556" + "\\u0561-\\u0587\\u10a0-\\u10c5\\u1e00-\\u1fbc\\u1fbe" + "\\u1fc2-\\u1fcc\\u1fd0-\\u1fdb\\u1fe0-\\u1fec" + "\\u1ff2-\\u1ffc\\u207f\\u2102\\u2107\\u210a-\\u2113" + "\\u2115\\u2119-\\u211d\\u2124\\u2126\\u2128" + "\\u212a-\\u212d\\u212f-\\u2131\\u2133\\u2134\\u2139" + "\\ufb00-\\ufb17\\uff21-\\uff3a\\uff41-\\uff5a", letter_bg: // note that this is not full set of Cyrillic letters "АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЬЮЯабвгдежзийклмнопрстуфхцчшщъьюя" }; /* Rules */ CT_RULES.push(function (s) { var a = /\[\[(\w+) language\|\1\]\] *[:—–\-] *([{letter} \"\'\\—–\-]+)/g .fix().getAllMatches(s); var b = []; for (var i = 0; i < a.length; i++) { var m = a[i]; if (CT_LANGUAGE_MAP[m[1]] == null) { continue; } var code = CT_LANGUAGE_MAP[m[1]] b.push(new Suggestion( m.start, m.end, m[0], "{{lang-" + code + "|" + m[2] + "}}", "lang-" + code, "The {{lang-" + code + "}} template can be applied for this text." )); } return b; }); CT_RULES.push(function (s) { // Only accept years 1000...2999 var a = /\( *([1-2][0-9]{3}) *(?:-|—|—|--) *([1-2][0-9]{3}) *\)/g .getAllMatches(s); for (var i = 0; i < a.length; i++) { var m = a[i]; a[i] = new Suggestion( m.start, m.end, m[0], "(" + m[1] + "–" + m[2] + ")", // the char in the middle is an n-dash "ndash", "Year ranges look better with an n-dash." ); } return a; }); CT_RULES.push(function (s) { var a = /^(?: *)(==+)( *)([^=]*[^= ])(?: *)\1/gm.getAllMatches(s); var b = []; for (var i = 0; i < a.length; i++) { var m = a[i]; var suggestion = new Suggestion( m.start, m.end, m[0], m[1] + m[2] + m[3] + m[2] + m[1], "heading", "Heading style should be \"== Heading ==\" or \"==Heading==\"." ); var frequentMistakes = [ { code: "see-also", wrong: /^see *al+so$/i, correct: "See also" }, { code: "ext-links", wrong: /^external links?$/i, correct: "External links" }, { code: "refs", wrong: /^ref+e?r+en(c|s)es?$/i, correct: "References" } ]; for (var j = 0; j < frequentMistakes.length; j++) { var fm = frequentMistakes[j]; if (fm.wrong.test(m[3]) && (m[3] != fm.correct)) { suggestion.replacementText = m[1] + m[2] + fm.correct + m[2] + m[1]; suggestion.name = fm.code; suggestion.description = "The correct spelling/capitalization is \"" + fm.correct + "\"."; } } if (suggestion.originalText != suggestion.replacementText) { b.push(suggestion); } } return b; }); CT_RULES.push(function (s) { var a = /\[\[([{letter} ,\(\)\-]+)\|\1\]\]/g .fix().getAllMatches(s); for (var i = 0; i < a.length; i++) { var m = a[i]; a[i] = new Suggestion( m.start, m.end, m[0], "[[" + m[1] + "]]", "[[A|A]]", "\"[[A|A]]\" can be simplified to [[A]]." ); } return a; }); CT_RULES.push(function (s) { var a = /\[\[([{letter} ,\(\)\-]+)\|\1([{letter}]+)\]\]/g .fix().getAllMatches(s); for (var i = 0; i < a.length; i++) { var m = a[i]; a[i] = new Suggestion( m.start, m.end, m[0], "[[" + m[1] + "]]" + m[2], "[[A|AB]]", "\"[[A|AB]]\" can be simplified to [[A]]B." ); } return a; }); /* CT_RULES.push(function (s) { var a = /\[\[(:?\w+:)([{letter} ,\-]+)( \([{letter} ,\-]+\))\|\2\]\]/g .fix().getAllMatches(s); for (var i = 0; i < a.length; i++) { var m = a[i]; a[i] = new Suggestion( m.start, m.end, m[0], "[[" + m[1] + m[2] + m[3] + "|]]", "[[ns:A (B)|A]]", "\"[[ns:A (B)|A]]\" can be simplified to [[ns:A (B)|]]." ); } return a; }); */ CT_RULES.push(function (s) { // For this rule to apply, at least four of the characters within the // brackets must be Bulgarian Cyrillic letters, and the rest are allowed // to be some limited punctuation var a = /\((([ ,.;:\"\'\\\-\\]|–|—|&|̀)*\ ({letter_bg}+([ ,.;:\"\'\\\-\\]|–|—|&|̀)*){4,})\)/g .fix().getAllMatches(s); // Dangerous rule! The text could be in another // language using the Cyrillic alphabet. for (var i = 0; i < a.length; i++) { var m = a[i]; a[i] = new Suggestion( m.start, m.end, m[0], "({{lang|bg|" + m[1] + "}})", "lang|bg", "Bulgarian text should be marked with \"{{lang|bg|}}\", " + "even when there is no [[Bulgarian language]] link." ); } return a; }); CT_RULES.push(function (s) { var exceptions = {}; // sure, there are many exceptions["Dolni Bogrov"] = true; exceptions["Gorni Bogrov"] = true; if (exceptions[wgTitle]) { return; } var re0 = /^([\w\-]+(?: [\w\-]+)?) ([A-Z][\w\-]+(?:ov|ev|ski))$/; var m0 = re0.exec(wgTitle); if (m0 == null) { return []; } var firstNames = m0[1]; var lastName = m0[2]; // Should be "lastName, firstName" in categories var a = /\[\[(Category:[\w _\(\),\-]+)\]\]/gi .getAllMatches(s); for (var i = 0; i < a.length; i++) { var m = a[i]; a[i] = new Suggestion( m.start, m.end, m[0], "[[" + m[1] + "|" + lastName + ", " + firstNames + "]]", "cat-sort", "The sort key for categories should be the family name." // Wikipedia:Categorization_of_people#Ordering_names_in_a_category ); } return a; }); CT_RULES.push(function (s) { var a = /\( *(?:b\.? *)?((?:19|20)[0-9][0-9]) *(?:[\-\—\–]|–|—|--) *\)/g .getAllMatches(s); for (var i = 0; i < a.length; i++) { var m = a[i]; a[i] = new Suggestion( m.start, m.end, m[0], "(born " + m[1] + ")", "born", "The word \"born\" should be fully written." // WP:DATE#Dates_of_birth_and_death ); } return a; }); /* RegExp utility functions */ RegExp.prototype.getAllMatches = function (s) { // : Match[] var p = 0; var a = []; while (true) { this.lastIndex = 0; var m = this.exec(s.substring(p)); if (m == null) { return a; } m.start = p + m.index; m.end = p + m.index + m[0].length; a.push(m); p = m.end; } } RegExp.prototype.fix = function () { // : RegExp if (this.fixedRE != null) { return this.fixedRE; } var s = this.source; for (var alias in CT_REG_EXP_REPLACEMENTS) { s = s.replace( new RegExp("{" + alias + "}", "g"), CT_REG_EXP_REPLACEMENTS[alias] ); } var re = new RegExp(s); re.global = this.global; re.ignoreCase = this.ignoreCase; re.multiline = this.multiline; this.fixedRE = re; // the fixed copy is cached return re; }; /* DOM utility functions */ function $(id) { return ((typeof id) == "string") ? document.getElementById(id) : id; } function $elem(tagName) { return document.createElement(tagName); } function $text(s) { return document.createTextNode(s); } function $clear(e) { while (e.firstChild != null) { e.removeChild(e.firstChild); } } function $anchor(text, href, title) { var e = $elem("A"); e.href = href; e.appendChild($text(text)); e.title = (title == null) ? "" : title; return e; } function $showTextareaSelection(ta) { // Adapted from a script by User:Zocky // Doesn't always work perfectly, // but this is the best approximation I could find. var allLines = 0; var w = ta.cols - 5; var dummy = ta.value.split("\n"); for (var i = 0; i < dummy.length; i++) { allLines += Math.ceil(dummy[i].length / w); } var dummy = ta.value.substring(0, ta.selectionStart).split("\n"); var lineNo = 0; for (var i = 0; i < dummy.length; i++) { lineNo += Math.ceil(dummy[i].length / w); } ta.scrollTop = ta.scrollHeight * (lineNo - 10) / allLines; ta.focus(); } /* Core */ // A Suggestion is a basic concept here. // It represents an exact occurrence of an error or warning within the text. function Suggestion(start, end, originalText, replacementText, name, description) { this.start = start; this.end = end; this.originalText = originalText; this.replacementText = replacementText; this.name = name; this.description = description; } function ctScan() { var s = ctTextbox1.value; ctSuggestions = []; for (var i = 0; i < CT_RULES.length; i++) { var a = CT_RULES[i](s); if (a.constructor == Suggestion) { a = [ a ]; } for (var j = 0; j < a.length; j++) { ctSuggestions.push(a[j]); } } ctSuggestions.sort(function (x, y) { return (x.start < y.start) ? -1 : (x.start > y.start) ? 1 : (x.end < y.end) ? -1 : (x.end > y.end) ? 1 : 0; }); $clear(ctTop); if (ctSuggestions.length == 0) { ctTop.appendChild($text("No suggestions.")); } else { var nSuggestions = Math.min(ctMaxSuggestions, ctSuggestions.length); ctTop.appendChild($text( (ctSuggestions.length == 1) ? "1 suggestion: " : (ctSuggestions.length + " suggestions: ") )); for (var i = 0; i < nSuggestions; i++) { ctTop.appendChild($anchor( ctSuggestions[i].name, "javascript:ctShowSuggestion(" + i + "); void(0);", ctSuggestions[i].description )); var eSup = $elem("SUP"); ctTop.appendChild(eSup); eSup.appendChild($anchor( "fix", "javascript:ctFixSuggestion(" + i + "); void(0);" )); ctTop.appendChild($text(" ")); } if (ctSuggestions.length > ctMaxSuggestions) { ctTop.appendChild($anchor("...", "javascript: ctMaxSuggestions = 1000; ctScan(); " + "ctMaxSuggestions = CT_DEFAULT_MAX_SUGGESTIONS; void(0);")); ctTop.appendChild($text(" ")); } } ctTop.appendChild($text("(")); ctTop.appendChild($anchor("re-scan", "javascript:ctScan();")); ctTop.appendChild($text(")")); } function ctFixSuggestion(k) { var suggestion = ctSuggestions[k]; if (ctTextbox1.value.substring(suggestion.start, suggestion.end) != suggestion.originalText) { alert("The applicable text has changed. Fix failed."); ctScan(); return; } ctTextbox1.value = ctTextbox1.value.substring(0, suggestion.start) + suggestion.replacementText + ctTextbox1.value.substring(suggestion.end); ctTextbox1.selectionStart = suggestion.start; ctTextbox1.selectionEnd = suggestion.start + suggestion.replacementText.length; $showTextareaSelection(ctTextbox1); ctScan(); } function ctShowSuggestion(k) { var suggestion = ctSuggestions[k]; if (ctTextbox1.value.substring(suggestion.start, suggestion.end) != suggestion.originalText) { // The text has changed - just do another scan and don't change selection ctScan(); return; } ctTextbox1.selectionStart = suggestion.start; ctTextbox1.selectionEnd = suggestion.end; $showTextareaSelection(ctTextbox1); } addOnloadHook(function () { var toolbar = $("toolbar"); if (toolbar == null) { return; } ctTop = $elem("DIV"); ctTop.id = "ctTop"; toolbar.appendChild(ctTop); ctTextbox1 = $("wpTextbox1"); //ctScan(); document.write("<style>#ctTop { border: dashed #ccc 1px; color: #888; }</style>"); // Deprecated ctTop.appendChild($text("To continue using this tool, you should replace \"User:Cameltrader/A.js\" with \"User:Cameltrader/Advisor.js\" in your \"monobook.js\"")); }); // </nowiki>