Направо към съдържанието

МедияУики:Gadget-Advisor.js и Потребител:Ted Masters/Test 3.js: Разлика между страниците

от Уикипедия, свободната енциклопедия
(Разлики между страниците)
Изтрито е съдържание Добавено е съдържание
м Грешки в статичния код: Остарели HTML-тагове
 
Редакция без резюме
 
Ред 2: Ред 2:
// Виж МедияУики:Gadget-Advisor-core.js за основния скрипт
// Виж МедияУики:Gadget-Advisor-core.js за основния скрипт
// виж http://en.wikipedia.org/wiki/User:Cameltrader/Advisor.js/Description
// виж http://en.wikipedia.org/wiki/User:Cameltrader/Advisor.js/Description

importScript('МедияУики:Gadget-Advisor-core.js');


var ct = ct || {};
var ct = ct || {};


ct.inBrackets = function (s, m, brackets) {
ct.inBrackets = function (s, m, brackets) {
var leftContext = s.substring(0, m.start);
let leftContext = s.substring(0, m.start);
var rightContext = s.substring(m.end);
let rightContext = s.substring(m.end);


var indexOfOpeningLeft = leftContext.lastIndexOf(brackets[0]);
let indexOfOpeningLeft = leftContext.lastIndexOf(brackets[0]);
var indexOfClosingLeft = leftContext.lastIndexOf(brackets[1]);
let indexOfClosingLeft = leftContext.lastIndexOf(brackets[1]);
var indexOfOpeningRight = rightContext.indexOf(brackets[0]);
let indexOfOpeningRight = rightContext.indexOf(brackets[0]);
var indexOfClosingRight = rightContext.indexOf(brackets[1]);
let indexOfClosingRight = rightContext.indexOf(brackets[1]);


return (indexOfOpeningLeft != -1 && (indexOfClosingLeft == -1 || indexOfOpeningLeft > indexOfClosingLeft)) ||
return (indexOfOpeningLeft !== -1 && (indexOfClosingLeft === -1 || indexOfOpeningLeft > indexOfClosingLeft)) ||
(indexOfClosingRight != -1 && (indexOfOpeningRight == -1 || indexOfOpeningRight > indexOfClosingRight));
(indexOfClosingRight !== -1 && (indexOfOpeningRight === -1 || indexOfOpeningRight > indexOfClosingRight));
};
};


// originally from https://en.wikipedia.org/wiki/User:GregU/dashes.js
// originally from https://en.wikipedia.org/wiki/User:GregU/dashes.js
// checkPat1, checkPat2, checkTags, checkFileName default to true
// checkPat1, checkPat2, checkTags, checkFileName default to true
ct.doNotFix = function (s, m, checkPat1, checkPat2, checkTags, checkFileName) {
ct.doNotFix = function (s, m, checkPat1, checkPat2, checkTags, checkFileName, checkWikiPreCode, checkQuotes) {
var pos = m.start;
let pos = m.start;
var pat = /\[\[[^|\]]*$|\{\{[^|}]*$|[:\/%][^\s|>]+$|<[^>]*$|#\w*expr:.*$/i;
let pat = /\[\[[^|\]]*$|\{\{[^|}]*$|[:\/%][^\s|>]+$|<[^>]*$|#\w*expr:.*$/i;
if (checkPat1 !== false && s.substring(pos - 260, pos + 1).search(pat) >= 0)
if (checkPat1 !== false && s.substring(pos - 260, pos + 1).search(pat) >= 0)
return true; // it's a link, so don't change it
return true; // it's a link, so don't change it


var pat2 = /\{\{(друг[ои] значени[ея]|основна|към|от пренасочване|категория|anchor)[^}]*$/i;
let pat2 = /\{\{\s*(?:#[A-Za-z]+:|друг[ои]\s+значени[ея]|основна|main|към|от\s+пренасочване|категория|anchor|cite|citation|цитат2?|c?quote|is[sb]n|lang[i2]?|[es]fn|hrf|harv|пост\s+списък)(?:(?=([^{}]+))\1|\{(?:(?=([^{}]+))\2|\{(?:(?=([^{}]+))\3|\{(?:(?=([^{}]+))\4|\{(?:(?=([^{}]+))\5|\{(?:(?=([^{}]+))\6|\{(?=([^{}]*))\7\})*\})*\})*\})*\})*\})*$/i; // long and ugly regex because of limited JS regex engine
if (checkPat2 !== false && s.substring(pos - 260, pos + 1).search(pat2) >= 0)
if (checkPat2 !== false && s.slice(0, pos + 1).search(pat2) >= 0)
return true; // specific template/parser function (with up to six balanced curly brackets -- 3 levels of template/parser function nesting or 2 levels of parameter placeholder nesting)
return true; // likely templates with page-name


if (checkTags !== false) {
if (checkTags !== false) {
var nextTagPos = s.slice(pos).search(/<\/?(math|pre|code|tt|source|syntaxhighlight|timeline|graph)\b/i);
let nextTagPos = s.slice(pos).search(/<\/?(chem|math|pre|code|tt|source|syntaxhighlight|timeline|graph|mapframe|maplink|poem|blockquote|q|i|ref)\b/i);
if (nextTagPos >= 0 && s.charAt(pos + nextTagPos + 1) == '/')
if (nextTagPos >= 0 && s.charAt(pos + nextTagPos + 1) === '/')
return true; // skip math/chem equations, source code, timelines, graphs, maps, ref content, content in citation tags
return true; // don't break a <math> equation, or source code
}
}


if (checkFileName !== false && s.slice(pos).search(/^[^|{}[\]<>\n]*\.([a-z]{3,4}\s*([|}\n]|\{\{!\}))/i) >= 0)
if (checkFileName !== false && s.slice(pos).search(/^[^|{}[\]<>\n]*\.([a-z]{3,4}\s*([|}\n]|\{\{!\}))/i) >= 0)
return true; // it's a file name parameter
return true; // it's a file name parameter

if (checkWikiPreCode !== false && s.slice(0, pos + 1).search(/(?:^|\n) +.*$/) >= 0)
return true; // it's a text in wiki pre code (a line starting with a normal space)

if (checkQuotes !== false) {
let leftSlice = s.slice(0, pos + 1).replace(/'''/g, '');
let rightSlice = s.slice(pos + 1).replace(/'''/g, '');
if (leftSlice.match(/„(?:(?=([^„“”]+))\1|„[^„“”]*[“”])*$/) &&
rightSlice.match(/^(?:(?=([^„“”]+))\1|„[^„“”]*[“”])*[“”]/) ||
leftSlice.match(/“(?:(?=([^“”]+))\1|“[^“”]*”)*$/) &&
rightSlice.match(/^(?:(?=([^“”]+))\1|“[^“”]*”)*”/) ||
leftSlice.match(/«(?:(?=([^«»]+))\1|«[^«»]*»)*$/) &&
rightSlice.match(/^(?:(?=([^«»]+))\1|«[^«»]*»)*»/) ||
leftSlice.match(/"[^"]*$/) &&
rightSlice.match(/^[^"]*"/) &&
leftSlice.match(/"/g).length % 2 === 1 &&
rightSlice.match(/"/g).length % 2 === 1 ||
leftSlice.match(/''(?:(?!'').)*$/) &&
rightSlice.match(/^.*?''/) &&
leftSlice.match(/''/g).length % 2 === 1 &&
rightSlice.match(/''/g).length % 2 === 1
) return true; // it's a text in quotes or italicized text
}
};
};


if (mw.config.get('wgUserLanguage') === 'bg') {
if (mw.config.get('wgUserLanguage') === 'bg') {
ct.translation = {
ct.translation = {

'Changing text in wikEd is not yet supported.':
'Changing text in wikEd is not yet supported.':
'Променянето на текст в wikEd още не се поддържа.',
'Променянето на текст в wikEd още не се поддържа.',
Ред 88: Ред 113:
};
};
}
}



ct.rules = ct.rules || [];
ct.rules = ct.rules || [];


ct.rules.push(function (s) {
ct.rules.push(function (s) {
var re = /\[\[([{letter} ,\(\)\-]+)\|\1([{letter}]+)?\]\]/g;
let re = /\[\[([{letter}]?)([^\|\[\]]+)\|(['„\(]*)([{letter}]?)\2([{letter}]*)(['“\).:,;]*)\]\]/g;
re = ct.fixRegExp(re);
re = ct.fixRegExp(re);
var a = ct.getAllMatches(re, s);
let a = ct.getAllMatches(re, s);
for (var i = 0; i < a.length; i++) {
let b = [];
for (let i = 0; i < a.length; i++) {
var m = a[i];
let m = a[i];
var ext1 = m[2] === undefined ? '' : 'Б';
m[1] = m[1] || '';
var ext2 = m[2] === undefined ? '' : m[2];
m[4] = m[4] || '';
if (m[1].toLowerCase() !== m[4].toLowerCase()) continue;
a[i] = {
m[3] = m[3] || '';
m[5] = m[5] || '';
m[6] = m[6] || '';
b.push({
start: m.start,
start: m.start,
end: m.end,
end: m.end,
replacement: '[[' + m[1] + ']]' + ext2,
replacement: m[3] + '[[' + m[4] + m[2] + ']]' + m[5] + m[6],
name: 'А|А' + ext1,
name: 'А|' + (m[3] ? '..' : '') + 'А' + (m[5] ? 'Б' : '') + (m[6] ? '..' : ''),
description: '„[['+ m[1] +'|'+ m[1] + ext2 + ']]“ може да се опрости до „[[' + m[1] +']]' + ext2 + '“.',
description: '„' + m[0] + '“ може да се опрости до „' + m[3] + '[[' + m[4] + m[2] + ']]' + m[5] + m[6] + '“.',
help: 'Синтаксисът на МедияУики позволява препратки от вида „<kbd>[[А|А' + ext1 + ']]</kbd>“ да се пишат като „<kbd>[[А]]' + ext1 + '</kbd>“.'
help: 'Синтаксисът на МедияУики позволява препратки от вида „<kbd>[[А|' + (m[3] ? '...' : '')
+ 'А' + (m[5] ? 'Б' : '') + (m[6] ? '...' : '') + ']]</kbd>“ да се пишат като „'
};
+ (m[3] ? '..' : '') + '[[А]]' + (m[5] ? 'Б' : '') + (m[6] ? '..' : '') + '“.'
});
}
}
return a;
return b;
});
});


ct.rules.push(function (s) {
ct.rules.push(function (s) {
function doNotFixSpaces(str, index) {
var preTagRE = /<\/?pre\b/i;
var sourceTagRE = /<\/?(source|syntaxhighlight)\b/i;
let nextTagPos = str.slice(index).search(/<\/?(source|syntaxhighlight|pre)\b/i);
let wikiPre = str.slice(0, index + 1).search(/(?:^|\n) +.*$/);
function doNotFixSpaces(s, index, re) {
return (nextTagPos > -1 && str.charAt(index + nextTagPos + 1) === '/' || wikiPre > -1);
var nextTagPos = s.slice(index).search(re);
}
if (nextTagPos >= 0 && s.charAt(index + nextTagPos + 1) == '/') return true;
return false;
}


var start = -1, end = 0;
let start = -1, end = 0;
let replacement = s.replace(/[^\S\r\n]+$/gm, function (m, index, str) {
var replacement;
// Remove end-of-line spaces
var spacesRemoved1 = 0;
let prev2chars = str.slice(index - 2, index);
var spacesRemoved2 = 0;
// don't rm EOL-space in empty table cells (after |) and in empty template param vals (after =)

// but after headings, yes (after ==)
// Remove end-of-line spaces
if (prev2chars[1] === '=' && prev2chars !== '==' || prev2chars[1] === '|') return m;
replacement = s.replace(/ +$/gm, function (m, index, s) {
if (start === -1) start = index;
var prev2chars = s.slice(index - 2, index);
end = index;
// don't rm EOL-space in empty table cells (after |) and in empty template param vals (after =)
return '';
// but after headings, yes (after ==)
}).replace(/[^\s][^\S\r\n]{2,}(?=[^\s=]|==)/g, function (m, index, str) {
if (prev2chars[1] == '|' || ( prev2chars[1] == '=' && prev2chars != '==' )) return m;
// Remove double spaces
if ( doNotFixSpaces(s, index, preTagRE) ) return m;
if (doNotFixSpaces(str, index)) return m;
if (start == -1) start = index;
end = index + m.length;
if (start === -1 || start > index + 1) start = index + 1;
spacesRemoved1 += m.length;
if (end < index + 2) end = index + 2;
return m[0] + ' ';
return '';
});
});
let spacesRemoved = s.length - replacement.length;

if (spacesRemoved === 0) return [];
end = end - spacesRemoved1;
return [{

start: start,
// Remove double spaces
end: end + spacesRemoved,
replacement = replacement.replace(/([^\s])([ \u00a0]{2,})(?=[^ =]|==)/g, function (m, $1, $2, index, s) {
replacement: replacement.slice(start, end),
var repl;
name: (spacesRemoved === 1 ? 'интервал' : spacesRemoved + ' интервала'),
if ( doNotFixSpaces(s, index, sourceTagRE) || doNotFixSpaces(s, index, preTagRE) ) {
description: 'Изтрий двойните интервали и интервалите в края на редовете',
repl = m;
help: 'Двойните интервали и интервалите в края на редовете са ненужни.'
}
}];
else {
repl = $1 + ' ';
if (start == -1 || start > index + 1) start = index + 1;
if (index + m.length > end) end = index + m.length;
spacesRemoved2 += $2.length - 1;
}
return repl;
});

end = end - spacesRemoved2;

var spacesRemoved = spacesRemoved1 + spacesRemoved2; // == s.length - replacement.length;

if (spacesRemoved === 0) return [];

replacement = replacement.slice(start, end);

var a = [{
start: start,
end: end + spacesRemoved,
replacement: replacement,
name: (spacesRemoved == 1 ? 'интервал' : spacesRemoved + ' интервала'),
description: 'Изтрий двойните интервали и интервалите в края на редовете',
help: 'Двойните интервали и интервалите в края на редовете са ненужни.'
}];

return a;
});
});


ct.rules.push(function (s) {
ct.rules.push(function (s) {
// [^|] - пропусни ако вероятно е за означаване на празна клетка в таблица
// [^|] - пропусни ако вероятно е за означаване на празна клетка в таблица
var re = /[^|]([ \u00a0]+|&nbsp;)[-\u2014] +/g;
let re = /[^|]([ \u00a0]+|&nbsp;)[-\u2014][ \u00a0]+/g;
var a = ct.getAllMatches(re, s);
let a = ct.getAllMatches(re, s);
var b = [];
let b = [];
for (var i = 0, l = a.length; i < l; i++) {
for (let i = 0; i < a.length; i++) {
var m = a[i];
let m = a[i];
if ( ct.doNotFix(s, m) ) {
if (ct.doNotFix(s, m)) continue;
continue;
}
b.push({
b.push({
start: m.start + 1,
start: m.start + 1,
Ред 202: Ред 203:
// не работи при липса на интервал преди цифра защото може да е негативно число
// не работи при липса на интервал преди цифра защото може да е негативно число
// също не работи преди " и" или " или" за случаи като "антропо- и зооморфна пластика"
// също не работи преди " и" или " или" за случаи като "антропо- и зооморфна пластика"
var re = /[^|]([ \u00a0]+|&nbsp;)-[^\s\d-]|[^-| \u00a0\n]-(\n| (?!и |или ))/g;
let re = /[^|]([ \u00a0]+|&nbsp;)-[^\s\d-]|[^-| \u00a0\n]-(\n|[ \u00a0](?!и(?:ли)?[ \u00a0]))/g;
var a = ct.getAllMatches(re, s);
let a = ct.getAllMatches(re, s);
var b = [];
let b = [];
for (var i = 0, l = a.length; i < l; i++) {
for (let i = 0; i < a.length; i++) {
var m = a[i];
let m = a[i];
if ( ct.doNotFix(s, m) ) continue;
if (ct.doNotFix(s, m)) continue;
b.push({
b.push({
start: m.start + 1,
start: m.start + 1,
Ред 221: Ред 222:


ct.rules.push(function (s) {
ct.rules.push(function (s) {
var re = /[^\d\wА-я–-](\d+|\[\[\d+\]\])(?:-|\u2014|--|\u2013)(\d+|\[\[\d+\]\]|\?|\.{3}|…)[^\d\wА-я–-]/g;
let re = /[^\dA-zА-я–-](\d+(?:\]\])?)(?:[-\u2013\u2014]|--)((?:\[\[)?\d+|\?|\.{3}|…)[^\dA-zА-я–-]/g;
// U+2014 is mdash, U+2013 is ndash
// U+2014 is mdash, U+2013 is ndash
re = ct.fixRegExp(re);
let a = ct.getAllMatches(re, s);
let b = [];
var a = ct.getAllMatches(re, s);
var b = [];
for (let i = 0; i < a.length; i++) {
let m = a[i];
for (var i = 0; i < a.length; i++) {
if (ct.doNotFix(s, m) || (m[1].length == 3 && m[2].length == 10)) continue; // don't change ISBN-13
var m = a[i];

if ( ct.doNotFix(s, m) || (m[1].length == 3 && m[2].length == 10) ) continue; // don't change ISBN-13

b.push({
b.push({
start: m.start + 1,
start: m.start + 1,
Ред 243: Ред 241:


ct.rules.push(function (s) {
ct.rules.push(function (s) {
var re = /^(==+)( *)([^=]*[^= ])( *)\1/gm;
let re = /^(={2,})([^=\n]*(?:=[^=\n]+)*)(={2,})$/gm;
var a = ct.getAllMatches(re, s);
let a = ct.getAllMatches(re, s);
var b = [];
let b = [];
var level = 0; // == Level 1 ==, === Level 2 ===, ==== Level 3 ====, etc.
let level = 0; // == Level 1 ==, === Level 2 ===, ==== Level 3 ====, etc.
var editform = document.getElementById('editform');
let editform = document.getElementById('editform');
// If we are editing a section, we have to be tolerant to the first heading's level
// If we are editing a section, we have to be tolerant to the first heading's level
var isSection = editform &&
let isSection = editform && editform.wpSection !== null && editform.wpSection.value !== '';
for (let i = 0; i < a.length; i++) {
editform['wpSection'] != null &&
let m = a[i];
editform['wpSection'].value != '';
for (var i = 0; i < a.length; i++) {
if (!m[2] || m[2].trim() === '') {
var m = a[i];
if (m[2] != ' ' || m[4] != ' ') {
b.push({
b.push({
start: m.start,
start: m.start,
end: m.end,
end: m.end,
replacement: m[1] + ' ' + m[3] + ' ' + m[1],
replacement: '',
name: 'заглавие-стил',
name: 'празно заглавие',
description: 'Поправи интервалите',
description: 'Премахни празно заглавие',
help: 'Празните заглавия са ненужни.'
help: 'Стилът на заглавието трябва да е <kbd>==&nbsp;С интервали&nbsp;==</kbd>.'
});
});
}
} else {
if (!m[2].startsWith(' ') || !m[2].endsWith(' ')) {
var oldLevel = level;
b.push({
level = m[1].length - 1;
start: m.start,
if (level - oldLevel > 1 && (!isSection || oldLevel > 0) ) {
end: m.end,
var h = '======='.substring(0, oldLevel + 2);
replacement: m[1] + ' ' + m[2].trim() + ' ' + m[3],
b.push({
name: 'заглавие-стил',
start: m.start,
description: 'Поправи интервалите',
end: m.end,
help: 'Стилът на заглавието трябва да е <kbd>==&nbsp;С интервали&nbsp;==</kbd>.'
//replacement: h + m[2] + m[3] + m[2] + h,
});
name: 'заглавие-вложеност',
}
description: 'Поправи ръчно неправилната вложеност, провери ръчно и следващите подзаглавия',
let oldLevel = level;
help: 'Всяко заглавие трябва да е вложено точно едно ниво под по-общото заглавие.'
level = m[1].length - 1;
});
if (level - oldLevel > 1 && (!isSection || oldLevel > 0) ||
}
m[1].length > 6 ||
var frequentMistakes = [
m[3].length > 6 ||
// { code: 'външни вр.', wrong: /^[Вв]ъншни *[Вв]ръзки$/i, correct: 'Външни препратки' },
m[1].length !== m[3].length
// { code: 'see-also', wrong: /^see *al+so$/i, correct: 'See also' },
) {
// { code: 'ext-links', wrong: /^external links?$/i, correct: 'External links' },
b.push({
// { code: 'refs', wrong: /^ref+e?r+en(c|s)es?$/i, correct: 'References' }
start: m.start,
];
end: m.end,
for (var j = 0; j < frequentMistakes.length; j++) {
name: 'заглавие-вложеност',
var fm = frequentMistakes[j];
description: 'Поправи ръчно неправилната вложеност, провери ръчно и следващите подзаглавия',
if (fm.wrong.test(m[3]) && m[3] != fm.correct) {
help: 'Всяко заглавие трябва да е вложено точно едно ниво под по-общото заглавие.'
var r = m[1] + m[2] + fm.correct + m[2] + m[1];
if (r != m[0]) {
});
b.push({
start: m.start,
end: m.end,
replacement: r,
name: fm.code,
description: 'Поправи на „' + fm.correct + "“.",
help: 'Правилното изписване е „<kbd>' + fm.correct + "</kbd>“."
});
}
}
}
// let frequentMistakes = [
// { code: 'външни вр.', wrong: /^[Вв]ъншни *[Вв]ръзки$/i, correct: 'Външни препратки' },
// { 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 (let j = 0; j < frequentMistakes.length; j++) {
// let fm = frequentMistakes[j];
// if (fm.wrong.test(m[3]) && m[3] != fm.correct) {
// let r = m[1] + m[2] + fm.correct + m[2] + m[1];
// if (r != m[0]) {
// b.push({
// start: m.start,
// end: m.end,
// replacement: r,
// name: fm.code,
// description: 'Поправи на „' + fm.correct + "“.",
// help: 'Правилното изписване е „<kbd>' + fm.correct + "</kbd>“."
// });
// }
// }
// }
}
}
}
}
Ред 305: Ред 314:
ct.rules.push(function (s) {
ct.rules.push(function (s) {
// ISBN: ten or thirteen digits, each digit optionally followed by a hyphen, the last digit can be 'X' or 'x'
// ISBN: ten or thirteen digits, each digit optionally followed by a hyphen, the last digit can be 'X' or 'x'
var a = ct.getAllMatches(/ISBN *=? *(([0-9Xx]-?)+)/gi, s);
let a = ct.getAllMatches(/ISBN *=? *(([0-9Xx]-?)+)/gi, s);
var b = [];
let b = [];
for (var i = 0; i < a.length; i++) {
for (let i = 0; i < a.length; i++) {
var m = a[i];
let m = a[i];
var s = m[1].replace(/[^0-9Xx]+/g, '').toUpperCase(); // remove all non-digits
let str = m[1].replace(/[^0-9Xx]+/g, '').toUpperCase(); // remove all non-digits
if (s.length !== 10 && s.length !== 13) {
if (str.length !== 10 && str.length !== 13) {
b.push({
b.push({
start: m.start,
start: m.start,
Ред 317: Ред 326:
description: 'Трябва да е дълъг 10 или 13 цифри',
description: 'Трябва да е дълъг 10 или 13 цифри',
help: 'ISBN номерата трябва да са дълги 10 или 13 цифри. '
help: 'ISBN номерата трябва да са дълги 10 или 13 цифри. '
+ 'Този се състои от ' + s.length + ' цифри:<br><kbd>' + m[1] + '</kbd>'
+ 'Този се състои от ' + str.length + ' цифри:<br><kbd>' + m[1] + '</kbd>'
});
});
continue;
continue;
}
}
var isNew = (s.length === 13); // old (10 digits) or new (13 digits)
let isNew = (str.length === 13); // old (10 digits) or new (13 digits)
var xIndex = s.indexOf('X');
let xIndex = str.indexOf('X');
if (xIndex !== -1 && (xIndex !== 9 || isNew)) {
if (xIndex !== -1 && (xIndex !== 9 || isNew)) {
b.push({
b.push({
Ред 334: Ред 343:
continue;
continue;
}
}
var computedChecksum = 0;
let computedChecksum = 0;
var modulus = isNew ? 10 : 11;
let modulus = isNew ? 10 : 11;
for (var j = s.length - 2; j >= 0; j--) {
for (let j = str.length - 2; j >= 0; j--) {
var digit = s.charCodeAt(j) - 48; // 48 is the ASCII code of '0'
let digit = str.charCodeAt(j) - 48; // 48 is the ASCII code of '0'
var quotient = isNew
let quotient = isNew
? ((j & 1) ? 3 : 1) // the new way: 1 for even, 3 for odd
? ((j & 1) ? 3 : 1) // the new way: 1 for even, 3 for odd
: 10 - j; // the old way: 10, 9, 8, etc
: 10 - j; // the old way: 10, 9, 8, etc
computedChecksum = (computedChecksum + (quotient * digit)) % modulus;
computedChecksum = (computedChecksum + (quotient * digit)) % modulus;
}
}
computedChecksum = (modulus - computedChecksum) % modulus;
computedChecksum = (modulus - computedChecksum) % modulus;
var c = s.charCodeAt(s.length - 1) - 48;
let c = str.charCodeAt(str.length - 1) - 48;
var actualChecksum = (c < 0 || 9 < c) ? 10 : c;
let actualChecksum = (c < 0 || 9 < c) ? 10 : c;
if (computedChecksum === actualChecksum) {
if (computedChecksum === actualChecksum) continue;
continue;
}
b.push({
b.push({
start: m.start,
start: m.start,
Ред 361: Ред 368:


ct.rules.push(function (s) {
ct.rules.push(function (s) {
var re = / й[^А-я\w]/g;
let re = / й[^А-яA-z\d]/g;
re = ct.fixRegExp(re);
let a = ct.getAllMatches(re, s);
let b = [];
var a = ct.getAllMatches(re, s);
for (var i = 0; i < a.length; i++) {
for (let i = 0; i < a.length; i++) {
var m = a[i];
let m = a[i];
if ( ct.doNotFix(s, m) ) continue;
if (ct.doNotFix(s, m)) continue;
a[i] = {
b.push({
start: m.start + 1,
start: m.start + 1,
end: m.end - 1,
end: m.end - 1,
Ред 375: Ред 382:
help: 'Когато се ползва като местоимение, „й“ трябва да се изписва '
help: 'Когато се ползва като местоимение, „й“ трябва да се изписва '
+ 'като „ѝ“ с ударение.'
+ 'като „ѝ“ с ударение.'
};
});
}
}
return a;
return b;
});
});


ct.rules.push(function (s) {
ct.rules.push(function (s) {
// год. предшествано от цифри, евентуално оградени с [[ и ]]
// единица, предшествана от цифри, евентуално в препратка
var re = /(\[\[[0-9]+\]\]|[0-9]+)([ \u00a0]+|&nbsp;)?\u0433\u043e\u0434\./g;
let re = /[-яA-z\d](\d+(?:\]\])?)((?:[ \u00a0]|&nbsp;)+)?(г(?:од)?\.|лв\.|щ\\.|(?:[мк]?г|[мск]?м|[mk]?g|[mck]?m)(?![А-яA-z\d]))/g;
let autofix = ['г.', 'лв.', 'щ.д.'];
var a = ct.getAllMatches(re, s);
let a = ct.getAllMatches(re, s);
for (var i = 0; i < a.length; i++) {
var m = a[i];
let b = [];
for (let i = 0; i < a.length; i++) {
if ( ct.doNotFix(s, m) ) continue;
a[i] = {
let m = a[i];
if (ct.doNotFix(s, m)) continue;
start: m.start,
let number = m[1];
end: m.end,
replacement: m[1] + '\u00a0г.',
let spacing = m[2] || '';
name: 'год.→г.',
let unit = m[3] === 'год.' ? 'г.' : m[3];
let autofixThis = $.inArray(unit, autofix) > -1;
description: 'год.→г.',
if (m[3] === 'год.' ||
help: 'Приетото съкращение за година е „г.“, а не „год.“'
(autofixThis && spacing !== ' ' && spacing !== '\u00a0' && spacing !== '&nbsp;') ||
};
(!autofixThis && spacing === '')
}
) {
return a;
});

ct.rules.push(function (s) {
var re = /(\[\[[0-9]+\]\]|[0-9]+)( +|&nbsp;)?(г\.|лв\.|щ\.д\.|(мг|кг|мм|см|км|mg|kg|mm|cm|km|m|м|g|г)(?![\w\dА-я]))/g;
var autofix = ['г.', 'лв.', 'щ.д.'];
var a = ct.getAllMatches(re, s);
var b = [];
for (var i = 0; i < a.length; i++) {
var m = a[i];
if ( ct.doNotFix(s, m) ) continue;
var number = m[1]; // може да е оградено с [[ и ]]
var spacing = m[2] || '';
var unit = m[3];
var autofixThis = $.inArray(unit, autofix) > -1;
if ( ( autofixThis && spacing !== ' ' ) || ( !autofixThis && spacing == '' ) ) {
b.push({
b.push({
start: m.start,
start: m.start + 1,
end: m.end,
end: m.end,
replacement: ( autofixThis ? number + '\u00a0' + unit : undefined ),
replacement: (autofixThis ? number + '\u00a0' + unit : undefined),
name: 'число+' + unit,
name: 'число+' + unit,
description: 'Добави интервал между числото и ' + unit,
description: 'Добави интервал между числото и единицата ' + unit,
help: 'Между число и ' + unit + ' трябва да се оставя един интервал, '
help: 'Между числото и единицата <i>' + unit + '</i> трябва да се оставя един интервал, '
+ 'за предпочитане непренасящият се <kbd>&amp;nbsp;</kbd> '
+ 'за предпочитане непренасящият се <kbd>&amp;nbsp;</kbd> '
+ '(non-breaking space, <kbd>U+00A0</kbd>).'
+ '(non-breaking space, <kbd>U+00A0</kbd>).'
Ред 428: Ред 420:


ct.rules.push(function (s) {
ct.rules.push(function (s) {
var re = /([А-я\]\)“]+)( , ?|,)(?=[А-я\[\(„])/g;
let re = /(([{letter}\d])[\]\)“']*)(?:[^\S\r\n]+,[^\S\r\n]*|,)([\[\(„']*([{letter}\d]))/g;
re = ct.fixRegExp(re);
re = ct.fixRegExp(re);
var a = ct.getAllMatches(re, s);
let a = ct.getAllMatches(re, s);
var b = [];
let b = [];
for (var i = 0; i < a.length; i++) {
for (let i = 0; i < a.length; i++) {
var m = a[i];
let m = a[i];
let m1 = { 'start': m.start + m[1].length, 'end': m.end - m[3].length };
if (ct.inBrackets(s, m, ['[', ']']) || ct.inBrackets(s, m, ['{', '}'])) {
if (ct.doNotFix(s, m, false, false, true, true, true, false) ||
continue;
ct.inBrackets(s, m1, ['[', ']']) ||
}
ct.inBrackets(s, m1, ['{', '}']) ||
b.push({
!isNaN(m[2]) && !isNaN(m[4]) // m[2] и m[4] са цифри; вероятност за десетично число
start: m.start + m[1].length,
) continue;
end: m.end,
b.push({
replacement: m[2].trim() + ' ',
start: m1.start,
name: 'запетая',
end: m1.end,
description: 'Премахни интервала преди запетаята и/или добави такъв след нея',
replacement: ', ',
help: 'Интервалът трябва да е след запетаята и не преди нея.'
name: 'запетая',
});
description: 'Премахни интервала преди запетаята и/или добави такъв след нея',
}
help: 'Интервалът трябва да е след запетаята и не преди нея.'
return b;
});
}
return b;
});
});


ct.rules.push(function (s) {
ct.rules.push(function (s) {
var re = /( [а-я]{2,})( \. ?|\.)(?=[А-Я][а-я]+)/g;
let re = /(([{letter}\d])[\]\)“']*)(?:[^\S\r\n]+\.[^\S\r\n]*|\.)([\[\(„']*([{letter}\d]))/g;
re = ct.fixRegExp(re);
re = ct.fixRegExp(re);
var a = ct.getAllMatches(re, s);
let a = ct.getAllMatches(re, s);
var b = [];
let b = [];
for (var i = 0; i < a.length; i++) {
for (let i = 0; i < a.length; i++) {
var m = a[i];
let m = a[i];
let m1 = { 'start': m.start + m[1].length, 'end': m.end - m[3].length };
if (ct.inBrackets(s, m, ['[', ']']) || ct.inBrackets(s, m, ['{', '}'])) {
if (ct.doNotFix(s, m, false, false, true, true, true, false) ||
continue;
ct.inBrackets(s, m1, ['[', ']']) ||
}
ct.inBrackets(s, m1, ['{', '}']) ||
b.push({
!isNaN(m[2]) && !isNaN(m[4]) || // m[2] и m[4] са цифри; вероятност за десетично число след копиране
start: m.start + m[1].length,
m[4] !== m[4].toUpperCase() // m[4] не е главна буква
end: m.end,
) continue;
replacement: m[2].trim() + ' ',
b.push({
name: 'точка',
start: m1.start,
description: 'Премахни интервала преди точката в края на изречението и/или добави такъв след нея',
end: m1.end,
help: 'Интервалът трябва да е след точката и не преди нея.'
replacement: '. ',
});
name: 'точка',
}
description: 'Премахни интервала преди точката в края на изречението и/или добави такъв след нея',
return b;
help: 'Интервалът трябва да е след точката и не преди нея.'
});
}
return b;
});
});


ct.rules.push(function (s) {
ct.rules.push(function (s) {
var re = /((=\n{2,}.)|[^=\n]\n=|.\n{3,}.|\.\n[А-я])/g;
let re = /((=\n{2,}.)|[^=\n]\n=|.\n{3,}.|\.\n[А-я])|^\n+/g;
re = ct.fixRegExp(re);
let a = ct.getAllMatches(re, s);
var a = ct.getAllMatches(re, s);
for (let i = 0; i < a.length; i++) {
let m = a[i];
for (var i = 0; i < a.length; i++) {
let lines = m[2] ? '\n' : '\n\n';
var m = a[i];
a[i] = {
var lines = (m[2] === undefined) ? '\n\n' : '\n';
start: m.start,
a[i] = {
end: m.end,
start: m.start,
replacement: m[1] ? m[1][0] + lines + m[1][m.end - m.start - 1] : '',
end: m.end,
name: 'нов ред',
replacement: m[1][0] + lines + m[1][m.end - m.start - 1],
description: 'Премахни излишните празни редове или добави нов ред между отделните абзаци',
name: 'нов ред',
help: 'Между отделните абзаци трябва да има един празен ред. Повече от един празен ред е излишен.'
description: 'Премахни излишните празни редове или добави нов ред между отделните абзаци',
};
help: 'Между отделните абзаци трябва да има един празен ред. Повече от един празен ред е излишен.'
}
};
return a;
}
return a;
});
});


ct.rules.push(function (s) {
ct.rules.push(function (s) {
function replace(latin, cyrillic, str, obj) {
var re = /(([А-я] e [А-я])|[a-z][А-я]|[А-я][a-z])/g;
if (str.indexOf(latin) > -1) {
re = ct.fixRegExp(re);
obj.replacement = str.replace(latin, cyrillic);
var a = ct.getAllMatches(re, s);
obj.description = 'Замени латинско "' + latin + '" с кирилско.';
for (var i = 0; i < a.length; i++) {
}
var m = a[i];
}
a[i] = {

start: m.start,
let re = /[А-я] [ec] [А-я]|[a-z][А-я]|[А-я][a-z]/g;
end: m.end,
let a = ct.getAllMatches(re, s);
replacement: null,
for (let i = 0; i < a.length; i++) {
name: '6lokavica',
let m = a[i];
description: 'Неизвестна замяна. Проверете текста.',
a[i] = {
help: 'Една дума трябва да бъде написана или само на кирилица или само на латиница.'
start: m.start,
};
end: m.end,
replacement: null,
function replace(latin, cyrillic) {
name: '6lokavica',
a[i].replacement = m[1].replace(latin, cyrillic);
description: 'Неизвестна замяна. Проверете текста.',
a[i].description = 'Замени латинско "' + latin + '" с кирилско.';
help: 'Една дума трябва да бъде написана или само на кирилица или само на латиница.'
}
};
if (m[2] !== undefined) replace('e', 'е');
replace('a', 'а', m[0], a[i]);
replace('e', 'е', m[0], a[i]);
else {
replace('o', 'о', m[0], a[i]);
if (m[1].indexOf('a') > -1) replace('a', 'а');
replace('x', 'х', m[0], a[i]);
else if (m[1].indexOf('e') > -1) replace('e', 'е');
replace('p', 'р', m[0], a[i]);
else if (m[1].indexOf('o') > -1) replace('o', 'о');
replace('c', 'с', m[0], a[i]);
else if (m[1].indexOf('x') > -1) replace('x', 'х');
}
else if (m[1].indexOf('p') > -1) replace('p', 'р');
return a;
else if (m[1].indexOf('c') > -1) replace('c', 'с');
}
}
return a;
});
});


ct.rules.push(function (s) {
ct.rules.push(function (s) {
// отварящи кавички ако са в нач. на реда или след '', интервали (непредхождани от единично =), ==, *, #, >, }, (, [, «
// отварящи кавички ако са в нач. на реда или в списъчен елемент, след '', интервали (непредхождани от единично =), ==, >, }, |, (
let re = /((?:^|\n)[*#:;]*|''|[^=\s][^\S\n]+|==[^\S\n]*|[>}|\(])(?:"(?![\s.,;])((?:[^"\[\]\s]|\s(?!")|\[\[[^\[\]]+\]\]|\[[^\[\]]+\])+)"|[„“](?![\s.,;])((?:[^„“”\[\]\s]|\s(?![“”])|\[\[[^\[\]]+\]\]|\[[^\[\]]+\])+)”|«(?!\s)((?:[^«»\[\]\s]|\s(?!»)|\[\[[^\[\]]+\]\]|\[[^\[\]]+\])+)»)/g;
// за "" (но не и за “”) също работи когато има "„“ кавички в адреса на връзка вътре в кавички
let a = ct.getAllMatches(re, s);
var re = /(\n|''|[^= ] +|== *|[*#>}|(\[«])(?:"(?![\s.,;])((?:[^"„“\[]|\[\[[^|\]"„“]+\]|\[\[[^\]|]+\|)*[^"„“\s(\[«])"|[“„](?![\s.,;])([^"„“]*[^"„“\s(\[«])”|[«](?![\s.,;])([^"„“«»]*[^"„“\s(\[«»])»)/g;
let b = [];
var a = ct.getAllMatches(re, s);
for (let i = 0; i < a.length; i++) {
var b = [];
let m = a[i];
for (var i = 0, l = a.length; i < l; i++) {
var m = a[i];
let str = m[2] || m[3] || m[4];
if ( ct.doNotFix(s, m) ) {
if (ct.doNotFix(s, m, true, true, true, true, true, false) ||
str.search(/[а-ъюяѝ]|ь(?=о)/i) === -1 || // не съдържа нито една кирилска буква, ползвана в българския
continue;
str.search(/(?![а-ъюяѝ]|ь(?=о))[\u0400-\u04ff]/i) > -1 // или пък съдържа и други кирилски букви, които не се ползват в българския
}
) continue;
b.push({
b.push({
start: m.start + m[1].length,
start: m.start + m[1].length,
end: m.end,
end: m.end,
replacement: '„' + (m[2] || m[3] || m[4]) + '“',
replacement: '„' + str + '“',
name: 'кавички',
name: 'кавички',
description: 'Заместване на "прави", “други горни”, „смесени” или «френски» с „български“ кавички.',
description: 'Заместване на "прави", “други горни”, „смесени” или «френски» с „български“ кавички.',
Ред 546: Ред 542:
});
});


// Премахването на празни параметри от шаблоните като практика не се ползва с
/*
// консенсусна подкрепа сред редакторите, затова на този етап е изключено.
Премахването на празни параметри от шаблоните като практика не се ползва с
консенсусна подкрепа сред редакторите, затова на този етап е изключено.
*/
/*
/*
ct.rules.push(function (s) {
ct.rules.push(function (s) {
var re = /(^|[^\n ] *)(\| *[\wА-я-]+ *= *(?=[\|\}]))+/g;
let re = /(^|[^\n ] *)(\| *[\wА-я-]+ *= *(?=[\|\}]))+/g;
var a = ct.getAllMatches(re, s);
let a = ct.getAllMatches(re, s);
if (a.length === 0) return [];
if (a.length === 0) return [];
var n = a.length;
let n = a.length;
var start = a[0].start + a[0][1].length;
let start = a[0].start + a[0][1].length;
var end = a[n - 1].end + 1;
let end = a[n - 1].end + 1;
let replacement = s.slice(start, end).replace(re, '$1');

let b = [{
var replacement = s.slice(start, end).replace(re, '$1');
start: start,

end: end - 1,
var b = [{
replacement: replacement.slice(0, -1),
start: start,
name: (n == 1 ? 'параметър' : n + '+ параметъра'),
end: end - 1,
description: 'Премахва неизползваните параметри от шаблоните',
replacement: replacement.slice(0, -1),
help: 'Неизползваните параметри са излишни.'
name: (n == 1 ? 'параметър' : n + '+ параметъра'),
}];
description: 'Премахва неизползваните параметри от шаблоните',
return b;
help: 'Неизползваните параметри са излишни.'
}];
return b;
});
});
*/
*/


ct.rules.push(function (s) {
ct.rules.push(function (s) {
var skipNext = 0;
let skipNext = 0;
var decoder = function (match, charCode, index, s) {
let decoder = function (match, charCode, index, s) {
if (skipNext > 0) {
if (skipNext > 0) {
skipNext--;
skipNext--;
return '';
return '';
}
}
let decimal = parseInt(charCode, 16);
let bin = Number(decimal).toString(2);
if (decimal < 128) return match; // ASCII, don't decode
let nOfBytes = bin.match(/^1+/)[0].length;
skipNext = nOfBytes - 1;
let urlEncoded = match + s.slice(index + 3, index + 3 * nOfBytes);
let char = decodeURI(urlEncoded);
return (char.length == 1 ? char : urlEncoded);
};


let re = /(https?:\/\/[^\/\s]+\/)([^\s|}<>\]]*)/g;
var decimal = parseInt(charCode, 16);
let a = ct.getAllMatches(re, s);
var bin = Number(decimal).toString(2);
let b = [];
if ( decimal < 128 ) return match; // ASCII, don't decode
let decoded;

for (let i = 0; i < a.length; i++) {
var nOfBytes = bin.match(/^1+/)[0].length;
let m = a[i];
skipNext = nOfBytes - 1;
try {

decoded = m[2].replace(/%([A-F\d]{2})/gi, decoder);
var urlEncoded = match + s.slice(index + 3, index + 3 * nOfBytes);
if (m[2] === decoded) continue;
b.push({
start: m.start,
end: m.end,
replacement: m[1] + decoded,
name: 'URL',
description: 'Декодира кодирани URL адреси',
help: 'URL адресите се четат по-лесно когато са декодирани.'
});
} catch (e) {
// не е кодиран Unicode текст
}
}
return b;
});


ct.rules.push(function (s) {
var char = decodeURI(urlEncoded);
let re = /\([^\S\r\n]+|[^\S\r\n]+\)/g;
return (char.length == 1 ? char : urlEncoded);
let a = ct.getAllMatches(re, s);
}
for (let i = 0; i < a.length; i++) {

let m = a[i];
var re = /(https?:\/\/[^\/ ]+\/)(((?![ \n\|\]\}><]).)*)/g;
if (ct.doNotFix(s, m)) continue;
var a = ct.getAllMatches(re, s);
a[i] = {
var b = [];
start: m.start,
var decoded;
end: m.end,
for (var i = 0; i < a.length; i++) {
replacement: m[0].trim(),
var m = a[i];
name: 'скоба',
try {
description: 'Премахни интервала след отварящата и/или преди затварящата скоба',
decoded = m[2].replace(/%([0-9A-Fa-f]{2})/g, decoder);
help: 'Интервалите са ненужни след отваряща и преди затваряща скоба.'
if (m[2] === decoded) continue;
};
b.push({
}
start: m.start,
return a;
end: m.end,
replacement: m[1] + decoded,
name: 'URL',
description: 'Декодира кодирани URL адреси',
help: 'URL адресите се четат по-лесно когато са декодирани.'
});
}
catch (e) {
// не е кодиран Unicode текст
}
}
return b;
});
});


ct.rules.push(function (s) {
ct.rules.push(function (s) {
let re = /((?:[^А-яA-z\d](?:[Оо]т|[Дд]о|[Пп]ре(?:з|ди)|[Сс]лед|[Мм]е(?:жду|сец)|[Ии](?:ли)?|[Вв])|[\d,])[ \u00a0])(Януари|Февруари|Март|Април|Май|Юни|Юли|Август|Септември|Октомври|Ноември|Декември)[^А-яA-z\d]/g;
var re = /([А-я“"]+)(\(|\)| ?\( | \) ?)(?=[А-я\d]+)/g;
re = ct.fixRegExp(re);
let a = ct.getAllMatches(re, s);
let b = [];
var a = ct.getAllMatches(re, s);
for (var i = 0; i < a.length; i++) {
for (let i = 0; i < a.length; i++) {
var m = a[i];
let m = a[i];
if ( ct.doNotFix(s, m) ) continue;
if (ct.doNotFix(s, m)) continue;
b.push({
a[i] = {
start: m.start + m[1].length,
start: m.start + m[1].length,
end: m.end,
end: m.end - 1,
replacement: m[2].toLowerCase(),
// replacement: m[2].indexOf('(') != -1 ? ' (' : ') ', // чупи параметри в Шаблон:ТВ продукция
name: 'скоба',
name: 'месец',
description: m[2] + ' → ' + m[2].toLowerCase(),
description: 'Добави/премахни интервала преди/след отварящата/затварящата скоба',
help: 'В българския език имената на месеците се пишат с малка буква.'
help: 'Преди отваряща и след затваряща скоба трябва да има интервал. Интервалите са ненужни след отваряща и преди затваряща скоба.'
});
};
}
}
return a;
return b;
});
});


ct.rules.push(function (s) {
ct.rules.push(function (s) {
let re = /№(\d+)/g;
var re = /[0-9] (Януари|Февруари|Март|Април|Май|Юни|Юли|Август|Септември|Октомври|Ноември|Декември)[^А-я]/g;
var a = ct.getAllMatches(re, s);
let a = ct.getAllMatches(re, s);
var m, replacement, b = [];
let b = [];
for (var i = 0; i < a.length; i++) {
for (let i = 0; i < a.length; i++) {
m = a[i];
let m = a[i];
if ( ct.doNotFix(s, m) ) continue;
if (ct.doNotFix(s, m)) continue;
b.push({
replacement = m[1][0].toLowerCase() + m[1].slice(1);
start: m.start,
b.push({
end: m.end,
start: m.start + 2,
replacement: '№\u00a0' + m[1],
end: m.end - 1,
name: '№+число',
replacement: replacement,
description: 'Добави несекаем интервал между № и последващите числа',
name: 'месец',
help: 'Между символа за номер и последващите числа трябва да има несекаем интервал.'
description: m[1] + ' -> ' + replacement,
});
help: 'В българския език имената на месеците се пишат с малка буква.'
}
});
return b;
}
return b;
});
});


ct.rules.push(function (s) {
window.ct = ct;
function skipReferenceTags(str, index) {
let sliced = str.slice(0, index);
let tag1Pos = sliced.search(/<references\b(?:(?=([^/>]+))\1|\/(?!>))*>(?:(?=([^<]+))\2|<(?!\/references\b))*$/i);
let tag2Pos = sliced.search(/\{\{\s*[Rr]eflist\b(?:(?=([^{}]+))\1|\{(?:(?=([^{}]+))\2|\{(?:(?=([^{}]+))\3|\{(?:(?=([^{}]+))\4|\{(?:(?=([^{}]+))\5|\{(?:(?=([^{}]+))\6|\{(?=([^{}]*))\7\})*\})*\})*\})*\})*\})*$/);
return (tag1Pos > -1 || tag2Pos > -1);
}


let start = -1, end = 0;
if ($.inArray(mw.config.get('wgCanonicalNamespace'), ['MediaWiki', 'Template', 'Module']) === -1) {
let noChange = true;
mw.loader.using( 'ext.gadget.Advisor', function () {
let replacement = s.replace(/(<\/[Rr][Ee][Ff]\s*>|<[Rr][Ee][Ff]\b[^>]*\/>|\{\{\s*(?:[SsEe]fn|[Hh]rf|[Rr]p)\b[^{}]*\}\})[\s.:,;]+(?=<[Rr][Ee][Ff]\b|\{\{\s*(?:[SsEe]fn|[Hh]rf|[Rr]p)\b)/g, function (m, g1, index, str) {
mw.loader.load('//bg.wikipedia.org/w/index.php?title=МедияУики:Gadget-Advisor-core.js&action=raw&ctype=text/javascript');
// Remove punctuation chars and spaces between refs
if (skipReferenceTags(str, index)) return m;
if (start === -1) start = index;
end = index + g1.length;
noChange = false;
return g1;
}).replace(/([^\s.:,;=]\s+|\s*[.,]\s+|[^\S\r\n]*[:;][^\S\r\n]+)(?:<[Rr][Ee][Ff]\b|\{\{\s*(?:[SsEe]fn|[Hh]rf|[Rr]p)\b)|(?:<\/[Rr][Ee][Ff]\s*>|<[Rr][Ee][Ff]\b[^>]*\/>|\{\{\s*(?:[SsEe]fn|[Hh]rf|[Rr]p)\b[^{}]*\}\})(?:\s+(?=[.,])|[^\S\r\n]+(?=[:;]))/g, function (m, g1, index, str) {
// Remove spaces before ref/after ref and between punctuation chars
if (skipReferenceTags(str, index)) return m;
if (g1) m = g1.trim() + m.slice(g1.length);
m = m.trim();
if (start === -1 || start > index) start = index;
if (end < index + m.length) end = index + m.length;
noChange = false;
return m;
}).replace(/([.:,;])((?:<[Rr][Ee][Ff]\b(?:(?=([^>/]+))\3|\/(?!>))*(?:\/|>(?:(?=([^<]+))\4|<(?!\/?[Rr][Ee][Ff]\b))*<\/[Rr][Ee][Ff]\s*)>|\{\{(?:[SsEe]fn|[Hh]rf|[Rr]p)\b[^{}]*\}\})+)\1/g, function (m, g1, g2, g3, g4, index, str) {
// Remove last punctuation char if equal at start/end position for a group of refs
if (skipReferenceTags(str, index)) return m;
if (start === -1 || start > index) start = index;
if (end < index + (g1 + g2).length) end = index + (g1 + g2).length;
noChange = false;
return g1 + g2;
}).replace(ct.fixRegExp(/(?:<\/[Rr][Ee][Ff]\s*>|<[Rr][Ee][Ff]\b[^>]*\/>|\{\{(?:[SsEe]fn|[Hh]rf|[Rr]p)\b[^{}]*\}\})[.:,;]?(?=[^{letter}\d\s<>{}.:,;]*[{letter}\d])/g), function (m, index, str) {
// Add space if closing ref is followed by a word, only if the word is not positioned after opening/closing angular/curly bracket
if (skipReferenceTags(str, index)) return m;
if (start === -1 || start > index) start = index;
if (end < index + m.length + 1) end = index + m.length + 1;
noChange = false;
return m + ' ';
});
});
if (noChange) return [];
}
let deltaLength = s.length - replacement.length;
return [{
start: start,
end: end + deltaLength,
replacement: replacement.slice(start, end),
name: 'пунктуация+ref',
description: 'Интервалите и/или част от пунктуационните знаци преди, между и след ref-овете са ненужни',
help: 'Изтрий ненужните пунктуационни знаци и интервали преди, между и след ref-овете.'
}];
});

ct.rules.push(function (s) {
let re = ct.fixRegExp(/(^|\s+)([Вв](?:ъв)?|[Сс](?:ъс)?)(\s+(?:[^{letter}\d\s\[\]{}<>-]*\[\[?[^\[\]]+\]|(?:[^\s\[\]{}<>]+\s+){1,2}))/g);
let a = ct.getAllMatches(re, s);
let b = [];
for (let i = 0; i < a.length; i++) {
let m = a[i];
if (ct.doNotFix(s, m)) continue;
let tooltip;
let preposition = m[2];
let text = m[3].replace(/\[(?:(?:https?:|ftps?:)?\/\/\S+|\[[^|\[\]]+\|)/gi, '')
.replace(ct.fixRegExp(/[^{letter}\d\s-]+/g), '')
.replace(/^\s+/, '');
if (preposition.toLowerCase() === 'в' && text.match(/^(?:[ВвФф][А-Яа-я]|2-р|II(?![А-яA-z\d])|и(?:ли)?\s+)/)) {
preposition += 'ъв';
tooltip = '„в“ трябва да стане „във“, тъй като следващата дума започва с „в“ или „ф“, или попада под логическо ударение.';
}
else if (preposition.toLowerCase() === 'във' && !text.match(/^(?:[ВвФфVvFfWw]|2-р|II(?![А-яA-z\d])|и(?:ли)?\s+)/)) {
preposition = preposition[0];
tooltip = '„във“ трябва да стане „в“, тъй като следващата дума не започва с „в“ или „ф“, или не попада под логическо ударение.';
}
else if (preposition.toLowerCase() === 'с' && text.match(/^(?:[СсЗз][А-Яа-я]|(?:1?7(?:\d|\s*\d{3})?|[17]\d\d)(?!\d)|(?:X?VII|LXX[IVX]*|(?:DC)?C[IVXL]*)(?![А-яA-z\d])|и(?:ли)?\s+без\s+)/)) {
preposition += 'ъс';
tooltip = '„с“ трябва да стане „със“, тъй като следващата дума започва със „з“ или „с“, или попада под логическо ударение.';
}
else if (preposition.toLowerCase() === 'със' && !text.match(/^(?:[СсЗзSsZzCc]|(?:1?7(?:\d|\s*\d{3})?|[17]\d\d)(?!\d)|(?:X?VII|LXX[IVX]*|DCC[IVXL]*)(?![А-яA-z\d])|и(?:ли)?\s+без\s+)/)) {
preposition = preposition[0];
tooltip = '„със“ трябва да стане „с“, тъй като следващата дума не започва със „з“ или „с“, или не попада под логическо ударение.';
}
if (tooltip) {
b.push({
start: m.start + m[1].length,
end: m.end - m[3].length,
replacement: preposition,
name: m[2].toLowerCase() + '→' + preposition.toLowerCase(),
description: 'Поправи „' + m[2] + '“ на „' + preposition + '“',
help: tooltip
});
}
}
return b;
});

Версия от 01:59, 10 октомври 2023

// Българска версия на правилата за скрипта Advisor.js
// Виж МедияУики:Gadget-Advisor-core.js за основния скрипт
// виж http://en.wikipedia.org/wiki/User:Cameltrader/Advisor.js/Description

importScript('МедияУики:Gadget-Advisor-core.js');

var ct = ct || {};

ct.inBrackets = function (s, m, brackets) {
	let leftContext = s.substring(0, m.start);
	let rightContext = s.substring(m.end);

	let indexOfOpeningLeft = leftContext.lastIndexOf(brackets[0]);
	let indexOfClosingLeft = leftContext.lastIndexOf(brackets[1]);
	let indexOfOpeningRight = rightContext.indexOf(brackets[0]);
	let indexOfClosingRight = rightContext.indexOf(brackets[1]);

	return (indexOfOpeningLeft !== -1 && (indexOfClosingLeft === -1 || indexOfOpeningLeft > indexOfClosingLeft)) ||
		(indexOfClosingRight !== -1 && (indexOfOpeningRight === -1 || indexOfOpeningRight > indexOfClosingRight));
};

// originally from https://en.wikipedia.org/wiki/User:GregU/dashes.js
// checkPat1, checkPat2, checkTags, checkFileName default to true
ct.doNotFix = function (s, m, checkPat1, checkPat2, checkTags, checkFileName, checkWikiPreCode, checkQuotes) {
	let pos = m.start;
	let pat = /\[\[[^|\]]*$|\{\{[^|}]*$|[:\/%][^\s|>]+$|<[^>]*$|#\w*expr:.*$/i;
	if (checkPat1 !== false && s.substring(pos - 260, pos + 1).search(pat) >= 0)
		return true; // it's a link, so don't change it

	let pat2 = /\{\{\s*(?:#[A-Za-z]+:|друг[ои]\s+значени[ея]|основна|main|към|от\s+пренасочване|категория|anchor|cite|citation|цитат2?|c?quote|is[sb]n|lang[i2]?|[es]fn|hrf|harv|пост\s+списък)(?:(?=([^{}]+))\1|\{(?:(?=([^{}]+))\2|\{(?:(?=([^{}]+))\3|\{(?:(?=([^{}]+))\4|\{(?:(?=([^{}]+))\5|\{(?:(?=([^{}]+))\6|\{(?=([^{}]*))\7\})*\})*\})*\})*\})*\})*$/i; // long and ugly regex because of limited JS regex engine
	if (checkPat2 !== false && s.slice(0, pos + 1).search(pat2) >= 0)
		return true; // specific template/parser function (with up to six balanced curly brackets -- 3 levels of template/parser function nesting or 2 levels of parameter placeholder nesting)

	if (checkTags !== false) {
		let nextTagPos = s.slice(pos).search(/<\/?(chem|math|pre|code|tt|source|syntaxhighlight|timeline|graph|mapframe|maplink|poem|blockquote|q|i|ref)\b/i);
		if (nextTagPos >= 0 && s.charAt(pos + nextTagPos + 1) === '/')
			return true; // skip math/chem equations, source code, timelines, graphs, maps, ref content, content in citation tags
	}

	if (checkFileName !== false && s.slice(pos).search(/^[^|{}[\]<>\n]*\.([a-z]{3,4}\s*([|}\n]|\{\{!\}))/i) >= 0)
		return true; // it's a file name parameter

	if (checkWikiPreCode !== false && s.slice(0, pos + 1).search(/(?:^|\n) +.*$/) >= 0)
		return true; // it's a text in wiki pre code (a line starting with a normal space)

	if (checkQuotes !== false) {
		let leftSlice = s.slice(0, pos + 1).replace(/'''/g, '');
		let rightSlice = s.slice(pos + 1).replace(/'''/g, '');
		if (leftSlice.match(/„(?:(?=([^„“”]+))\1|„[^„“”]*[“”])*$/) &&
			rightSlice.match(/^(?:(?=([^„“”]+))\1|„[^„“”]*[“”])*[“”]/) ||
			leftSlice.match(/“(?:(?=([^“”]+))\1|“[^“”]*”)*$/) &&
			rightSlice.match(/^(?:(?=([^“”]+))\1|“[^“”]*”)*”/) ||
			leftSlice.match(/«(?:(?=([^«»]+))\1|«[^«»]*»)*$/) &&
			rightSlice.match(/^(?:(?=([^«»]+))\1|«[^«»]*»)*»/) ||
			leftSlice.match(/"[^"]*$/) &&
			rightSlice.match(/^[^"]*"/) &&
			leftSlice.match(/"/g).length % 2 === 1 &&
			rightSlice.match(/"/g).length % 2 === 1 ||
			leftSlice.match(/''(?:(?!'').)*$/) &&
			rightSlice.match(/^.*?''/) &&
			leftSlice.match(/''/g).length % 2 === 1 &&
			rightSlice.match(/''/g).length % 2 === 1
		) return true; // it's a text in quotes or italicized text
	}
};

if (mw.config.get('wgUserLanguage') === 'bg') {
	ct.translation = {

'Changing text in wikEd is not yet supported.':
	'Променянето на текст в wikEd още не се поддържа.',

'This article is rather long.  Advisor.js may consume a lot of RAM and CPU resources while trying to parse the text.  You could limit your edit to a single section, or ':
	'Тази статия е твърде дълга. Advisor.js може да консумира много RAM и процесорни ресурси, докато се опитва да анализира текста. Можеш да ограничиш редакцията на един раздел, или да ',

'Advisor.js is disabled on talk pages, because it might suggest changing other users\' comments.  That would be something against talk page conventions.  If you promise to be careful, you can ':
	'Advisor.js по подразбиране е спрян за беседите, защото има опасност да предложи промяна на чужди коментари. Това би било в противоречие с конвенциите за беседи. Ако обещаваш да си внимателен, можеш да ',

'scan the text anyway.':
	'провериш текста.',

'Ignore this warning.':
	'Игнорирай това предупреждение.',

'OK \u2014 Advisor.js found no issues with the text.':
	'ОК \u2014 Advisor.js не намира проблеми в текста.',

'1 suggestion: ':
	'1 предложение: ',

'$1 suggestions: ':
	'$1 предложения: ',

'fix':
	'поправи',

'Show All':
	'Покажи всички',

'formatting: $1 (using [[User:Cameltrader#Advisor.js|Advisor.js]])':
	'форматиране: $1 (ползвайки [[У:Съв|Advisor]])',

'Add to summary':
	'Добави към резюмето',

'Append the proposed summary to the input field below':
	'Добави предложеното резюме към полето отдолу',

'Error: If the proposed text is added to the summary, its length will exceed the $1-character maximum by $2 characters.':
	'Ако предложеният текст бъде добавен към резюмето, дължината му ще прехвърли ограничението от $1 символа с $2.',

'':''
	};
}

ct.rules = ct.rules || [];

ct.rules.push(function (s) {
	let re = /\[\[([{letter}]?)([^\|\[\]]+)\|(['„\(]*)([{letter}]?)\2([{letter}]*)(['“\).:,;]*)\]\]/g;
	re = ct.fixRegExp(re);
	let a = ct.getAllMatches(re, s);
	let b = [];
	for (let i = 0; i < a.length; i++) {
		let m = a[i];
		m[1] = m[1] || '';
		m[4] = m[4] || '';
		if (m[1].toLowerCase() !== m[4].toLowerCase()) continue;
		m[3] = m[3] || '';
		m[5] = m[5] || '';
		m[6] = m[6] || '';
		b.push({
			start: m.start,
			end: m.end,
			replacement: m[3] + '[[' + m[4] + m[2] + ']]' + m[5] + m[6],
			name: 'А|' + (m[3] ? '..' : '') + 'А' + (m[5] ? 'Б' : '') + (m[6] ? '..' : ''),
			description: '„' + m[0] + '“ може да се опрости до „' + m[3] + '[[' + m[4] + m[2] + ']]' + m[5] + m[6] + '“.',
			help: 'Синтаксисът на МедияУики позволява препратки от вида „<kbd>[[А|' + (m[3] ? '...' : '')
				+ 'А' + (m[5] ? 'Б' : '') + (m[6] ? '...' : '') + ']]</kbd>“ да се пишат като „'
				+ (m[3] ? '..' : '') + '[[А]]' + (m[5] ? 'Б' : '') + (m[6] ? '..' : '') + '“.'
		});
	}
	return b;
});

ct.rules.push(function (s) {
	function doNotFixSpaces(str, index) {
		let nextTagPos = str.slice(index).search(/<\/?(source|syntaxhighlight|pre)\b/i);
		let wikiPre = str.slice(0, index + 1).search(/(?:^|\n) +.*$/);
		return (nextTagPos > -1 && str.charAt(index + nextTagPos + 1) === '/' || wikiPre > -1);
	}

	let start = -1, end = 0;
	let replacement = s.replace(/[^\S\r\n]+$/gm, function (m, index, str) {
		// Remove end-of-line spaces
		let prev2chars = str.slice(index - 2, index);
		// don't rm EOL-space in empty table cells (after |) and in empty template param vals (after =)
		// but after headings, yes (after ==)
		if (prev2chars[1] === '=' && prev2chars !== '==' || prev2chars[1] === '|') return m;
		if (start === -1) start = index;
		end = index;
		return '';
	}).replace(/[^\s][^\S\r\n]{2,}(?=[^\s=]|==)/g, function (m, index, str) {
		// Remove double spaces
		if (doNotFixSpaces(str, index)) return m;
		if (start === -1 || start > index + 1) start = index + 1;
		if (end < index + 2) end = index + 2;
		return m[0] + ' ';
	});
	let spacesRemoved = s.length - replacement.length;
	if (spacesRemoved === 0) return [];
	return [{
		start: start,
		end: end + spacesRemoved,
		replacement: replacement.slice(start, end),
		name: (spacesRemoved === 1 ? 'интервал' : spacesRemoved + ' интервала'),
		description: 'Изтрий двойните интервали и интервалите в края на редовете',
		help: 'Двойните интервали и интервалите в края на редовете са ненужни.'
	}];
});

ct.rules.push(function (s) {
	// [^|] - пропусни ако вероятно е за означаване на празна клетка в таблица
	let re = /[^|]([ \u00a0]+|&nbsp;)[-\u2014][ \u00a0]+/g;
	let a = ct.getAllMatches(re, s);
	let b = [];
	for (let i = 0; i < a.length; i++) {
		let m = a[i];
		if (ct.doNotFix(s, m)) continue;
		b.push({
			start: m.start + 1,
			end: m.end,
			replacement: '\u00a0\u2013 ', // U+2013 is an ndash
			name: 'тире',
			description: 'Смени със средно тире (en dash)',
			help: 'В изречение, късо тире оградено с интервали, почти сигурно трябва да е средно тире (en dash).'
		});
	}
	return b;
});

ct.rules.push(function (s) {
	// [^|] - пропусни ако вероятно е за означаване на празна клетка в таблица
	// не работи при липса на интервал преди цифра защото може да е негативно число
	// също не работи преди " и" или " или" за случаи като "антропо- и зооморфна пластика"
	let re = /[^|]([ \u00a0]+|&nbsp;)-[^\s\d-]|[^-| \u00a0\n]-(\n|[ \u00a0](?!и(?:ли)?[ \u00a0]))/g;
	let a = ct.getAllMatches(re, s);
	let b = [];
	for (let i = 0; i < a.length; i++) {
		let m = a[i];
		if (ct.doNotFix(s, m)) continue;
		b.push({
			start: m.start + 1,
			end: m.end - 1,
			//replacement: '\u00a0\u2013 ', // U+2013 is an ndash
			name: 'тире-',
			description: 'Късо тире с интервал само от едната страна',
			help: 'Късо тире с интервал само от едната страна вероятно трябва да е средно тире (en dash) с интервали и от двете страни.'
		});
	}
	return b;
});

ct.rules.push(function (s) {
	let re = /[^\dA-zА-я–-](\d+(?:\]\])?)(?:[-\u2013\u2014]|--)((?:\[\[)?\d+|\?|\.{3}|…)[^\dA-zА-я–-]/g;
	// U+2014 is mdash, U+2013 is ndash
	let a = ct.getAllMatches(re, s);
	let b = [];
	for (let i = 0; i < a.length; i++) {
		let m = a[i];
		if (ct.doNotFix(s, m) || (m[1].length == 3 && m[2].length == 10)) continue; // don't change ISBN-13
		b.push({
			start: m.start + 1,
			end: m.end - 1,
			replacement: m[1] + '\u00a0\u2013 ' + m[2],
			name: 'тире-числа',
			description: 'За числовите интервали се използва средно тире (en dash) оградено с интервали.'
		});
	}
	return b;
});

ct.rules.push(function (s) {
	let re = /^(={2,})([^=\n]*(?:=[^=\n]+)*)(={2,})$/gm;
	let a = ct.getAllMatches(re, s);
	let b = [];
	let level = 0; // == Level 1 ==, === Level 2 ===, ==== Level 3 ====, etc.
	let editform = document.getElementById('editform');
	// If we are editing a section, we have to be tolerant to the first heading's level
	let isSection = editform && editform.wpSection !== null && editform.wpSection.value !== '';
	for (let i = 0; i < a.length; i++) {
		let m = a[i];
		if (!m[2] || m[2].trim() === '') {
			b.push({
				start: m.start,
				end: m.end,
				replacement: '',
				name: 'празно заглавие',
				description: 'Премахни празно заглавие',
				help: 'Празните заглавия са ненужни.'
			});
		} else {
			if (!m[2].startsWith(' ') || !m[2].endsWith(' ')) {
				b.push({
					start: m.start,
					end: m.end,
					replacement: m[1] + ' ' + m[2].trim() + ' ' + m[3],
					name: 'заглавие-стил',
					description: 'Поправи интервалите',
					help: 'Стилът на заглавието трябва да е <kbd>==&nbsp;С интервали&nbsp;==</kbd>.'
				});
			}
			let oldLevel = level;
			level = m[1].length - 1;
			if (level - oldLevel > 1 && (!isSection || oldLevel > 0) ||
				m[1].length > 6 ||
				m[3].length > 6 ||
				m[1].length !== m[3].length
			) {
				b.push({
					start: m.start,
					end: m.end,
					name: 'заглавие-вложеност',
					description: 'Поправи ръчно неправилната вложеност, провери ръчно и следващите подзаглавия',
					help: 'Всяко заглавие трябва да е вложено точно едно ниво под по-общото заглавие.'
				});
			}
//			let frequentMistakes = [
//				{ code: 'външни вр.', wrong: /^[Вв]ъншни *[Вв]ръзки$/i, correct: 'Външни препратки' },
//				{ 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 (let j = 0; j < frequentMistakes.length; j++) {
//				let fm = frequentMistakes[j];
//				if (fm.wrong.test(m[3]) && m[3] != fm.correct) {
//				let r = m[1] + m[2] + fm.correct + m[2] + m[1];
//				if (r != m[0]) {
//					b.push({
//						start: m.start,
//						end: m.end,
//						replacement: r,
//						name: fm.code,
//						description: 'Поправи на „' + fm.correct + "“.",
//						help: 'Правилното изписване е „<kbd>' + fm.correct + "</kbd>“."
//					});
//				}
//			}
//		}
		}
	}
	return b;
});

ct.rules.push(function (s) {
	// ISBN: ten or thirteen digits, each digit optionally followed by a hyphen, the last digit can be 'X' or 'x'
	let a = ct.getAllMatches(/ISBN *=? *(([0-9Xx]-?)+)/gi, s);
	let b = [];
	for (let i = 0; i < a.length; i++) {
		let m = a[i];
		let str = m[1].replace(/[^0-9Xx]+/g, '').toUpperCase(); // remove all non-digits
		if (str.length !== 10 && str.length !== 13) {
			b.push({
				start: m.start,
				end: m.end,
				name: 'ISBN',
				description: 'Трябва да е дълъг 10 или 13 цифри',
				help: 'ISBN номерата трябва да са дълги 10 или 13 цифри. '
					+ 'Този се състои от ' + str.length + ' цифри:<br><kbd>' + m[1] + '</kbd>'
			});
			continue;
		}
		let isNew = (str.length === 13); // old (10 digits) or new (13 digits)
		let xIndex = str.indexOf('X');
		if (xIndex !== -1 && (xIndex !== 9 || isNew)) {
			b.push({
				start: m.start,
				end: m.end,
				name: 'ISBN',
				description: 'Неправилна употреба на X като цифра',
				help: "``<kbd>X</kbd>'' може да се ползва само като последна цифра в в 10-цифрен ISBN номер "
					+ '<br><kbd>' + m[1] + '</kbd>'
			});
			continue;
		}
		let computedChecksum = 0;
		let modulus = isNew ? 10 : 11;
		for (let j = str.length - 2; j >= 0; j--) {
			let digit = str.charCodeAt(j) - 48; // 48 is the ASCII code of '0'
			let quotient = isNew
				? ((j & 1) ? 3 : 1) // the new way: 1 for even, 3 for odd
				: 10 - j; // the old way: 10, 9, 8, etc
			computedChecksum = (computedChecksum + (quotient * digit)) % modulus;
		}
		computedChecksum = (modulus - computedChecksum) % modulus;
		let c = str.charCodeAt(str.length - 1) - 48;
		let actualChecksum = (c < 0 || 9 < c) ? 10 : c;
		if (computedChecksum === actualChecksum) continue;
		b.push({
			start: m.start,
			end: m.end,
			name: 'ISBN',
			description: 'Неправилна контролна сума',
			help: 'Неправилна контролна сума на ISBN номер:<br/><kbd>' + m[1] + '</kbd><br/>'
		});
	}
	return b;
});

ct.rules.push(function (s) {
	let re = / й[^А-яA-z\d]/g;
	let a = ct.getAllMatches(re, s);
	let b = [];
	for (let i = 0; i < a.length; i++) {
		let m = a[i];
		if (ct.doNotFix(s, m)) continue;
		b.push({
			start: m.start + 1,
			end: m.end - 1,
			replacement: 'ѝ',
			name: 'й→ѝ',
			description: 'Промени „й“ на „ѝ“',
			help: 'Когато се ползва като местоимение, „й“ трябва да се изписва '
				+ 'като „ѝ“ с ударение.'
		});
	}
	return b;
});

ct.rules.push(function (s) {
	// единица, предшествана от цифри, евентуално в препратка
	let re = /[^А-яA-z\d](\d+(?:\]\])?)((?:[ \u00a0]|&nbsp;)+)?(г(?:од)?\.|лв\.|щ\.д\.|(?:[мк]?г|[мск]?м|[mk]?g|[mck]?m)(?![А-яA-z\d]))/g;
	let autofix = ['г.', 'лв.', 'щ.д.'];
	let a = ct.getAllMatches(re, s);
	let b = [];
	for (let i = 0; i < a.length; i++) {
		let m = a[i];
		if (ct.doNotFix(s, m)) continue;
		let number = m[1];
		let spacing = m[2] || '';
		let unit = m[3] === 'год.' ? 'г.' : m[3];
		let autofixThis = $.inArray(unit, autofix) > -1;
		if (m[3] === 'год.' ||
			(autofixThis && spacing !== ' ' && spacing !== '\u00a0' && spacing !== '&nbsp;') ||
			(!autofixThis && spacing === '')
		) {
			b.push({
				start: m.start + 1,
				end: m.end,
				replacement: (autofixThis ? number + '\u00a0' + unit : undefined),
				name: 'число+' + unit,
				description: 'Добави интервал между числото и единицата ' + unit,
				help: 'Между числото и единицата <i>' + unit + '</i> трябва да се оставя един интервал, '
					+ 'за предпочитане непренасящият се <kbd>&amp;nbsp;</kbd> '
					+ '(non-breaking space, <kbd>U+00A0</kbd>).'
			});
		}
	}
	return b;
});

ct.rules.push(function (s) {
	let re = /(([{letter}\d])[\]\)“']*)(?:[^\S\r\n]+,[^\S\r\n]*|,)([\[\(„']*([{letter}\d]))/g;
	re = ct.fixRegExp(re);
	let a = ct.getAllMatches(re, s);
	let b = [];
	for (let i = 0; i < a.length; i++) {
		let m = a[i];
		let m1 = { 'start': m.start + m[1].length, 'end': m.end - m[3].length };
		if (ct.doNotFix(s, m, false, false, true, true, true, false) ||
			ct.inBrackets(s, m1, ['[', ']']) ||
			ct.inBrackets(s, m1, ['{', '}']) ||
			!isNaN(m[2]) && !isNaN(m[4]) // m[2] и m[4] са цифри; вероятност за десетично число
		) continue;
		b.push({
			start: m1.start,
			end: m1.end,
			replacement: ', ',
			name: 'запетая',
			description: 'Премахни интервала преди запетаята и/или добави такъв след нея',
			help: 'Интервалът трябва да е след запетаята и не преди нея.'
		});
	}
	return b;
});

ct.rules.push(function (s) {
	let re = /(([{letter}\d])[\]\)“']*)(?:[^\S\r\n]+\.[^\S\r\n]*|\.)([\[\(„']*([{letter}\d]))/g;
	re = ct.fixRegExp(re);
	let a = ct.getAllMatches(re, s);
	let b = [];
	for (let i = 0; i < a.length; i++) {
		let m = a[i];
		let m1 = { 'start': m.start + m[1].length, 'end': m.end - m[3].length };
		if (ct.doNotFix(s, m, false, false, true, true, true, false) ||
			ct.inBrackets(s, m1, ['[', ']']) ||
			ct.inBrackets(s, m1, ['{', '}']) ||
			!isNaN(m[2]) && !isNaN(m[4]) || // m[2] и m[4] са цифри; вероятност за десетично число след копиране
			m[4] !== m[4].toUpperCase() // m[4] не е главна буква
		) continue;
		b.push({
			start: m1.start,
			end: m1.end,
			replacement: '. ',
			name: 'точка',
			description: 'Премахни интервала преди точката в края на изречението и/или добави такъв след нея',
			help: 'Интервалът трябва да е след точката и не преди нея.'
		});
	}
	return b;
});

ct.rules.push(function (s) {
	let re = /((=\n{2,}.)|[^=\n]\n=|.\n{3,}.|\.\n[А-я])|^\n+/g;
	let a = ct.getAllMatches(re, s);
	for (let i = 0; i < a.length; i++) {
		let m = a[i];
		let lines = m[2] ? '\n' : '\n\n';
		a[i] = {
			start: m.start,
			end: m.end,
			replacement: m[1] ? m[1][0] + lines + m[1][m.end - m.start - 1] : '',
			name: 'нов ред',
			description: 'Премахни излишните празни редове или добави нов ред между отделните абзаци',
			help: 'Между отделните абзаци трябва да има един празен ред. Повече от един празен ред е излишен.'
		};
	}
	return a;
});

ct.rules.push(function (s) {
	function replace(latin, cyrillic, str, obj) {
		if (str.indexOf(latin) > -1) {
			obj.replacement = str.replace(latin, cyrillic);
			obj.description = 'Замени латинско "' + latin + '" с кирилско.';
		}
	}

	let re = /[А-я] [ec] [А-я]|[a-z][А-я]|[А-я][a-z]/g;
	let a = ct.getAllMatches(re, s);
	for (let i = 0; i < a.length; i++) {
		let m = a[i];
		a[i] = {
			start: m.start,
			end: m.end,
			replacement: null,
			name: '6lokavica',
			description: 'Неизвестна замяна. Проверете текста.',
			help: 'Една дума трябва да бъде написана или само на кирилица или само на латиница.'
		};
		replace('a', 'а', m[0], a[i]);
		replace('e', 'е', m[0], a[i]);
		replace('o', 'о', m[0], a[i]);
		replace('x', 'х', m[0], a[i]);
		replace('p', 'р', m[0], a[i]);
		replace('c', 'с', m[0], a[i]);
	}
	return a;
});

ct.rules.push(function (s) {
	// отварящи кавички ако са в нач. на реда или в списъчен елемент, след '', интервали (непредхождани от единично =), ==, >, }, |, (
	let re = /((?:^|\n)[*#:;]*|''|[^=\s][^\S\n]+|==[^\S\n]*|[>}|\(])(?:"(?![\s.,;])((?:[^"\[\]\s]|\s(?!")|\[\[[^\[\]]+\]\]|\[[^\[\]]+\])+)"|[„“](?![\s.,;])((?:[^„“”\[\]\s]|\s(?![“”])|\[\[[^\[\]]+\]\]|\[[^\[\]]+\])+)”|«(?!\s)((?:[^«»\[\]\s]|\s(?!»)|\[\[[^\[\]]+\]\]|\[[^\[\]]+\])+)»)/g;
	let a = ct.getAllMatches(re, s);
	let b = [];
	for (let i = 0; i < a.length; i++) {
		let m = a[i];
		let str = m[2] || m[3] || m[4];
		if (ct.doNotFix(s, m, true, true, true, true, true, false) ||
			str.search(/[а-ъюяѝ]|ь(?=о)/i) === -1 || // не съдържа нито една кирилска буква, ползвана в българския
			str.search(/(?![а-ъюяѝ]|ь(?=о))[\u0400-\u04ff]/i) > -1 // или пък съдържа и други кирилски букви, които не се ползват в българския
		) continue;
		b.push({
			start: m.start + m[1].length,
			end: m.end,
			replacement: '„' + str + '“',
			name: 'кавички',
			description: 'Заместване на "прави", “други горни”, „смесени” или «френски» с „български“ кавички.',
			help: 'В българския език и в Уикипедия на български се използват тези кавички: „ и “.'
		});
	}
	return b;
});

// Премахването на празни параметри от шаблоните като практика не се ползва с
// консенсусна подкрепа сред редакторите, затова на този етап е изключено.
/*
ct.rules.push(function (s) {
	let re = /(^|[^\n ] *)(\| *[\wА-я-]+ *= *(?=[\|\}]))+/g;
	let a = ct.getAllMatches(re, s);
	if (a.length === 0) return [];
	let n = a.length;
	let start = a[0].start + a[0][1].length;
	let end = a[n - 1].end + 1;
	let replacement = s.slice(start, end).replace(re, '$1');
	let b = [{
		start: start,
		end: end - 1,
		replacement: replacement.slice(0, -1),
		name: (n == 1 ? 'параметър' : n + '+ параметъра'),
		description: 'Премахва неизползваните параметри от шаблоните',
		help: 'Неизползваните параметри са излишни.'
	}];
	return b;
});
*/

ct.rules.push(function (s) {
	let skipNext = 0;
	let decoder = function (match, charCode, index, s) {
		if (skipNext > 0) {
			skipNext--;
			return '';
		}
		let decimal = parseInt(charCode, 16);
		let bin = Number(decimal).toString(2);
		if (decimal < 128) return match; // ASCII, don't decode
		let nOfBytes = bin.match(/^1+/)[0].length;
		skipNext = nOfBytes - 1;
		let urlEncoded = match + s.slice(index + 3, index + 3 * nOfBytes);
		let char = decodeURI(urlEncoded);
		return (char.length == 1 ? char : urlEncoded);
	};

	let re = /(https?:\/\/[^\/\s]+\/)([^\s|}<>\]]*)/g;
	let a = ct.getAllMatches(re, s);
	let b = [];
	let decoded;
	for (let i = 0; i < a.length; i++) {
		let m = a[i];
		try {
			decoded = m[2].replace(/%([A-F\d]{2})/gi, decoder);
			if (m[2] === decoded) continue;
			b.push({
				start: m.start,
				end: m.end,
				replacement: m[1] + decoded,
				name: 'URL',
				description: 'Декодира кодирани URL адреси',
				help: 'URL адресите се четат по-лесно когато са декодирани.'
			});
		} catch (e) {
			// не е кодиран Unicode текст
		}
	}
	return b;
});

ct.rules.push(function (s) {
	let re = /\([^\S\r\n]+|[^\S\r\n]+\)/g;
	let a = ct.getAllMatches(re, s);
	for (let i = 0; i < a.length; i++) {
		let m = a[i];
		if (ct.doNotFix(s, m)) continue;
		a[i] = {
			start: m.start,
			end: m.end,
			replacement: m[0].trim(),
			name: 'скоба',
			description: 'Премахни интервала след отварящата и/или преди затварящата скоба',
			help: 'Интервалите са ненужни след отваряща и преди затваряща скоба.'
		};
	}
	return a;
});

ct.rules.push(function (s) {
	let re = /((?:[^А-яA-z\d](?:[Оо]т|[Дд]о|[Пп]ре(?:з|ди)|[Сс]лед|[Мм]е(?:жду|сец)|[Ии](?:ли)?|[Вв])|[\d,])[ \u00a0])(Януари|Февруари|Март|Април|Май|Юни|Юли|Август|Септември|Октомври|Ноември|Декември)[^А-яA-z\d]/g;
	let a = ct.getAllMatches(re, s);
	let b = [];
	for (let i = 0; i < a.length; i++) {
		let m = a[i];
		if (ct.doNotFix(s, m)) continue;
		b.push({
			start: m.start + m[1].length,
			end: m.end - 1,
			replacement: m[2].toLowerCase(),
			name: 'месец',
			description: m[2] + ' → ' + m[2].toLowerCase(),
			help: 'В българския език имената на месеците се пишат с малка буква.'
		});
	}
	return b;
});

ct.rules.push(function (s) {
	let re = /№(\d+)/g;
	let a = ct.getAllMatches(re, s);
	let b = [];
	for (let i = 0; i < a.length; i++) {
		let m = a[i];
		if (ct.doNotFix(s, m)) continue;
		b.push({
			start: m.start,
			end: m.end,
			replacement: '№\u00a0' + m[1],
			name: '№+число',
			description: 'Добави несекаем интервал между № и последващите числа',
			help: 'Между символа за номер и последващите числа трябва да има несекаем интервал.'
		});
	}
	return b;
});

ct.rules.push(function (s) {
	function skipReferenceTags(str, index) {
		let sliced = str.slice(0, index);
		let tag1Pos = sliced.search(/<references\b(?:(?=([^/>]+))\1|\/(?!>))*>(?:(?=([^<]+))\2|<(?!\/references\b))*$/i);
		let tag2Pos = sliced.search(/\{\{\s*[Rr]eflist\b(?:(?=([^{}]+))\1|\{(?:(?=([^{}]+))\2|\{(?:(?=([^{}]+))\3|\{(?:(?=([^{}]+))\4|\{(?:(?=([^{}]+))\5|\{(?:(?=([^{}]+))\6|\{(?=([^{}]*))\7\})*\})*\})*\})*\})*\})*$/);
		return (tag1Pos > -1 || tag2Pos > -1);
	}

	let start = -1, end = 0;
	let noChange = true;
	let replacement = s.replace(/(<\/[Rr][Ee][Ff]\s*>|<[Rr][Ee][Ff]\b[^>]*\/>|\{\{\s*(?:[SsEe]fn|[Hh]rf|[Rr]p)\b[^{}]*\}\})[\s.:,;]+(?=<[Rr][Ee][Ff]\b|\{\{\s*(?:[SsEe]fn|[Hh]rf|[Rr]p)\b)/g, function (m, g1, index, str) {
		// Remove punctuation chars and spaces between refs
		if (skipReferenceTags(str, index)) return m;
		if (start === -1) start = index;
		end = index + g1.length;
		noChange = false;
		return g1;
	}).replace(/([^\s.:,;=]\s+|\s*[.,]\s+|[^\S\r\n]*[:;][^\S\r\n]+)(?:<[Rr][Ee][Ff]\b|\{\{\s*(?:[SsEe]fn|[Hh]rf|[Rr]p)\b)|(?:<\/[Rr][Ee][Ff]\s*>|<[Rr][Ee][Ff]\b[^>]*\/>|\{\{\s*(?:[SsEe]fn|[Hh]rf|[Rr]p)\b[^{}]*\}\})(?:\s+(?=[.,])|[^\S\r\n]+(?=[:;]))/g, function (m, g1, index, str) {
		// Remove spaces before ref/after ref and between punctuation chars
		if (skipReferenceTags(str, index)) return m;
		if (g1) m = g1.trim() + m.slice(g1.length);
		m = m.trim();
		if (start === -1 || start > index) start = index;
		if (end < index + m.length) end = index + m.length;
		noChange = false;
		return m;
	}).replace(/([.:,;])((?:<[Rr][Ee][Ff]\b(?:(?=([^>/]+))\3|\/(?!>))*(?:\/|>(?:(?=([^<]+))\4|<(?!\/?[Rr][Ee][Ff]\b))*<\/[Rr][Ee][Ff]\s*)>|\{\{(?:[SsEe]fn|[Hh]rf|[Rr]p)\b[^{}]*\}\})+)\1/g, function (m, g1, g2, g3, g4, index, str) {
		// Remove last punctuation char if equal at start/end position for a group of refs
		if (skipReferenceTags(str, index)) return m;
		if (start === -1 || start > index) start = index;
		if (end < index + (g1 + g2).length) end = index + (g1 + g2).length;
		noChange = false;
		return g1 + g2;
	}).replace(ct.fixRegExp(/(?:<\/[Rr][Ee][Ff]\s*>|<[Rr][Ee][Ff]\b[^>]*\/>|\{\{(?:[SsEe]fn|[Hh]rf|[Rr]p)\b[^{}]*\}\})[.:,;]?(?=[^{letter}\d\s<>{}.:,;]*[{letter}\d])/g), function (m, index, str) {
		// Add space if closing ref is followed by a word, only if the word is not positioned after opening/closing angular/curly bracket
		if (skipReferenceTags(str, index)) return m;
		if (start === -1 || start > index) start = index;
		if (end < index + m.length + 1) end = index + m.length + 1;
		noChange = false;
		return m + ' ';
	});
	if (noChange) return [];
	let deltaLength = s.length - replacement.length;
	return [{
		start: start,
		end: end + deltaLength,
		replacement: replacement.slice(start, end),
		name: 'пунктуация+ref',
		description: 'Интервалите и/или част от пунктуационните знаци преди, между и след ref-овете са ненужни',
		help: 'Изтрий ненужните пунктуационни знаци и интервали преди, между и след ref-овете.'
	}];
});

ct.rules.push(function (s) {
	let re = ct.fixRegExp(/(^|\s+)([Вв](?:ъв)?|[Сс](?:ъс)?)(\s+(?:[^{letter}\d\s\[\]{}<>-]*\[\[?[^\[\]]+\]|(?:[^\s\[\]{}<>]+\s+){1,2}))/g);
	let a = ct.getAllMatches(re, s);
	let b = [];
	for (let i = 0; i < a.length; i++) {
		let m = a[i];
		if (ct.doNotFix(s, m)) continue;
		let tooltip;
		let preposition = m[2];
		let text = m[3].replace(/\[(?:(?:https?:|ftps?:)?\/\/\S+|\[[^|\[\]]+\|)/gi, '')
					   .replace(ct.fixRegExp(/[^{letter}\d\s-]+/g), '')
					   .replace(/^\s+/, '');
		if (preposition.toLowerCase() === 'в' && text.match(/^(?:[ВвФф][А-Яа-я]|2-р|II(?![А-яA-z\d])|и(?:ли)?\s+)/)) {
			preposition += 'ъв';
			tooltip = '„в“ трябва да стане „във“, тъй като следващата дума започва с „в“ или „ф“, или попада под логическо ударение.';
		}
		else if (preposition.toLowerCase() === 'във' && !text.match(/^(?:[ВвФфVvFfWw]|2-р|II(?![А-яA-z\d])|и(?:ли)?\s+)/)) {
			preposition = preposition[0];
			tooltip = '„във“ трябва да стане „в“, тъй като следващата дума не започва с „в“ или „ф“, или не попада под логическо ударение.';
		}
		else if (preposition.toLowerCase() === 'с' && text.match(/^(?:[СсЗз][А-Яа-я]|(?:1?7(?:\d|\s*\d{3})?|[17]\d\d)(?!\d)|(?:X?VII|LXX[IVX]*|(?:DC)?C[IVXL]*)(?![А-яA-z\d])|и(?:ли)?\s+без\s+)/)) {
			preposition += 'ъс';
			tooltip = '„с“ трябва да стане „със“, тъй като следващата дума започва със „з“ или „с“, или попада под логическо ударение.';
		}
		else if (preposition.toLowerCase() === 'със' && !text.match(/^(?:[СсЗзSsZzCc]|(?:1?7(?:\d|\s*\d{3})?|[17]\d\d)(?!\d)|(?:X?VII|LXX[IVX]*|DCC[IVXL]*)(?![А-яA-z\d])|и(?:ли)?\s+без\s+)/)) {
			preposition = preposition[0];
			tooltip = '„със“ трябва да стане „с“, тъй като следващата дума не започва със „з“ или „с“, или не попада под логическо ударение.';
		}
		if (tooltip) {
			b.push({
				start: m.start + m[1].length,
				end: m.end - m[3].length,
				replacement: preposition,
				name: m[2].toLowerCase() + '→' + preposition.toLowerCase(),
				description: 'Поправи „' + m[2] + '“ на „' + preposition + '“',
				help: tooltip
			});
		}
	}
	return b;
});