MediaWiki:Gadget-Pikan-core.js: Difference between revisions
Jump to navigation
Jump to search
(I see what's up. This script is failing if the advanced (JS) editor is loading at the same time. Hopefully this makes it independent enough to work.) |
m (Forgot to update the version number.) |
||
Line 6: | Line 6: | ||
var pikan = pikan || {}; | var pikan = pikan || {}; | ||
pikan.version = "2. | pikan.version = "2.2.0"; | ||
//Data to be filled by each wiki. | //Data to be filled by each wiki. |
Revision as of 17:15, July 27, 2017
// <nowiki>
/* * * * * * * * * * * * * * * * * * * * * * * * *
* Configs
* * * * * * * * * * * * * * * * * * * * * * * * */
var pikan = pikan || {};
pikan.version = "2.2.0";
//Data to be filled by each wiki.
pikan.wiki_url = "";
pikan.find_problems = function() {};
pikan.types = [];
pikan.help_link = "";
pikan.module_name = "";
pikan.module_version = "";
pikan.text = "";
pikan.textbox = null;
pikan.prob_total_tag = null;
pikan.prob_detail_tag = null;
pikan.prob_nr_tag = null;
pikan.cur_prob_nr = -1;
pikan.problems = [];
/* * * * * * * * * * * * * * * * * * * * * * * * *
* Find problems
* * * * * * * * * * * * * * * * * * * * * * * * *
* For every possible problem, the text is scanned
* for instances of said problem. If you want to
* customize the analyzer, you edit this function.
*/
pikan.start_problem_search = function() {
//Setup.
pikan.text = pikan.textbox.value;
pikan.clear();
pikan.find_problems();
pikan.update();
}
/* * * * * * * * * * * * * * * * * * * * * * * * *
* Setup
* * * * * * * * * * * * * * * * * * * * * * * * *
* Creates the necessary page elements and
* initializes some values needed for the
* analyzer to work.
*/
pikan.setup = function() {
//Create the elements for the job.
var editform = document.getElementById("editform");
if(!editform) return;
pikan.textbox = editform.wpTextbox1;
//Div that encapsulates pikan.
pikan.pikan_div = document.createElement("div");
editform.insertBefore(pikan.pikan_div, editform.firstChild);
//Button that finds problems.
var find_button = document.createElement("input");
pikan.pikan_div.appendChild(find_button);
find_button.onclick = pikan.start_problem_search;
find_button.type = "button";
find_button.value = "Check for problems";
find_button.title = "Start the analysis. [alt-shift-?]";
find_button.setAttribute("accesskey", "?");
//Help link.
var help_link_sup = document.createElement("sup");
pikan.pikan_div.appendChild(help_link_sup);
var help_link = document.createElement("a");
help_link_sup.appendChild(help_link);
help_link.innerHTML = "[?]";
help_link.title = "This is a tool that checks the entire page source, and reports problems, like policy breaks or style issues. You don't have to use this, unless you want to help make the page thoroughly clean. Click this link to open a help page. (Pikan version: " + pikan.version + ". " + pikan.module_name + " module version: " + pikan.module_version + ")";
help_link.href = pikan.help_link;
help_link.target = "window";
help_link.style.cursor = "help";
//Span with the number of problems.
pikan.prob_total_tag = document.createElement("span");
pikan.pikan_div.appendChild(pikan.prob_total_tag);
pikan.prob_total_tag.id = "pikan_prob_total";
pikan.prob_total_tag.style.paddingLeft = "8px";
pikan.prob_total_tag.style.fontWeight = "bold";
//Span with error control.
pikan.prob_control_tag = document.createElement("span");
pikan.pikan_div.appendChild(pikan.prob_control_tag);
pikan.prob_control_tag.id = "pikan_prob_control";
pikan.prob_control_tag.style.display = "none";
//Padding vertical bar.
var bar = document.createElement("span");
pikan.prob_control_tag.appendChild(bar);
bar.style.fontWeight = "bold";
bar.style.marginLeft = "12px";
bar.innerHTML = "|";
//Previous error button.
var prev_button = document.createElement("input");
pikan.prob_control_tag.appendChild(prev_button);
prev_button.onclick = pikan.prev_problem;
prev_button.type = "button";
prev_button.style.marginLeft = "12px";
prev_button.style.width = "4em";
prev_button.value = "\u25C4";
prev_button.title = "Go to the previous problem on the list. [alt-shift-minus]"
prev_button.setAttribute("accesskey", "-");
//Next error button.
var next_button = document.createElement("input");
pikan.prob_control_tag.appendChild(next_button);
next_button.onclick = pikan.next_problem;
next_button.style.marginLeft = "4px";
next_button.style.width = "4em";
next_button.type = "button";
next_button.value = "\u25BA";
next_button.title = "Go to the next problem on the list. [alt-shift-plus]"
next_button.setAttribute("accesskey", "+");
//Skip type button.
var skip_button = document.createElement("input");
pikan.prob_control_tag.appendChild(skip_button);
skip_button.onclick = pikan.skip;
skip_button.style.marginLeft = "4px";
skip_button.style.width = "4em";
skip_button.type = "button";
skip_button.value = "\u25BA\u25BA";
skip_button.title = "Skip all problems of this type, and go to the next.";
//Go to error button.
var goto_button = document.createElement("input");
pikan.prob_control_tag.appendChild(goto_button);
goto_button.onclick = pikan.goto_problem;
goto_button.style.marginLeft = "4px";
goto_button.style.width = "4em";
goto_button.type = "button";
goto_button.value = "Select";
goto_button.title = "Highlight the text that is causing the current problem, if any.";
//Current problem nr.
pikan.prob_nr_tag = document.createElement("span");
pikan.prob_control_tag.appendChild(pikan.prob_nr_tag);
pikan.prob_nr_tag.style.marginLeft = "4px";
pikan.prob_nr_tag.style.fontSize = "90%";
pikan.prob_nr_tag.id = "pikan_prob_nr";
//Line break before the details.
pikan.prob_control_tag.appendChild(document.createElement("br"));
//Span with details.
pikan.prob_detail_tag = document.createElement("span");
pikan.prob_control_tag.appendChild(pikan.prob_detail_tag);
pikan.prob_detail_tag.style.fontStyle = "italic";
pikan.prob_detail_tag.id = "pikan_prob_detail";
pikan.textbox.oninput = pikan.clear;
pikan.clear();
}
/* * * * * * * * * * * * * * * * * * * * * * * * *
* Show problem
* * * * * * * * * * * * * * * * * * * * * * * * *
* Shows information about the current problem,
* and highlights it on the textbox.
*/
pikan.show_problem = function(){
pikan.cur_prob_nr = Math.min(pikan.cur_prob_nr, pikan.problems.length);
pikan.cur_prob_nr = Math.max(pikan.cur_prob_nr, 0);
pikan.prob_detail_tag.style.display = "inline";
var cur_prob = pikan.problems[pikan.cur_prob_nr];
var prob_text = "<span style=\"color:" + pikan.types[cur_prob.type].color;
prob_text += "; background:" + pikan.types[cur_prob.type].background;
prob_text += "; padding-left:1em; padding-right:1em; border-radius:8px;\"";
prob_text += "title=\"" + pikan.types[cur_prob.type].description + "\">";
prob_text += cur_prob.type + "</span> ";
prob_text += cur_prob.text;
if(cur_prob.page && cur_prob.page.length > 0){
prob_text += " <a href=\"" + pikan.wiki_url + cur_prob.page + "\" target=\"window\" style=\"font-size:smaller;\">[More info]</a>";
}
pikan.prob_detail_tag.innerHTML = prob_text;
pikan.prob_nr_tag.innerHTML = "(#" + (pikan.cur_prob_nr + 1) + "/" + pikan.problems.length + ")";
pikan.goto_problem();
}
/* * * * * * * * * * * * * * * * * * * * * * * * *
* Go to problem
* * * * * * * * * * * * * * * * * * * * * * * * *
* Highlights the text where the current problem
* was found.
*/
pikan.goto_problem = function(){
if(pikan.cur_prob_nr == -1) return;
var prob = pikan.problems[pikan.cur_prob_nr];
if(prob.pos == 0 && prob.len == 0) return;
pikan.textbox.focus();
//We need a hack for Chrome and some other browsers (I can't find a pattern),
//as they don't scroll the textbox to the selection.
var text_before_selection = pikan.text.substr(0, prob.pos);
pikan.textbox.value = text_before_selection;
pikan.textbox.scrollTop = 999999;
pikan.textbox.value = pikan.text;
//Normal selection code. For browsers that scroll to the selection,
//the previous code will be pointless.
pikan.textbox.selectionStart = pikan.problems[pikan.cur_prob_nr].pos;
pikan.textbox.selectionEnd = pikan.problems[pikan.cur_prob_nr].pos + pikan.problems[pikan.cur_prob_nr].len;
}
/* * * * * * * * * * * * * * * * * * * * * * * * *
* Previous result
* * * * * * * * * * * * * * * * * * * * * * * * *
* Changes to the previous result,
* looping around to the last,
* and then shows and highlights it.
*/
pikan.prev_problem = function(){
if(pikan.cur_prob_nr == -1) return;
pikan.cur_prob_nr--;
if(pikan.cur_prob_nr == -1) pikan.cur_prob_nr = pikan.problems.length - 1;
pikan.show_problem();
}
/* * * * * * * * * * * * * * * * * * * * * * * * *
* Next result
* * * * * * * * * * * * * * * * * * * * * * * * *
* Changes to the next result,
* looping around to the first,
* and then shows and highlights it.
*/
pikan.next_problem = function(){
if(pikan.cur_prob_nr == -1) return;
pikan.cur_prob_nr++;
if(pikan.cur_prob_nr == pikan.problems.length) pikan.cur_prob_nr = 0;
pikan.show_problem();
}
/* * * * * * * * * * * * * * * * * * * * * * * * *
* Skip problem
* * * * * * * * * * * * * * * * * * * * * * * * *
* Changes to the next result of a different
* problem, looping back to the start if needed,
* and then shows and highlights it.
* If there are no different problems, it just
* stays on the same problem.
*/
pikan.skip = function(){
if(pikan.cur_prob_nr == -1) return;
var prob_text = pikan.problems[pikan.cur_prob_nr].text;
var done = false;
var starting_nr = pikan.cur_prob_nr;
while(!done){
pikan.cur_prob_nr++;
if(pikan.cur_prob_nr >= pikan.problems.length){
pikan.cur_prob_nr = 0;
}
if(
pikan.problems[pikan.cur_prob_nr].text != prob_text ||
pikan.cur_prob_nr == starting_nr
){
done = true;
}
}
pikan.show_problem();
}
/* * * * * * * * * * * * * * * * * * * * * * * * *
* Clear results
* * * * * * * * * * * * * * * * * * * * * * * * *
* Clear all results found and hides the problem
* report text.
*/
pikan.clear = function(){
pikan.problems = [];
pikan.prob_detail_tag.style.display = "none";
pikan.prob_total_tag.innerHTML = "";
pikan.prob_control_tag.style.display = "none";
}
/* * * * * * * * * * * * * * * * * * * * * * * * *
* Update number
* * * * * * * * * * * * * * * * * * * * * * * * *
* This updates the number of problems found, and
* displays it on the page.
*/
pikan.update = function(){
//Update the number.
pikan.prob_total_tag.innerHTML = "Problems found: " + pikan.problems.length;
if(pikan.problems.length > 0) {
pikan.prob_total_tag.style.color = "#600";
pikan.prob_control_tag.style.display = "inline";
if(pikan.cur_prob_nr >= pikan.problems.length) pikan.cur_prob_nr = pikan.problems.length - 1;
pikan.show_problem();
} else {
pikan.prob_total_tag.style.color = "#363";
pikan.cur_prob_nr = -1;
pikan.prob_control_tag.style.display = "none";
}
pikan.pikan_div.style.minHeight = "3.5em";
}
/* * * * * * * * * * * * * * * * * * * * * * * * *
* Save problem
* * * * * * * * * * * * * * * * * * * * * * * * *
* Saves a found problem.
* pos: Starting position of the text where the
* problem was found.
* len: Length of the text.
* type: Type of problem. Use pikan.types.
* text: Description text for the problem.
* page: Optional link to a page on the wiki,
* explaining the problem and solutions better.
*/
pikan.save_problem = function(pos, len, type, text, page){
pikan.problems.push({
pos: pos,
len: len,
type: type,
text: text,
page: page
});
}
/* * * * * * * * * * * * * * * * * * * * * * * * *
* Regex check
* * * * * * * * * * * * * * * * * * * * * * * * *
* Checks for all instances of the specified regex
* pattern, and adds them to the list of
* problems.
* reg; prob_des; page:
* Read save_problem() for an explanation.
* cond: condition function.
* Takes a regex pattern match.
* Returns false if that match is to be ignored.
*/
pikan.regex_check = function(reg, type, prob_des, page, cond){
while((found = reg.exec(pikan.text)) !== null) {
var valid = true;
if(cond){
valid = cond(found);
}
if(valid){
pikan.save_problem(found.index, found[0].length, type, prob_des, page);
}
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * *
* Is in area
* * * * * * * * * * * * * * * * * * * * * * * * *
* Returns whether a regex match is inside an area
* delimited by two strings. Currently, it only
* works as expected if the area is single-level
* in depth.
* r: regex match.
* starter: area start delimiter.
* ender: area end delimiter.
* exclude: if defined, this string must not be in-between
* the starter and the ender.
*/
pikan.in_area = function(r, starter, ender, exclude){
if(r[0] == starter || r[0] == ender) return false;
var before_text = pikan.text.substr(0, r.index);
var last_starter_before = before_text.lastIndexOf(starter);
if(last_starter_before == -1) return false; //We couldn't even find the starter text.
var last_ender_before = before_text.lastIndexOf(ender);
if(last_ender_before > last_starter_before) return false; //The closest area before the text closes before the text too.
var after_text = pikan.text.substr(r.index + r.length);
var first_ender_after = after_text.indexOf(ender);
var first_exclude_after = after_text.indexOf(exclude);
if(first_ender_after == -1) return false; //We couldn't even find the ender text.
if(first_exclude_after != -1 && first_exclude_after < first_ender_after) return false;
return true;
}
/* * * * * * * * * * * * * * * * * * * * * * * * *
* Get section level
* * * * * * * * * * * * * * * * * * * * * * * * *
* Returns the level of a section header.
* str: the string containing the header's text.
*/
pikan.get_section_level = function(str){
var section_level = 0;
for(c = 0; c < str.length; c++){
if(str[c] == '=') section_level++;
else break;
}
return section_level;
}
/* * * * * * * * * * * * * * * * * * * * * * * * *
* Text before
* * * * * * * * * * * * * * * * * * * * * * * * *
* Returns a string of text that comes before a
* regex match.
* r: regex match.
* n: number of characters for the result.
* -1: whole text before until a line break
* is hit.
* -2: whole text before until the start.
*/
pikan.before = function(r, n){
if(n > 0){
return pikan.text.substr(Math.max(0, r.index - n), n);
}
if(n == -1){
var line_break = false;
var char_n = r.index;
while(!line_break){
if(char_n == 0){
line_break = true;
}else{
var c = pikan.text[char_n - 1];
if(c == "\n" || c == "\r"){
line_break = true;
}else{
char_n--;
}
}
}
return pikan.text.substr(char_n, r.index - char_n);
}
if(n == -2){
return pikan.text.substr(0, r.index);
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * *
* Text after
* * * * * * * * * * * * * * * * * * * * * * * * *
* Returns a string of text that comes after a
* regex match.
* r: regex match.
* n: number of characters for the result.
* -1: whole text after until a line break
* is hit.
* -2: whole text after until the end.
*/
pikan.after = function(r, n){
var start = r.index + r[0].length;
if(n > 0){
return pikan.text.substr(start, n);
}
if(n == -1){
var line_break = false;
var char_n = r.index + r[0].length;
while(!line_break){
if(char_n == pikan.text.length){
line_break = true;
}else{
var c = pikan.text[char_n + 1];
if(c == "\n" || c == "\r"){
line_break = true;
}else{
char_n++;
}
}
}
char_n++;
return pikan.text.substr(start, char_n - start);
}
if(n == -2){
return pikan.text.substr(start, pikan.text.length - start);
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * *
* Must be uppercase
* * * * * * * * * * * * * * * * * * * * * * * * *
* Returns whether a regex match's word MUST be
* in uppercase (start of a new sentence) or not.
* reg: regex match.
*/
pikan.must_be_upper = function(reg){
//If it's a template parameter name or value, forget it.
if(pikan.in_area(reg, "{{", "}}")) return true;
var in_link = pikan.in_area(reg, "[[", "]]", true);
//If it's a page name (exclusively), forget it.
if(in_link && pikan.in_area(reg, "[[", "|", "]]")) return true;
var in_gallery = pikan.in_area(reg, "<gallery", "/gallery");
var before = pikan.before(reg, -1);
//If it's a filename in a gallery, then forget it.
if(in_gallery && before.indexOf("|") == -1) return true;
//Check if this word belongs to a link's display text,
//where we'll ignore the start of the link.
if(in_link && before.substr(before.length - 1, 1) == "|"){
while(before[before.length - 1] != "["){
before = before.slice(0, before.length - 1);
}
}
//Ignore link starts (if it's a link like [[Apples|bananas]], it will
//find "Apples" further up in the function).
if(before.substr(before.length - 2, 2) == "[["){
before = before.slice(0, before.length - 2);
}
//Ignore bold and italics, lists, quotes, equals, spaces.
while(
before[before.length - 1] == "'" ||
before[before.length - 1] == "*" ||
before[before.length - 1] == "#" ||
before[before.length - 1] == "\"" ||
before[before.length - 1] == "=" ||
before[before.length - 1] == " "
){
before = before.slice(0, before.length - 1);
}
//Check if it's the start of a sentence, because if so, it must be upper.
if(
before.length == 0 ||
(in_gallery && before.substr(before.length - 1, 1) == "|") ||
before.substr(before.length - 1, 1) == "."
){
return true;
}
return false;
}
/* * * * * * * * * * * * * * * * * * * * * * * * *
* Get section number
* * * * * * * * * * * * * * * * * * * * * * * * *
* Returns the section number that some text is in.
* This is an array, with each position being a level.
* e.g. section 4.6.2 on the index would be an array
* like [4][6][2]. Returns an empty array for none.
* reg: regex match.
*/
pikan.get_section_nr = function(reg){
var sec_reg = /^=+[^=]+=+$/gm;
var sec_match = null;
var last_section = [];
while(sec_match = sec_reg.exec(pikan.text)){
if(sec_match.index > reg.index) break;
var cur_section = [];
var l = pikan.get_section_level(sec_match[0]);
for(var x = 0; x < l; x++){
cur_section[x] = 0;
}
if(last_section.length == 0) last_section = cur_section;
for(var x = 0; x < Math.min(last_section.length, cur_section.length); x++){
cur_section[x] = last_section[x];
}
cur_section[cur_section.length - 1]++;
last_section = cur_section;
}
return last_section;
}
/* * * * * * * * * * * * * * * * * * * * * * * * *
* Is same section
* * * * * * * * * * * * * * * * * * * * * * * * *
* Returns whether section nr1 is a sub-section
* of section nr2. The numbers you feed it are
* the arrays returned by pikan.get_section_nr.
* nr1: first number.
* nr2: second number.
*/
pikan.section_in_section = function(nr1, nr2){
if(nr2.length > nr1.length) return false;
for(var l = 0; l < Math.min(nr1.length, nr2.length); l++){
if(nr1[l] != nr2[l]) return false;
}
return true;
}
// </nowiki>