MediaWiki:Gadget-watchlist.js: различия между версиями

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
Содержимое удалено Содержимое добавлено
появилась новая возможность побороть скачки здесь — воспользуемся (см. css и Gadgets-definition)
устанавливаем куки на 90 дней — имхо, оптимально: не слишком коротко, но и не навечно, оформление кода
Строка 1: Строка 1:
if ( mw.config.get( 'wgCanonicalSpecialPageName' ) === 'Watchlist' && mw.config.get( 'wgAction' ) === 'view' )
if (mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && mw.config.get('wgAction') === 'view') {
mw.hook('wikipage.content').add(function () {
mw.hook('wikipage.content').add(function () {
setTimeout(function () { // лечение для Firefox
setTimeout(function () { // лечение для Firefox


/* MAIN BLOCK */
var whenPageLoaded = +(new Date()) - 20000 //add 20 sec just in case
var whenPageLoaded = +(new Date()) - 20000; // add 20 seconds just in case


var mainTab, hideInterfaceCSS, rvOffCSS
var hideInterfaceCSS, rvOffCSS;
var sortingDone, allStarsOn, hoverOnTitlesDone
var sortingDone, allStarsOn, hoverOnTitlesDone;


//ruwiki
// ruwiki
var mm = {
var mm = {
sortTip: 'Сортировать страницы по пространствам',
sortTip: 'Сортировать страницы по пространствам',
Строка 21: Строка 22:
};
};


var isEnhanced = mw.util.$content.find('ul.special').length == 0 //RC type in preferences
// recent changes type in preferences
var isEnhanced = mw.util.$content.find('ul.special').length == 0;


// find insertion points for links: after "days all"
var linksAt = $('#mw-watchlist-options').find('#days').next();


//find insertion points for links: after "days all"
var linksAt = $('#mw-watchlist-options').find('#days').next()


// UNWATCH LINKS

// on every line
//UNWATCH links:
if (document.cookie.indexOf('wlunw=1') != -1) {
//on every line
showAllStars();
if( document.cookie.indexOf('wlunw=1') != -1 ) $(showAllStars)
else $(bindHoverOnTitles) //mouseover on title
} else {
bindHoverOnTitles(); // mouseover on title
}
// switch for above, saved in a cookie
// switch for above, saved in a cookie
addLink(
addLnk('<img alt="x" style="width:1em;" src="/upwiki/wikipedia/commons/'
+ 'a/a4/Vector_skin_-_page_not_in_the_watchlist.png">', mm.unwatchTip).click(showAllStars)
'<img alt="x" style="width:1em;" src="/upwiki/wikipedia/commons/a/a4/Vector_skin_-_page_not_in_the_watchlist.png">',
mm.unwatchTip
//click on timestamp (enhanced RC) / empty space (non-enhanced)
).click(showAllStars); // click on timestamp (enhanced recent changes) / empty space (non-enhanced)


if (document.cookie.indexOf('wlrvoff=1') != -1) switchRevert();
if (document.cookie.indexOf('wlrvoff=1') != -1) switchRevert();




// OTHER LINKS
// "sort" link
// "sort" link
addLnk('↑↓', mm.sortTip).click(sortWatchlist);
addLink('↑↓', mm.sortTip).click(sortWatchlist);


// "expand all" link
// "expand all" link
if ($('.mw-enhancedchanges-arrow').length) {
if ($('.mw-enhancedchanges-arrow').length) {
addLnk('±', mm.expandAll).click(expandMultipleEdits);
addLink('±', mm.expandAll).click(expandMultipleEdits);
}
}


// "switch revert" link
if ($('.mw-rollback-link').length) {
if ($('.mw-rollback-link').length) {
addLink('⎌', mm.switchRevert).click(switchRevert);
// "switch revert" link
addLnk('⎌', mm.switchRevert).click(switchRevert);
}
}


// "only new" link
// "only new" link
addLnk(mm.onlynew, mm.onlynewTip).mousedown(onlyNewEntries).attr('id', 'listSince');
addLink(mm.onlynew, mm.onlynewTip).mousedown(onlyNewEntries).attr('id', 'listSince');




//TABS
// TABS
if( window.wlNoTabs ) return
if (window.wlNoTabs) return;
var mainTab = $('#ca-special, #ca-nstab-special').eq(0) //"Special" tab
var mainTab = $('#ca-special, #ca-nstab-special').eq(0); // "Special" tab


//change main tab into "watchlist Δ"
// change main tab into "watchlist Δ"
var wl = $.trim( $('h1#firstHeading').text() )
var wl = $.trim($('h1#firstHeading').text());
mainTab.find('a') //replace "Special" tab text with "Watchlist"
mainTab.find('a') // replace "Special" tab text with "Watchlist"
.text(wl + ' △') //Δ is good but monobook makes is lowercase
.text(wl + ' △') // Δ is good but monobook makes is lowercase
.attr('title', wl + ' — ' + mm.onlynewTipLowerCase)
.attr('title', wl + ' — ' + mm.onlynewTipLowerCase)
.on('mousedown keydown', onlyNewEntries)
.on('mousedown keydown', onlyNewEntries);


//add "hideInterface" tab
// add "hideInterface" tab
mainTab.clone(true).removeClass('selected')
mainTab.clone(true).removeClass('selected')
.appendTo(mainTab.parent())
.appendTo(mainTab.parent())
.click(hideInterface)
.click(hideInterface)
.attr('id','').attr('href','#')
.attr('id','').attr('href', '#')
.find('a')
.find('a')
.text('↸').attr('title', mm.fullPage).attr('accesskey','')
.text('↸').attr('title', mm.fullPage).attr('accesskey', '');


if( document.cookie.indexOf('wlmax=1') != -1 ) hideInterface()
if (document.cookie.indexOf('wlmax=1') != -1) hideInterface();


if ($('.mw-changeslist .mw-rollback-link').first().css('visibility') == 'hidden') {
if ($('.mw-changeslist .mw-rollback-link').first().css('visibility') == 'hidden') {
Строка 82: Строка 88:
}
}


return
return;




/* FUNCTIONS */

function addLink(txt, tip) {
linksAt.before(' &nbsp;');
return $('<a href="#" title="' + tip + '">' + txt + '</a>').insertBefore(linksAt);
function addLnk(txt, tip){
linksAt.before(' &nbsp;')
return $('<a href=# title="'+tip+'">'+txt+'</a>').insertBefore(linksAt)
}
}




function onlyNewEntries(e) {
function onlyNewEntries(e) {
var url = window.location.href.split('#')[0]
var url = window.location.href.split('#')[0];
var days = ( +(new Date()) - whenPageLoaded)/(1000 * 3600 * 24)
var days = (+(new Date()) - whenPageLoaded)/(1000 * 3600 * 24);
if( days < 0 ) days = 0.01 //negative might happen when adjusting local time
if (days < 0) days = 0.01; // negative might happen when adjusting local time
e.target.href = /[?&]days=/.test(url)
e.target.href = /[?&]days=/.test(url)
? url.replace(/([?&]days=)[^&]*/, '$1'+days)
? url.replace(/([?&]days=)[^&]*/, '$1' + days)
: url + (url.indexOf('?') < 0 ? '?':'&') + 'days=' + days
: url + (url.indexOf('?') < 0 ? '?':'&') + 'days=' + days;
return true
return true;
}
}


function showAllStars(e) {

if (e) {

e.preventDefault();
function showAllStars(e){
}
if( !allStarsOn ){
if (!allStarsOn) {
mw.util.$content.find('a[href*="action=history"]')
mw.util.$content.find('a[href*="action=history"]')
.each( function(i, lnk){ updateStar( getRow(this) ) } )
.each(function(i, lnk) {
document.cookie = 'wlunw=1; path=/'
updateStar(getRow(this));
allStarsOn = true
});
}else{ //otherwise remove
if (e) {
mw.util.$content.find('a.aj-unwatch').not('unwatched').remove()
document.cookie = 'wlunw=0; expires=' + (new Date()).toGMTString() + '; path=/'
var cookieDate = new Date($.now() + 1000 * 60 * 60 * 24 * 90).toGMTString();
document.cookie = 'wlunw=1; expires=' + cookieDate + '; path=/';
if( !hoverOnTitlesDone ) bindHoverOnTitles()
}
allStarsOn = false
allStarsOn = true;
}
} else { // otherwise remove
return false
mw.util.$content.find('a.aj-unwatch').not('unwatched').remove();
document.cookie = 'wlunw=0; expires=' + (new Date()).toGMTString() + '; path=/';
if (!hoverOnTitlesDone) {
bindHoverOnTitles();
}
allStarsOn = false;
}
}
}


function sortWatchlist(e){
function sortWatchlist(e) {
e.preventDefault()
e.preventDefault();
if( sortingDone ) return alert(mm.sortDone)
if (sortingDone) {
return alert(mm.sortDone);
}
mw.util.$content.find('h4').each(function(){ //sort all days separately
mw.util.$content.find('h4').each(function() { // sort all days separately
var container = $(this).next('div, ul')
var rows = container.children('li, table')
var container = $(this).next('div, ul');
var rows = container.children('li, table');
//create sorting keys
// create sorting keys
var key
var key;
rows.each( function(i){
rows.each(function(i) {
//use built-in class: either li.watchlist-5-<title> or table.mw-changeslist-ns100-<title> in enhanced RC
// use built-in class: either li.watchlist-5-<title> or
key = /(\d+)-(\S+)/.exec( this.className ) || ['', 0, ' '] //logs might not have this class
// table.mw-changeslist-ns100-<title> in enhanced recent changes
if( key[1] % 2 ) key[1]-- //sort talk page as if it was a base page
key = /(\d+)-(\S+)/.exec(this.className) || ['', 0, ' ']; // logs might not have this class
if( window.watchlistSortNamespaceOnly ) key[2] = zzz(i) //keep timestamp order within each NS block
this.skey = zzz(key[1]) + ':' + key[2]
if (key[1] % 2) {
key[1]--; // sort talk page as if it was a base page
})
}
//sort array and then HTML
if (window.watchlistSortNamespaceOnly) {
rows.sort(function(a,b){ return a.skey > b.skey ? 1 : ( a.skey < b.skey ? -1 : 0 ) })
key[2] = zzz(i); // keep timestamp order within each NS block
for( i=0; i<rows.length; i++ ) container.append( rows.eq(i) )
}
})
this.skey = zzz(key[1]) + ':' + key[2];
sortingDone = true
});
// sort array and then HTML
rows.sort(function(a, b) {
return a.skey > b.skey ? 1 : (a.skey < b.skey ? -1 : 0);
});
for (i = 0; i < rows.length; i++) {
container.append(rows.eq(i));
}
});
sortingDone = true;
}
}


function expandMultipleEdits(e){
function expandMultipleEdits(e) {
e.preventDefault()
e.preventDefault();
var i = 0, sp, state = $('.mw-changeslist .mw-collapsible')[0].style.display
var i = 0, sp, state = $('.mw-changeslist .mw-collapsible')[0].style.display;
while( sp=$('.mw-changeslist .mw-collapsible')[i++] )
while (sp = $('.mw-changeslist .mw-collapsible')[i++]) {
if( sp.style.display == state ) $(sp).find('.mw-enhancedchanges-arrow').click()
if (sp.style.display == state) {
$(sp).find('.mw-enhancedchanges-arrow').click();
}
}
}
}


function switchRevert(e) {
function switchRevert(e) {
if (e) {
if (e) e.preventDefault();
e.preventDefault();
}
if (!rvOffCSS) {
if (!rvOffCSS) {
rvOffCSS = mw.util.addCSS('.mw-rollback-link { display:none; }')
rvOffCSS = mw.util.addCSS('.mw-rollback-link { display:none; }');
} else {
document.cookie = 'wlrvoff=1; expires=' + (new Date($.now() + 1000 * 60 * 60 * 24 * 365)).toGMTString() + '; path=/';
} else {
rvOffCSS.disabled = !rvOffCSS.disabled;
rvOffCSS.disabled = !rvOffCSS.disabled;
}
if (rvOffCSS.disabled) {
// Поставлено 28 января 2017, чтобы сбросить куки, установленные ранее по путям /wiki/ и /ruwiki/w/,
// Поставлено 28 января 2017 года, чтобы сбросить куки, установленные ранее по путям
// которые могут перебивать новую настройку. Через год можно убирать
// /wiki/ и /ruwiki/w/, которые могут перебивать новую настройку. Через год можно убирать.
document.cookie = 'wlrvoff=0; expires=' + (new Date()).toGMTString() + '';
document.cookie = 'wlrvoff=0; expires=' + (new Date()).toGMTString() + '';
document.cookie = 'wlrvoff=0; expires=' + (new Date()).toGMTString() + '; path=/';
document.cookie = 'wlrvoff=0; expires=' + (new Date()).toGMTString() + '; path=/';
} else if (e) {
var cookieDate = (new Date($.now() + 1000 * 60 * 60 * 24 * 90)).toGMTString();
document.cookie = 'wlrvoff=1; expires=' + cookieDate + '; path=/';
}
}
}
}


function bindHoverOnTitles() { // find all "titles" links and assign hover event
if (hoverOnTitlesDone) return;
// $('#mw-content-text').find(isEnhanced ? 'table' : 'li');
mw.util.$content.find('a[href*="action=history"]')
.each(function() {
getRow(this).find('a[href^="/wiki/"]:first').hover(hoverOnTitle);
});
hoverOnTitlesDone = true;
}


function bindHoverOnTitles(){ //find all "titles" links and assign hover event
function hoverOnTitle(e) { // on hover: add "unwatch" star after 1 second
var lnk = $(this);
if( hoverOnTitlesDone ) return
if (e.type == 'mouseenter') {
//$('#mw-content-text').find( isEnhanced ? 'table' : 'li')
lnk.data('uwTimeout', setTimeout(function() {showStarOnHover(lnk)}, 1000));
mw.util.$content.find('a[href*="action=history"]')
} else {
.each( function(){
clearTimeout(lnk.data('uwTimeout'));
getRow(this).find('a[href^="/wiki/"]:first').hover( hoverOnTitle )
})
}
hoverOnTitlesDone = true
}
}


function showStarOnHover(lnk) {
var row = getRow(lnk);
updateStar(row);
// attach mouseleave to remove the star
if (row.attr('leaveAssigned')) return;
row.attr('leaveAssigned', true);
row.mouseleave(function(e) {
var uw = $(this).find('.aj-unwatch');
if (uw.length
&& /unwatch/.test(uw.attr('href'))
&& !/waiting|failure/.test(uw.attr('class'))
&& !allStarsOn
) {
uw.remove();
}
});
}


function updateStar(row) {
function hoverOnTitle(e){ //on hover: add "unwatch" star after 1s
var lnk = $(this)
var star = row.find('a.aj-unwatch');
if (!star.length) { // create
if( e.type == 'mouseenter' )
star = $('<a class="aj-unwatch" href="'
lnk.data( 'uwTimeout', setTimeout( function(){showStarOnHover(lnk)}, 1000 ) )
+ row.find('a[href*="action=history"]').attr('href').replace(/&curid=\d+/,'')
else
+ '">x</a>')
clearTimeout( lnk.data( 'uwTimeout' ) )
.click(ajaxUnwatch)
}
.insertBefore(row.find('a[href^="/wiki/"]:first'))

.after(' ');

}
function showStarOnHover(lnk){
// update
var row = getRow(lnk)
var isUnwatched = row.hasClass('unwatched');
updateStar(row)
var state = isUnwatched ? 'watch' : 'unwatch';
//attach mouseleave to remove the star
if( row.attr('leaveAssigned') ) return
star.attr('title', mw.msg(state))
.attr('href', star.attr('href').replace(/&action=\w+/, '&action='+ state))
row.attr('leaveAssigned', true)
.html('<img alt="x" style="width:0.6em;" src="/upwiki/wikipedia/commons/'
row.mouseleave( function(e){
+ (isUnwatched
var uw = $(this).find('.aj-unwatch')
? 'a/a4/Vector_skin_-_page_not_in_the_watchlist.png'
if( uw.length
: 'f/f2/Vector_skin_-_page_in_the_watchlist.png')
&& /unwatch/.test( uw.attr('href') )
+ '" />');
&& !/waiting|failure/.test( uw.attr('class') )
&& !allStarsOn
)
uw.remove()
})

}
}


function getRow(el) {

return $(el).closest(isEnhanced ? 'tr' : 'li');


function updateStar(row){
var star = row.find('a.aj-unwatch')
if( !star.length ){ //create
star = $('<a class=aj-unwatch href="'
+ row.find('a[href*="action=history"]').attr('href').replace(/&curid=\d+/,'')
+ '">x</a>')
.click(ajaxUnwatch)
.insertBefore( row.find('a[href^="/wiki/"]:first') )
.after(' ')
}
//update
var isUnwatched = row.hasClass('unwatched')
var state = isUnwatched ? 'watch' : 'unwatch'
star.attr( 'title', mw.msg(state) )
.attr( 'href', star.attr('href').replace(/&action=\w+/, '&action='+ state) )
.html('<img alt=x style="width:0.6em" '
+ 'src="/upwiki/wikipedia/commons/'
+ ( isUnwatched
? 'a/a4/Vector_skin_-_page_not_in_the_watchlist.png'
: 'f/f2/Vector_skin_-_page_in_the_watchlist.png'
)
+ '" />')
}
}

function getRow(el){
return $(el).closest(isEnhanced ? 'tr' : 'li')
}








function ajaxUnwatch(e) {
function ajaxUnwatch(e) {
var xLnk = $(this), errMsg = ''
var xLnk = $(this), errMsg = '';
var req = { token: mw.user.tokens.get('watchToken'),
var req = {
token: mw.user.tokens.get('watchToken'),
title: getLinkTitle(xLnk) }
title: getLinkTitle(xLnk)
};
if( /&action=unwatch/.test(xLnk.attr('href')) ) req.unwatch = ''
if (/&action=unwatch/.test(xLnk.attr('href'))) {
xLnk.addClass('waiting')
req.unwatch = '';
$.ajax({
}
type:'POST', dataType: 'json',
xLnk.addClass('waiting');
url: mw.util.wikiScript('api') + '?action=watch&format=json',
$.ajax({
data: req,
type:'POST', dataType: 'json',
timeout: 5000,
url: mw.util.wikiScript('api') + '?action=watch&format=json',
success: function(resp){
data: req,
if( resp.error ) errMsg = resp.error.info
timeout: 5000,
else if( !resp.watch ) errMsg = 'empty response'
success: function(resp) {
else if( typeof resp.watch.unwatched == 'string') unwatchSuccess( req.title, true )
if (resp.error) {
else if( typeof resp.watch.watched == 'string') unwatchSuccess( req.title, false )
else errMsg = 'unrecognized response'
errMsg = resp.error.info;
} else if (!resp.watch) {
},
errMsg = 'empty response';
error: function(xhr, status, err) {
} else if (typeof resp.watch.unwatched == 'string') {
errMsg = status + ':' + err
unwatchSuccess(req.title, true);
},
} else if (typeof resp.watch.watched == 'string') {
complete: function(){ //update X link
unwatchSuccess(req.title, false);
xLnk.removeClass('waiting')
} else {
if( errMsg ) xLnk.attr( 'title', 'API error: ' + errMsg ).addClass('failure')
errMsg = 'unrecognized response';
else xLnk.removeClass('failure')
}
}
})
},
error: function(xhr, status, err) {
return false
errMsg = status + ':' + err;
},
complete: function() { // update X link
xLnk.removeClass('waiting');
if (errMsg) xLnk.attr('title', 'API error: ' + errMsg).addClass('failure');
else xLnk.removeClass('failure');
}
});
return false;
}
}




function unwatchSuccess(name, isUnwatched) {
function unwatchSuccess(name, isUnwatched) {
//find full name of associated talk page (or vice versa)
// find full name of associated talk page (or vice versa)
var ns = getTitleNamespace(name)
var ns = getTitleNamespace(name);
var name2 = name
var name2 = name;
if( ns > 0 ) name2 = name2.replace(/^.+?:/,'') //remove old prefix
if (ns > 0) {
name2 = name2.replace(/^.+?:/,''); // remove old prefix
}
if( ns % 2 ) ns--; else ns++ //switch to "other" namespace
if (ns % 2) {
if( ns > 0 ) name2 = mw.config.get( 'wgFormattedNamespaces' )[ns] + ':' + name2 //add new prefix
ns--;
//mark all rows that are either name or name2
} else {
mw.util.$content.find('a[href*="action=history"]').each(function(){
ns++; // switch to "other" namespace
var ttl = getLinkTitle(this)
}
if( ttl != name && ttl != name2 ) return
if (ns > 0) {
var row = getRow(this)
name2 = mw.config.get('wgFormattedNamespaces')[ns] + ':' + name2; // add new prefix
row.toggleClass('unwatched', isUnwatched || false)
}
updateStar(row)
// mark all rows that are either name or name2
if( !isUnwatched && !allStarsOn) row.find('a.aj-unwatch').remove()
mw.util.$content.find('a[href*="action=history"]').each(function() {
})
var ttl = getLinkTitle(this);
if (ttl != name && ttl != name2) return;
var row = getRow(this);
row.toggleClass('unwatched', isUnwatched || false);
updateStar(row);
if (!isUnwatched && !allStarsOn) {
row.find('a.aj-unwatch').remove();
}
});
}
}


function hideInterface(e) {
if (e) e.preventDefault();
if (!hideInterfaceCSS) {
hideInterfaceCSS = mw.util.addCSS('\
h4 { font-size: 90%; }\
\
#firstHeading,\
div#siteNotice, #contentSub, fieldset#mw-watchlist-options,\
div.mw-rc-label-legend, #mw-fr-watchlist-pending-notice { display: none; }');
} else {
hideInterfaceCSS.disabled = !hideInterfaceCSS.disabled;
}
if (e) {
if (!hideInterfaceCSS.disabled) {
var cookieDate = new Date($.now() + 1000 * 60 * 60 * 24 * 90).toGMTString();
document.cookie = 'wlmax=1; expires=' + cookieDate + '; path=/';
} else {
document.cookie = 'wlmax=0; expires=' + (new Date()).toGMTString() + '; path=/';
}
}
}


function getTitleNamespace(title) { // returns namespace number
var prefix = /^(.+?):/.exec(title);
if (!prefix) return 0; // no prefix means article
return mw.config.get('wgNamespaceIds')[ prefix[1].toLowerCase().replace(/ /g,'_') ] || 0;
}


function getLinkTitle(lnk) { // gets 'title=' part from a link

return mw.util.getParamValue('title', $(lnk).attr('href')).replace(/_/g,' ');

// var ma = /(&|\?)title=([^&]+)/.exec($(lnk).attr('href'));

// if (ma) return decodeURIComponent(ma[2]).replace(/_/g,' ');

// else return '';
function hideInterface(e){

if( e ) e.preventDefault()

if (!hideInterfaceCSS) hideInterfaceCSS = mw.util.addCSS('\
h4 {font-size:90%}\
#firstHeading,\
div#siteNotice, #contentSub, fieldset#mw-watchlist-options,\
div.mw-rc-label-legend, #mw-fr-watchlist-pending-notice {display:none}')
else hideInterfaceCSS.disabled = !hideInterfaceCSS.disabled
document.cookie = 'wlmax=' + (!hideInterfaceCSS.disabled ? '1; path=/' : '0; expires=' + (new Date()).toGMTString() + '; path=/')
}
}


function zzz(s) { // 5 -> 005

s = s.toString();

if (s.length == 1) {
function getTitleNamespace(title){ //returns namespace number
return '00' + s;
var prefix = /^(.+?):/.exec(title)
} else if (s.length == 2) {
if( !prefix ) return 0 //no prefix means article
return '0' + s;
return mw.config.get( 'wgNamespaceIds' )[ prefix[1].toLowerCase().replace(/ /g,'_') ] || 0
}
} else {
return s;

}
function getLinkTitle (lnk){ //gets 'title=' part from a link
return mw.util.getParamValue('title', $(lnk).attr('href')).replace(/_/g,' ')
//var ma = /(&|\?)title=([^&]+)/.exec( $(lnk).attr('href') )
//if( ma ) return decodeURIComponent(ma[2]).replace(/_/g,' ')
//else return ''
}
}


}, 0);
function zzz(s){ // 5 -> 005
}); // main
s = s.toString()
if( s.length==1 ) return '00'+s
else if( s.length==2 ) return '0'+s
else return s
}
}

}, 0)
})//main

Версия от 16:07, 19 февраля 2017

if (mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist' && mw.config.get('wgAction') === 'view') {
	mw.hook('wikipage.content').add(function () {
		setTimeout(function () {  // лечение для Firefox

/* MAIN BLOCK */
var whenPageLoaded = +(new Date()) - 20000;  // add 20 seconds just in case

var hideInterfaceCSS, rvOffCSS;
var sortingDone, allStarsOn, hoverOnTitlesDone;

// ruwiki
var mm = {
	sortTip: 'Сортировать страницы по пространствам',
	sortDone: 'Изменения уже отсортированы',
	unwatchTip: 'Добавить звёздочки для вычёркивания страниц из списка наблюдения',
	onlynew: 'Только новые',
	onlynewTip: 'Изменения с момента загрузки этой страницы',
	onlynewTipLowerCase: 'изменения с момента загрузки этой страницы',
	expandAll: 'Показать/спрятать все свёрнутые правки',
	switchRevert: 'Спрятать/показать ссылки «откатить»',
	fullPage: 'Спрятать/показать элементы интерфейса'
};

// recent changes type in preferences
var isEnhanced = mw.util.$content.find('ul.special').length == 0;

// find insertion points for links: after "days all"
var linksAt = $('#mw-watchlist-options').find('#days').next();


// UNWATCH LINKS
// on every line
if (document.cookie.indexOf('wlunw=1') != -1) {
	showAllStars();
} else {
	bindHoverOnTitles();  // mouseover on title
}
// switch for above, saved in a cookie
addLink(
	'<img alt="x" style="width:1em;" src="/upwiki/wikipedia/commons/a/a4/Vector_skin_-_page_not_in_the_watchlist.png">',
	mm.unwatchTip
).click(showAllStars);  // click on timestamp (enhanced recent changes) / empty space (non-enhanced)

if (document.cookie.indexOf('wlrvoff=1') != -1) switchRevert();


// OTHER LINKS
// "sort" link
addLink('↑↓', mm.sortTip).click(sortWatchlist);

// "expand all" link
if ($('.mw-enhancedchanges-arrow').length) {
	addLink('±', mm.expandAll).click(expandMultipleEdits);
}

// "switch revert" link
if ($('.mw-rollback-link').length) {
	addLink('⎌', mm.switchRevert).click(switchRevert);
}

// "only new" link
addLink(mm.onlynew, mm.onlynewTip).mousedown(onlyNewEntries).attr('id', 'listSince');


// TABS
if (window.wlNoTabs) return;
var mainTab = $('#ca-special, #ca-nstab-special').eq(0);  // "Special" tab

// change main tab into "watchlist Δ"
var wl = $.trim($('h1#firstHeading').text());
mainTab.find('a')  // replace "Special" tab text with "Watchlist"
	.text(wl + ' △')  // Δ is good but monobook makes is lowercase
	.attr('title', wl + ' — ' + mm.onlynewTipLowerCase)
	.on('mousedown keydown', onlyNewEntries);

// add "hideInterface" tab
mainTab.clone(true).removeClass('selected')
	.appendTo(mainTab.parent())
	.click(hideInterface)
	.attr('id','').attr('href', '#')
	.find('a')
	.text('↸').attr('title', mm.fullPage).attr('accesskey', '');

if (document.cookie.indexOf('wlmax=1') != -1) hideInterface();

if ($('.mw-changeslist .mw-rollback-link').first().css('visibility') == 'hidden') {
	$('.mw-changeslist .mw-rollback-link').css('visibility', 'visible');
}

return;


/* FUNCTIONS */
function addLink(txt, tip) {
	linksAt.before(' &nbsp;');
	return $('<a href="#" title="' + tip + '">' + txt + '</a>').insertBefore(linksAt);
}


function onlyNewEntries(e) {
	var url = window.location.href.split('#')[0];
	var days = (+(new Date()) - whenPageLoaded)/(1000 * 3600 * 24);
	if (days < 0) days = 0.01;  // negative might happen when adjusting local time
	e.target.href = /[?&]days=/.test(url)
		? url.replace(/([?&]days=)[^&]*/, '$1' + days)
		: url + (url.indexOf('?') < 0 ? '?':'&') + 'days=' + days;
	return true;
}

function showAllStars(e) {
	if (e) {
		e.preventDefault();
	}
	if (!allStarsOn) {
		mw.util.$content.find('a[href*="action=history"]')
			.each(function(i, lnk) {
				updateStar(getRow(this));
			});
		if (e) {
			var cookieDate = new Date($.now() + 1000 * 60 * 60 * 24 * 90).toGMTString();
			document.cookie = 'wlunw=1; expires=' + cookieDate + '; path=/';
		}
		allStarsOn = true;
	} else {  // otherwise remove
		mw.util.$content.find('a.aj-unwatch').not('unwatched').remove();
		document.cookie = 'wlunw=0; expires=' + (new Date()).toGMTString() + '; path=/';
		if (!hoverOnTitlesDone) {
			bindHoverOnTitles();
		}
		allStarsOn = false;
	}
}

function sortWatchlist(e) {
	e.preventDefault();
	if (sortingDone) {
		return alert(mm.sortDone);
	}
	mw.util.$content.find('h4').each(function() {  // sort all days separately
		var container = $(this).next('div, ul');
		var rows = container.children('li, table');
		// create sorting keys
		var key;
		rows.each(function(i) {
			// use built-in class: either li.watchlist-5-<title> or
			// table.mw-changeslist-ns100-<title> in enhanced recent changes
			key = /(\d+)-(\S+)/.exec(this.className) || ['', 0, ' '];  // logs might not have this class
			if (key[1] % 2) {
				key[1]--;  // sort talk page as if it was a base page
			}
			if (window.watchlistSortNamespaceOnly) {
				key[2] = zzz(i);  // keep timestamp order within each NS block
			}
			this.skey = zzz(key[1]) + ':' +  key[2];
		});
		// sort array and then HTML
		rows.sort(function(a, b) {
			return a.skey > b.skey ? 1 : (a.skey < b.skey ? -1 : 0);
		});
		for (i = 0; i < rows.length; i++) {
			container.append(rows.eq(i));
		}
	});
	sortingDone = true;
}

function expandMultipleEdits(e) {
	e.preventDefault();
	var i = 0, sp, state = $('.mw-changeslist .mw-collapsible')[0].style.display;
	while (sp = $('.mw-changeslist .mw-collapsible')[i++]) {
		if (sp.style.display == state) {
			$(sp).find('.mw-enhancedchanges-arrow').click();
		}
	}
}

function switchRevert(e) {
	if (e) e.preventDefault();
	if (!rvOffCSS) {
		rvOffCSS = mw.util.addCSS('.mw-rollback-link { display:none; }');
	} else {
		rvOffCSS.disabled = !rvOffCSS.disabled;
	}
	if (rvOffCSS.disabled) {
		// Поставлено 28 января 2017 года, чтобы сбросить куки, установленные ранее по путям
		// /wiki/ и /ruwiki/w/, которые могут перебивать новую настройку. Через год можно убирать.
		document.cookie = 'wlrvoff=0; expires=' + (new Date()).toGMTString() + '';
		
		document.cookie = 'wlrvoff=0; expires=' + (new Date()).toGMTString() + '; path=/';
	} else if (e) {
		var cookieDate = (new Date($.now() + 1000 * 60 * 60 * 24 * 90)).toGMTString();
		document.cookie = 'wlrvoff=1; expires=' + cookieDate + '; path=/';
    }
}

function bindHoverOnTitles() {  // find all "titles" links and assign hover event
	if (hoverOnTitlesDone) return;
	// $('#mw-content-text').find(isEnhanced ? 'table' : 'li');
	mw.util.$content.find('a[href*="action=history"]')
		.each(function() {
			getRow(this).find('a[href^="/wiki/"]:first').hover(hoverOnTitle);
		});
	hoverOnTitlesDone = true;
}

function hoverOnTitle(e) {  // on hover: add "unwatch" star after 1 second
	var lnk = $(this);
	if (e.type == 'mouseenter') {
		lnk.data('uwTimeout', setTimeout(function() {showStarOnHover(lnk)}, 1000));
	} else {
		clearTimeout(lnk.data('uwTimeout'));
	}
}

function showStarOnHover(lnk) {
	var row = getRow(lnk);
	updateStar(row);
	// attach mouseleave to remove the star
	if (row.attr('leaveAssigned')) return;
	row.attr('leaveAssigned', true);
	row.mouseleave(function(e) {
		var uw = $(this).find('.aj-unwatch');
		if (uw.length
			&& /unwatch/.test(uw.attr('href'))
			&& !/waiting|failure/.test(uw.attr('class'))
			&& !allStarsOn
		) {
			uw.remove();
		}
	});
}

function updateStar(row) {
	var star = row.find('a.aj-unwatch');
	if (!star.length) { // create
		star = $('<a class="aj-unwatch" href="' 
			+ row.find('a[href*="action=history"]').attr('href').replace(/&curid=\d+/,'') 
			+ '">x</a>')
				.click(ajaxUnwatch)
				.insertBefore(row.find('a[href^="/wiki/"]:first'))
				.after(' ');
	}
	// update
	var isUnwatched = row.hasClass('unwatched');
	var state = isUnwatched ? 'watch' : 'unwatch';
	star.attr('title', mw.msg(state))
		.attr('href', star.attr('href').replace(/&action=\w+/, '&action='+ state))
		.html('<img alt="x" style="width:0.6em;" src="/upwiki/wikipedia/commons/'
			+ (isUnwatched
				? 'a/a4/Vector_skin_-_page_not_in_the_watchlist.png'
				: 'f/f2/Vector_skin_-_page_in_the_watchlist.png')
			+ '" />');
}

function getRow(el) {
	return $(el).closest(isEnhanced ? 'tr' : 'li');
}

function ajaxUnwatch(e) {
	var xLnk = $(this), errMsg = '';
	var req = {
		token: mw.user.tokens.get('watchToken'),
		title: getLinkTitle(xLnk)
	};
	if (/&action=unwatch/.test(xLnk.attr('href'))) {
		req.unwatch = '';
	}
	xLnk.addClass('waiting');
	$.ajax({
		type:'POST', dataType: 'json',
		url: mw.util.wikiScript('api') + '?action=watch&format=json',
		data: req,
		timeout: 5000,
		success: function(resp) {
			if (resp.error) {
				errMsg = resp.error.info;
			} else if (!resp.watch) {
				errMsg = 'empty response';
			} else if (typeof resp.watch.unwatched == 'string') {
				unwatchSuccess(req.title, true);
			} else if (typeof resp.watch.watched   == 'string') {
				unwatchSuccess(req.title, false);
			} else {
				errMsg = 'unrecognized response';
			}
		},
		error: function(xhr, status, err) {
			errMsg = status + ':' + err;
		},
		complete: function() {  // update X link
			xLnk.removeClass('waiting');
			if (errMsg) xLnk.attr('title', 'API error: ' + errMsg).addClass('failure');
			else xLnk.removeClass('failure');
		}
	});
	return false;
}

function unwatchSuccess(name, isUnwatched) {
	// find full name of associated talk page (or vice versa)
	var ns = getTitleNamespace(name);
	var name2 = name;
	if (ns > 0) {
		name2 = name2.replace(/^.+?:/,'');  // remove old prefix
	}
	if (ns % 2) {
		ns--;
	} else {
		ns++;  // switch to "other" namespace
	}
	if (ns > 0) {
		name2 = mw.config.get('wgFormattedNamespaces')[ns] + ':' +  name2;  // add new prefix
	}
	// mark all rows that are either name or name2
	mw.util.$content.find('a[href*="action=history"]').each(function() {
		var ttl = getLinkTitle(this);
		if (ttl != name && ttl != name2) return;
		var row = getRow(this);
		row.toggleClass('unwatched', isUnwatched || false);
		updateStar(row);
		if (!isUnwatched && !allStarsOn) {
			row.find('a.aj-unwatch').remove();
		}
	});
}

function hideInterface(e) {
	if (e) e.preventDefault();
	
	if (!hideInterfaceCSS) {
		hideInterfaceCSS = mw.util.addCSS('\
			h4 { font-size: 90%; }\
			\
			#firstHeading,\
			div#siteNotice, #contentSub, fieldset#mw-watchlist-options,\
			div.mw-rc-label-legend, #mw-fr-watchlist-pending-notice { display: none; }');
	} else {
		hideInterfaceCSS.disabled = !hideInterfaceCSS.disabled;
	}
	
	if (e) {
		if (!hideInterfaceCSS.disabled) {
			var cookieDate = new Date($.now() + 1000 * 60 * 60 * 24 * 90).toGMTString();
			document.cookie = 'wlmax=1; expires=' + cookieDate + '; path=/';
		} else {
			document.cookie = 'wlmax=0; expires=' + (new Date()).toGMTString() + '; path=/';
		}
	}
}

function getTitleNamespace(title) {  // returns namespace number
	var prefix = /^(.+?):/.exec(title);
	if (!prefix) return 0;  // no prefix means article
	return mw.config.get('wgNamespaceIds')[ prefix[1].toLowerCase().replace(/ /g,'_') ] || 0;
}

function getLinkTitle(lnk) {  // gets 'title=' part from a link
	return mw.util.getParamValue('title', $(lnk).attr('href')).replace(/_/g,' ');
	// var ma = /(&|\?)title=([^&]+)/.exec($(lnk).attr('href'));
	// if (ma) return decodeURIComponent(ma[2]).replace(/_/g,' ');
	// else return '';
}

function zzz(s) {  // 5 -> 005
	s = s.toString();
	if (s.length == 1) {
		return '00' + s;
	} else if (s.length == 2) {
		return '0' + s;
	} else {
		return s;
	}
}

		}, 0);
	});  // main
}