Участник:Землеройкин/remove.js
Перейти к навигации
Перейти к поиску
/*
CC-BY-SA 4.0 Основано на u:higimo/remove.js?oldid=107716412 // v5.0.4
Вынесение на КБУ (+ отсроченное), КУ (+ множественное, + оставлено), КУЛ, КПМ.
Подключение (на своём /common.js):
importScript('у:Землеройкин/remove.js');
var g_user_alert = 1 // можно указать, чтоб уведомлять автора по умолчанию
*/
$(document).ready(function() {
var config = mw.config.get(['skin', 'wgNamespaceIds', 'wgFormattedNamespaces', 'wgNamespaceNumber', 'wgPageName', 'wgPageContentModel', 'wgIsRedirect', 'wgUserName']),
/*
Пространства имён, в которых будет включён удалятор
*/
rmNamespaces = (typeof g_rm_namespaces == 'undefined') ? [0, 2, 4, 6, 10, 14, 100, 104, 828] : g_rm_namespaces,
/*
Все кнопки меню
*/
menu_raw = {imp:'КУЛ', rnm:'КПМ', tRm:'КУ', mRm:'Много КУ', fRm:'КБУ', merge:'КОБ', split:'КРАЗД', recov:'ВУС', ret:'Оставить', doneRnm:'Переименовано', noRnm:'Не переименовано'},
/*
Доступные кнопки
*/
rmActions = (typeof g_rm_actions == 'undefined') ? Object.keys(menu_raw) : g_rm_actions,
/*
Переменная, управляющая функцией оповещения создателя статьи
*/
setAlert = (typeof g_user_alert == 'undefined') ? 0 : g_user_alert,
/*
Индикатор ошибки api
*/
isError,
/*
Здесь хранятся параметры номинации
*/
param,
rOld,
/*
Возможные разделы КБУ в зависимости от пространства имён
*/
fastRemovePrefix = (config.wgIsRedirect ? 'ОП' : 'О') + ({0:'Сd', 2:'У', 3:'У', 6:'Ф', 14:'К'}[config.wgNamespaceNumber] || ''),
/*
Список всех доступных причин КБУ
Коды для админов, текст для остальных.
Используется максимально короткий шаблон.
Третий параметр — условие для появления <input>
*/
fastRemove = [
['подст:ds', 'ds Отсроченное' ],
['уд-бессвязно', 'О1 Бессвязный текст' ],
['уд-тест', 'О2 Тестовая страница' ],
['уд-ванд', 'О3 Вандальная страница' ],
['уд-повторно', 'О4 Уже удалялось' ],
['уд-автор', 'О5 По просьбе автора' ],
['уд-обс', 'О6 Ненужная подстраница' ],
['уд-переим', 'О7 Для переименования', 'страницу'],
['уд-дубль', 'О8 Дубликат', 'страницу'],
['уд-реклама', 'О9 Реклама или спам' ],
['db-badtalk', 'О10 Нецелевая СО' ],
['уд-копивио', 'О11 Нарушение АП', 'ссылку'],
['уд-пусто', 'С1 Пусто или коротко' ],
['уд-иностр', 'С2 Не на русском' ],
['уд-ссылки', 'С3 Лишь ссылки' ],
['уд-нз', 'С5 Явно незначимо' ],
['уд-в никуда', 'П1 Перенапр. в никуда' ],
['db-redirspace', 'П2 Межпростр. перенапр.' ],
['уд-опечатка', 'П3 Перенапр. с опечаткой' ],
['уд-падеж', 'П4 Не именительный падеж' ],
['уд-смысл', 'П5 Неверное перенапр.' ],
['db-redirtalk', 'П6 Перенапр. на СО' ],
['db-duplicate', 'Ф1 Копия файла', 'файл'],
['db-badimage', 'Ф2 Повреждённый файл' ],
['подст:nld', 'Ф3 Нет данных о лицензии' ],
['подст:nsd', 'Ф3 Нет данных о источнике' ],
['подст:nad', 'Ф3 Нет данных о авторе' ],
['подст:dd', 'Ф3 Сомнительные данные файла' ],
['подст:ofud', 'Ф4 Неиспользуемый КДИ' ],
['подст:dfud', 'Ф5 Нет КДИ' ],
['db-badfairuse', 'Ф6 Неоправданное КДИ' ],
['NCT', 'Ф8 Есть на Складе', 'файл'],
['подст:Nothost', 'Ф9 Файл — ВП:НЕХОСТИНГ' ],
['уд-пусткат', 'К1 Пустая категория' ],
['уд-перекат', 'К2 Переименованная кат.', 'категорию'],
['уд-владелец', 'У1 По желанию владельца' ],
['уд-анон', 'У2 Устаревшая СО анонима' ],
['уд-несущ', 'У3 Несуществующий участник' ],
['уд-нецелевое', 'У4 Нецелевое использ. ЛП' ],
['уд-неактив', 'У5 Подстраница неактивного' ],
['db', 'Особый случай', 'причину']
].filter(function(arr) {
return fastRemovePrefix.indexOf(arr[1].charAt(0)) >= 0
}),
/*
Функция запроса к API.
Токен и формат каждый раз дополняется перед запросом.
Аргументы:
1. Передаваемые параметры
2. Режим запроса (query, edit или parse)
3. Колбек
void apiReq(object, string, function(result, status))
*/
apiReq = function(par, mode, clbck) {
par.format = 'json'
par.token = mw.user.tokens.get('csrfToken')
par.action = mode
$.post('/ruwiki/w/api.php', par, clbck)
},
/*
Функция получения даты.
Необходимо, например, для КУ-запросов.
Есть возможность указать собственную дату для подытоживания номинации.
['1051-32-33', '20 апреля 2014'] getDate(string)
*/
getDate = function(s) {
var d = (!!s) ? new Date(s) : new Date()
return [d.toISOString().substr(0, 10), d.getUTCDate() + ' ' +
'января,февраля,марта,апреля,мая,июня,июля,августа,сентября,октября,ноября,декабря'.split(',')[d.getUTCMonth()] +
' ' + d.getUTCFullYear()]
},
/*
Функция получения <input>
* Атрибут «h» используется в КБУ, указывая type
* Атрибут placeholder используется везде, для улучшения UI
* Атрибут id используется везде для получения информации из поля
string getInput(string, string, bool)
*/
getInput = function(id, p, h) {
return '<input id=' + id + ' type=' + (h ? 'hidden' : 'text') + ' placeholder="' + p + '" class=messagebox>'
},
/*
Определяет СО страницы
*/
getSO = function(pg) {
var tmp = /([^:]*:)?(.*)/.exec(pg)
if (tmp[1]) {
var ns = config.wgNamespaceIds[tmp[1].slice(0, -1).toLowerCase().replace(/ /g, '_')]
if (ns != undefined)
return config.wgFormattedNamespaces[ns | 1] + ':' + tmp[2]
}
return 'Обсуждение:' + pg
},
/*
Функция получения текста страницы, null если ошибка
getText(string, function(string))
*/
getText = function(pg, clbck) {
apiReq({
prop: 'wikitext',
page: pg
}, 'parse', function(txt) {
clbck(txt.parse ? txt.parse.wikitext['*'] : null)
})
},
/*
Функция отправки уведомления пользователю
По выполнению вызывает callback
userAlert(string, function(error))
*/
userAlert = function(pg, clbck) {
apiReq({
prop: 'revisions',
rvprop: 'user',
rvdir: 'newer',
titles: pg
}, 'query', function (t) {
var i = t.query.pages
if (!('-1' in i)) {
var rvdata = i[Object.keys(i)[0]].revisions[0]
if (!('anon' in rvdata) && !rvdata.userhidden && (rvdata.user && rvdata.user !== config.wgUserName)) {
apiReq({
title: 'User talk:' + rvdata.user,
section: 'new',
sectiontitle: 'Удалятор: [[:' + pg + ']]',
summary: param.sum,
text: 'Страница [[:' + pg + ']], созданная вами, ' + (param[3] ? '' : 'предложена ') + param[1] + '. ' +
(param.place ? 'Обсуждение — на странице [[' + param.place + '#' + param.sectionNW + ']]. ' : '') +
'~~\~~<br><small>Это автоматическое уведомление, сгенерированное [[у:Землеройкин/remove.js|скриптом «Удалятор»]].</small>',
assertuser: mw.config.get( 'wgUserName' ),
}, 'edit', function(t) {
clbck(t.error)
})
}
else clbck('не нужно')
}
else clbck('страница удалена')
})
},
/*
Функция работы с текстом статьи.
Определяет необходимые шаблоны и устанавливает их на СО или статью.
changeArticle(string, function(object))
*/
changeArticle = function(pg, clbck) {
if (/(noRnm|ret|doneRnm)/g.test(param[0]))
changeArticleDenom(pg, clbck)
else
changeArticleNom(pg, clbck)
},
// Закрытие номинации (снять в статье, установить на СО)
changeArticleDenom = function(pg, clbck) {
getText(pg, function(article) {
var tpl = RegExp('{\{(' + param[3] + ')\\|(\\d{4}-\\d\\d-\\d\\d)\\|?(.*?)}}', 'gi').exec(article)
if (tpl == null) {
clbck({
code: 'ошибка',
info: 'Невозможно снять шаблон «' + param[3] + '».'
})
return
}
rOld += ' '
param.date = getDate(tpl[2])
param.place = 'ВП:' + param[3].replace(/\|.*/, '') + '/' + param.date[1]
if (param[0] == 'doneRnm') { //переименовано
param.sectionNW = rOld + ' → ' + pg
param.tplpar = rOld + '|' + pg
}
if (param[0] == 'noRnm') { //не переименовано
param.sectionNW = pg + ' → ' + tpl[3]
param.tplpar = pg + '|' + tpl[3]
}
if (param[0] == 'ret') { //оставлено
param.sectionNW = tpl[3].length ? tpl[3] : pg
param.tplpar = 'l1=' + param.sectionNW
}
param.sum = '[[у:Землеройкин/remove.js|Удалятор]]: номинация [[' + (param.place ? param.place + '#' : '') + param.sectionNW + ']] — ' + param[2]
apiReq({
summary: param.sum,
title: getSO(pg),
prependtext: '{{' + param[2] + '|' + param.date[1] + '|' + param.tplpar + '}}\n'
}, 'edit')
article = article.replace(RegExp('(<noin.*?>)?{\{(' + param[3] + ')\\|.*?}}\n?(<\/noin.*?>)?\n?', 'gi'), '')
apiReq({
title: pg,
text: article,
summary: param.sum,
watchlist: 'nochange',
assertuser: mw.config.get( 'wgUserName' ),
}, 'edit', function(t) {
clbck(t.error)
})
})
},
// Открытие номинации (установка шаблона в статье)
changeArticleNom = function(pg, clbck) {
getText(pg, function(article) {
if (article == null) {
clbck({
code: 'ошибка',
info: 'Страница «' + pg + '» не существует.'
})
return
}
var tpl = ''
if (param[0] == 'fRm') {
tpl = fastRemove[$('#rmSel').val()][0] + '|1=' + $('#fiRm').val()
}
else {
tpl = param.tplpar
if (param[0] == 'merge') {
tpl = ('|' + tpl + '|').replace('|' + pg + '|', '|').slice(1, -1)
}
tpl = param[2] + '|' + param.date[0] + (tpl.length ? '|' + tpl : '')
}
apiReq({
title: pg,
text: (tpl.length ? '<noinclude>{{' + tpl + '}}\n</noinclude>' : '') + article,
summary: param.sum
}, 'edit', function(t) {
clbck(t.error)
})
})
},
/*
Функция установки номинации на соответствующую страницу
void setNominate(callback)
*/
setNominate = function(clbck) {
apiReq({
title: param.place,
createonly: '1',
text: '{{' + param[4] + '-Навигация}}\n',
summary: '[[у:Землеройкин/remove.js|Удалятор]]: автоматическая шапка',
assertuser: mw.config.get( 'wgUserName' ),
}, 'edit', function(t) {
apiReq({
title: param.place,
section: 'new',
sectiontitle: param.section,
summary: param.sum,
text: param.msg + ' ~~\~~',
assertuser: mw.config.get( 'wgUserName' ),
}, 'edit', function(t) {
clbck(t.error)
})
})
},
/*
Заполнение параметров номинации (переменная param).
.date — дата номинации
.msg — текст номинации
.place — место обсуждения (ВП:к_чему-то-там... или пусто, если КБУ)
.section — заголовок раздела номинации
.sectionNW — то же, без викификации
.tplpar — параметры шаблона, для установки в статье
.sum — описание правок скрипта
Возвращает массив страниц для обработки (до 5 шт. для МногоКУ, 2 для КОБ, 0 для ВУС, иначе 1)
*/
setParam = function() {
var i,
pg = config.wgPageName.replace(/_/g, ' '),
ttl = $('#rmHeader').val(),
ttl2 = $('#rmHeader2').length ? $('#rmHeader2').val() : '',
msg = $('#rmMsg').val(),
pgs = [pg]
rOld = ttl
if (/(noRnm|ret|doneRnm)/g.test(param[0]))
return pgs
param.date = getDate()
param.msg = msg ? msg.trim() : ''
param.place = param[0] != 'fRm' ? 'ВП:' + param[2] + '/' + param.date[1] : ''
param.section = param[0] == 'mRm' ? ttl : '[[:' + pg + ']]'
param.tplpar = ''
if (param[0] == 'rnm') {
param.tplpar = ttl
param.section += ' → [[:' + ttl + ']]'
}
else if (param[0] == 'merge') {
param.tplpar = pg + '|' + ttl
param.section += ' и [[:' + ttl + ']]'
}
else if (param[0] == 'split') {
param.tplpar = '[[:' + ttl + ']]' + (ttl2 ? ' и [[:' + ttl2 + ']]' : '')
param.section += ' → ' + param.tplpar
}
param.sectionNW = param.section.replace(/\[\[:/g, '').replace(/]]/g, '')
param.sum = '[[у:Землеройкин/remove.js|Удалятор]]: номинация [[' + (param.place ? param.place + '#' : '') + param.sectionNW + ']]' + (param[0] == 'fRm' ? ' ' + param[2] : '')
if (param[0] == 'mRm') {
pgs = []
param.msg = '=== По всем ===\n' + param.msg
for (i = 4; i >= 0; i--) {
pg = $('#rmArticle' + i).val()
if (pg.length) {
pgs.push(pg)
param.msg = '=== [[:' + pg + ']] ===\n' + param.msg
}
}
}
else if (param[0] == 'merge') {
pgs.push(ttl)
}
else if (param[0] == 'recov') {
pgs = []
}
return pgs
},
/*
Вывод сообщений об ошибках
*/
logError = function(s, err) {
if (err && err.code) isError = 1
$('#rmWindow').append(
'<br>' + s + ' — ' + (
err ? (err.code ? '<span class="error"><small>' + err.code + ': ' + err.info + '</small></span>' : err)
: 'OK'
)
)
},
/*
Обработка массива страниц
*/
processPages = function(pgs) {
if (pgs.length) {
var pg = pgs.pop()
changeArticle(pg, function(err) {
logError('Правка статьи «' + pg + '»', err)
if (isError) {
finalizeWindow()
return
}
if (setAlert) {
userAlert(pg, function(err) {
logError('Уведомление создателя', err)
processPages(pgs)
})
}
else processPages(pgs)
})
}
else {
if (param[4]) {
setNominate(function(err) {
logError('Запись номинации', err)
// window.open('/wiki/' + param.place + '#' + encodeURI(param.sectionNW.replace(/ /g, '_')))
finalizeWindow()
})
}
else finalizeWindow()
}
},
/*
Вызывается, когда всё сделано
*/
finalizeWindow = function() {
if (isError) {
$('.mw-small-spinner').remove()
$('#rmWindow')
.append('<p class="error">При выполнении скрипта случились ошибки. Поправьте всё, что надо, вручную.')
.children().prop('disabled', false)
$('#rmBtn').attr('disabled', 1)
$('#rmClose').text('Закрыть')
}
else location.reload()
},
/*
Функция создания модального окна
*/
modalHandler = function() {
var i,
content = ''
if (param[0] == 'mRm') {
content += getInput('rmHeader', 'Заголовок номинации')
for (i = 0; i < 5; i++) {
content += getInput('rmArticle' + i, 'Статья' + (i + 1))
}
}
if (param[0] == 'fRm') {
content += '<select id=rmSel class=messagebox>'
for (i = 0; i < fastRemove.length; i++) {
content += '<option value=' + i + '>' + fastRemove[i][1] + '</option>'
}
content += '</select>' + getInput('fiRm', '', 1)
}
if (param[0] == 'rnm') {
content += getInput('rmHeader', 'Новое название')
}
if (param[0] == 'doneRnm') {
content += getInput('rmHeader', 'Старое название')
}
if (param[0] == 'merge') {
content += getInput('rmHeader', 'Объединить с…')
}
if (param[0] == 'split') {
content += getInput('rmHeader', 'Разделить на эту')
content += getInput('rmHeader2', 'И на эту')
}
if (param[4]) {
content += '<textarea id=rmMsg placeholder="Текст номинации без «~~\~~»." rows=4></textarea>'
}
$('#content').prepend(
'<div id=rmWindow style="padding:2em;margin:1em;border:1px solid #985; background: #fec;">' +
'<h1>Удалятор: ' + param[2] + '</h1>' + content +
'<br><label><input name="rmUAlert" type=checkbox ' + ((setAlert) ? 'checked' : '') + '>Оповестить автора</label><br>' +
'<button id=rmBtn class=mw-ui-button>Отправить</button><button id=rmClose class=mw-ui-button>Отмена</button>'
)
if (param[0] == 'mRm') $('#rmArticle0').val(config.wgPageName.replace(/_/g, ' '))
$('#rmSel').change(function() {
var i = fastRemove[this.value][2]
$('#fiRm').attr({type: (i ? 'text' : 'hidden'), placeholder: 'Укажите ' + i})
})
$('#rmClose').click(function() {
$('#rmWindow').remove()
})
$('#rmBtn').click(function() {
$('#rmWindow')
.append('<b class=mw-small-spinner></b>')
.children().attr('disabled', '1')
setAlert = $('[name="rmUAlert"]').is(':checked')
var pgs = setParam()
processPages(pgs)
})
/*
Реализация ctrl+enter события
*/
$(window).keydown(function (e) {
if (e.ctrlKey && e.keyCode == 13)
$('#rmBtn').click()
})
};
/*
Добавление выпадающего меню на все страницы
*/
if ((rmNamespaces.indexOf(config.wgNamespaceNumber & ~1) >= 0) && (config.wgPageContentModel == 'wikitext')) {
var menuLocation = 'p-cactions';
var nextNode = '#ca-move';
if ( config.skin === 'minerva' ) {
menuLocation = 'p-tb';
nextNode = null;
}
if ( config.skin === 'vector' || config.skin === 'vector-2022' || config.skin === 'timeless' ) {
var portlet = mw.util.addPortlet( 'p-remove-js', 'Удалятор', '#p-cactions' );
menuLocation = 'p-remove-js';
nextNode = null;
}
for (var a of rmActions) {
var portletLink = mw.util.addPortletLink( menuLocation, '#', menu_raw[ a ], 'ca-remove-' + a, null, null, nextNode );
$( portletLink ).find( 'a' ).click(function( e ) {
e.preventDefault();
var i = $( this ).parent().attr( 'id' ).replace( 'ca-remove-', '' );
param = (
// [текущее действие, комментарий, шаблон/место обсуждения, поддерживаемые шаблоны
i == 'imp' ? [ i, 'к срочному улучшению', 'к улучшению', '', 'КУЛ'] :
i == 'rnm' ? [ i, 'к переименованию', 'к переименованию', '', 'КПМ'] :
i == 'tRm' ? [ i, 'к удалению', 'к удалению', '', 'КУ'] :
i == 'mRm' ? [ i, 'к удалению', 'к удалению', '', 'КУ'] :
i == 'merge' ? [ i, 'к объединению с другой', 'к объединению', '', 'КОБ'] :
i == 'split' ? [ i, 'к разделению', 'к разделению', '', 'КР'] :
i == 'fRm' ? [ i, 'к [[ВП:КБУ|быстрому удалению]]', 'к быстрому удалению'] :
i == 'recov' ? [ i, '', 'к восстановлению', '', 'ВУС'] :
i == 'ret' ? [ i, 'оставлена', 'оставлено', 'к удалению|ку'] :
i == 'doneRnm'? [ i, 'переименована', 'переименовано', 'к переименованию|кпм|rename'] :
i == 'noRnm' ? [ i, 'не переименована', 'не переименовано', 'к переименованию|кпм|rename'] :
0
)
isError = 0
modalHandler()
});
}
}
})