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

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


var firstRun = true;
var whenPageLoaded = +(new Date()) - 20000 //add 20 sec just in case


if (mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist') {
var mainTab, hideInterfaceCSS, rvOffCSS
// Polyfill
var sortingDone, allStarsOn, hoverOnTitlesDone
if (!String.prototype.includes) {
String.prototype.includes = function (search, start) {
'use strict';
if (typeof start !== 'number') {
start = 0;
}
if (start + search.length > this.length) {
return false;
} else {
return this.indexOf(search, start) !== -1;
}
};
}
var SECONDS_IN_A_DAY = 1000 * 60 * 60 * 24;
var whenPageLoadedOrUpdated;
var hideInterfaceCSS, rvOffCSS;
var sortingDone, allStarsShown, hoverOnTitlesDone;
// ruwiki strings
var strings = {
watch: 'Следить',
unwatch: 'Не следить',
sortTip: 'Сортировать изменения по пространствам имён',
sortDone: 'Изменения уже отсортированы',
unwatchTip: 'Добавить/убрать звёздочки для вычёркивания страниц из списка наблюдения',
newOnly: 'Только новые',
newOnlyTip: 'Изменения с момента загрузки этой страницы',
watchlistTabNewOnlyTip: 'По клику откроются изменения с момента загрузки этой страницы',
all: 'Все',
allTip: 'Все изменения',
expandAll: 'Показать/спрятать все свёрнутые правки',
switchRevert: 'Спрятать/показать ссылки «откатить»',
fullPage: 'Спрятать/показать элементы интерфейса',
error: 'Ошибка',
unrecognizedReponse: 'ответ не распознан'
};
mw.hook('wikipage.content').add(function ($content) {
function main() {
/* FUNCTIONS */
function addLink(contents, tip, classes, href) {
return $('<a>')
.attr('href', href || 'javascript:')
.attr('title', tip)
.addClass(classes)
.append(contents)
.appendTo($linksIn);
}
function generateNewEntriesOnlyUrl() {
var uri = new mw.Uri();
uri.query.from = whenPageLoadedOrUpdated;
delete uri.query.days;
return uri.toString();
}
function generateAllEntriesUrl() {
var uri = new mw.Uri();
delete uri.query.from;
return uri.toString();
}
function newEntriesOnly(e) {
e.target.href = generateNewEntriesOnlyUrl();
}
function allEntries(e) {
e.target.href = generateAllEntriesUrl();
}
function showAllStars(e) {
if (e) {
e.preventDefault();
}
// cookie set = stars are on
if (!allStarsShown) {
$changeslist.find('.mw-title')
.each(function (i, link) {
updateStar(getRow($(this)));
});
if (e) {
var cookieDate = new Date($.now() + SECONDS_IN_A_DAY * 90).toGMTString();
document.cookie = 'wlunw=1; expires=' + cookieDate + '; path=/';
}
allStarsShown = true;
} else { // otherwise remove
$changeslist.find('.gadgetWatchlist-unwatchLink').remove();
document.cookie = 'wlunw=0; expires=' + (new Date()).toGMTString() + '; path=/';
if (!hoverOnTitlesDone) {
bindHoverOnTitles();
}
allStarsShown = false;
}
}
function sortWatchlist(e) {
e.preventDefault();
if (sortingDone) {
alert(strings.sortDone);
return;
}
$changeslist.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));
}
});
$('.mw-rcfilters-ui-changesListWrapperWidget-previousChangesIndicator').remove();
sortingDone = true;
}
function expandMultipleEdits(e) {
e.preventDefault();
var $collapsibles = $('.mw-changeslist .mw-collapsible:not(.mw-changeslist-legend)');
// If at lease one branch is collapsed, we expand everything.
$collapsibles
[$collapsibles.hasClass('mw-collapsed') ? 'filter' : 'not']('.mw-collapsed')
.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) {
document.cookie = 'wlrvoff=0; expires=' + (new Date()).toGMTString() + '; path=/';
} else if (e) {
var cookieDate = (new Date($.now() + SECONDS_IN_A_DAY * 90)).toGMTString();
document.cookie = 'wlrvoff=1; expires=' + cookieDate + '; path=/';
}
}
function bindHoverOnTitles() { // find all title links and assign hover event
if (hoverOnTitlesDone) return;
$changeslist.find('.mw-title')
.each(function () {
getRow($(this))
.find('.mw-title')
.children('a')
.hover(hoverOnTitle);
});
hoverOnTitlesDone = true;
}
function hoverOnTitle(e) { // on hover: add "unwatch" star after 1 second
var $link = $(this);
if (e.type === 'mouseenter') {
$link.data('unwatchTimeout', setTimeout(function () {
showStarOnHover($link);
}, 1000));
} else {
clearTimeout($link.data('unwatchTimeout'));
}
}
function showStarOnHover($link) {
var $row = getRow($link);
updateStar($row);
// attach mouseleave to remove the star
if ($row.data('leaveAssigned')) return;
$row.data('leaveAssigned', true);
$row.mouseleave(function (e) {
var $unwatchLink = $(this).find('.gadgetWatchlist-unwatchLink');
if ($unwatchLink.length &&
!($unwatchLink.hasClass('gadgetWatchlist-failure') ||
$unwatchLink.hasClass('gadgetWatchlist-waiting')
) &&
!allStarsShown
) {
$unwatchLink.remove();
}
});
}
function updateStar($row) {
var action = $row.hasClass('gadgetWatchlist-unwatchedRow') ? 'watch' : 'unwatch';
var $star = $row.find('.gadgetWatchlist-unwatchLink, .gadgetWatchlist-watchLink');
if (!$star.length) { // create
var $link = $row.find('.mw-title').children('a');
if (!$link.length) return;
$star = $('<a>')
.attr('href', $link.attr('href').replace(/\/wiki\//, '/ruwiki/w/index.php?title=') + '&action=' +
action)
.addClass('gadgetWatchlist-' + action + 'Link gadgetWatchlist-icon')
.click(changeWatchState)
.insertBefore($link);
} else { // update
$star
.attr('href', $star
.attr('href')
.replace(/&action=\w+/, '&action=' + action))
.removeClass('gadgetWatchlist-' + (action === 'unwatch' ? 'watch' : 'unwatch') + 'Link')
.addClass('gadgetWatchlist-' + action + 'Link');
}
$star.attr('title', mw.messages.get(action) || strings[action]);
}
function getRow($el) {
return $el.closest(isEnhanced ? 'tr' : 'li');
}
function changeWatchState(e) {
var $star = $(this), errorMsg = '';
var action;
var unwatch;
if ($star.attr('href').includes('&action=unwatch')) {
unwatch = '';
action = 'unwatch';
} else {
action = 'watch';
}
var titles = getLinkTitle($star);
$star
.removeClass('gadgetWatchlist-failure')
.addClass('gadgetWatchlist-waiting');
new mw.Api().postWithToken('watch', {
action: 'watch',
titles: titles,
unwatch: unwatch,
formatversion: 2,
}).then(
function (resp) {
if (!resp.watch || !resp.watch[0]) {
errorMsg = 'empty response';
} else if (typeof resp.watch[0].unwatched === 'boolean') {
changeWatchStateSuccess(titles, true);
} else if (typeof resp.watch[0].watched === 'boolean') {
changeWatchStateSuccess(titles, false);
} else {
errorMsg = strings.unrecognizedReponse;
}
},
function (code, data) {
var errorMsg = (
// HTTP error
data.textStatus ||


// API error
//ruwiki
(data.error && data.error.info)
var mm = {
);
sortTip: 'Сортировать страницы по пространствам',
if (errorMsg) {
sortDone: 'Изменения уже отсортированы',
errorMsg += ' (' + code + ')';
unwatchTip: 'Добавить звёздочки для вычёркивания страниц из списка наблюдения',
} else {
onlynew: 'Только новые',
errorMsg = code;
onlynewTip: 'Изменения с момента загрузки этой страницы',
}
onlynewTipLowerCase: 'изменения с момента загрузки этой страницы',
}
expandAll: 'Показать/спрятать все свёрнутые правки',
).always(function () { // update unwatch link
switchRevert: 'Спрятать/показать ссылки «откатить»',
$star.removeClass('gadgetWatchlist-waiting');
fullPage: 'Спрятать/показать элементы интерфейса'
if (errorMsg) {
};
$star

.attr('title', strings.error + ': ' + errorMsg)
var isEnhanced = mw.util.$content.find('ul.special').length == 0 //RC type in preferences
.addClass('gadgetWatchlist-failure gadgetWatchlist-' + action + 'Link');

}

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


function changeWatchStateSuccess(name, isUnwatched) {
//UNWATCH links:
// find full name of associated talk page (or vice versa)
//on every line
var ns = getTitleNamespace(name);
if( document.cookie.indexOf('wlunw=1') != -1 ) $(showAllStars)
var name2 = name;
else $(bindHoverOnTitles) //mouseover on title
if (ns > 0) {
// switch for above, saved in a cookie
name2 = name2.replace(/^.+?:/, ''); // remove old prefix
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)
ns = ns % 2 ? ns - 1 : ns + 1; // switch to associated namespace
//click on timestamp (enhanced RC) / empty space (non-enhanced)
if (ns > 0) {

name2 = mw.config.get('wgFormattedNamespaces')[ns] + ':' + name2; // add new prefix
if (document.cookie.indexOf('wlrvoff=1') != -1) switchRevert();
}

// mark all rows that are either name or name2

$changeslist.find('.mw-changeslist-title').each(function () {
// "sort" link
var title = getLinkTitle($(this));
addLnk('↑↓', mm.sortTip).click(sortWatchlist);
if (title !== name && title !== name2) return;

var $row = getRow($(this));
// "expand all" link
$row.toggleClass('gadgetWatchlist-unwatchedRow', isUnwatched);
if ($('.mw-enhancedchanges-arrow').length) {
if (!isUnwatched && !allStarsShown) {
addLnk('±', mm.expandAll).click(expandMultipleEdits);
$row.find('.gadgetWatchlist-watchLink').remove();
}
} else {

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

function hideInterface(e) {
// "only new" link
if (e) {
addLnk(mm.onlynew, mm.onlynewTip).mousedown(onlyNewEntries).attr('id', 'listSince');
e.preventDefault();

}

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

#contentSub,\
//change main tab into "watchlist Δ"
.mw-wlheader-showupdated,\
var wl = $.trim( $('h1#firstHeading').text() )
fieldset#mw-watchlist-options,\
mainTab.find('a') //replace "Special" tab text with "Watchlist"
div.mw-rc-label-legend,\
.text(wl + ' △') //Δ is good but monobook makes is lowercase
#mw-fr-watchlist-pending-notice,\
.attr('title', wl + ' — ' + mm.onlynewTipLowerCase)
.mw-indicators,\
.on('mousedown keydown', onlyNewEntries)
.mw-rcfilters-ui-filterTagMultiselectWidget,\

.mw-rcfilters-ui-watchlistTopSectionWidget-savedLinksTable,\
//add "hideInterface" tab
.mw-rcfilters-ui-watchlistTopSectionWidget-editWatchlistButton,\
mainTab.clone(true).removeClass('selected')
.mw-rcfilters-ui-watchlistTopSectionWidget-separator {\
.appendTo(mainTab.parent())
display: none;\
.click(hideInterface)
}\
.attr('id','').attr('href','#')
\
.find('a')
.client-js .mw-special-Watchlist .rcfilters-head.rcfilters-head {\
.text('↸').attr('title', mm.fullPage).attr('accesskey','')
min-height: auto;\

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


if (!hideInterfaceCSS) {
return
// If the new filters are on, wait until the interface is initialized.

if (!mw.user.options.get('wlenhancedfilters-disable')) {

hideInterfaceCSS = mw.util.addCSS(hideInterfaceCSSCode);

$('.mw-rcfilters-ui-markSeenButtonWidget')
.wrap('<div>')
.parent()
function addLnk(txt, tip){
.addClass('mw-rcfilters-ui-markSeenButtonWidget-container')
linksAt.before(' &nbsp;')
.appendTo('.mw-rcfilters-ui-watchlistTopSectionWidget-watchlistDetails');
return $('<a href=# title="'+tip+'">'+txt+'</a>').insertBefore(linksAt)
} else {
}
hideInterfaceCSS = mw.util.addCSS(hideInterfaceCSSCode);

}

} else {
function onlyNewEntries(e) {
hideInterfaceCSS.disabled = !hideInterfaceCSS.disabled;
var url = window.location.href.split('#')[0]
if (!mw.user.options.get('wlenhancedfilters-disable')) {
var days = ( +(new Date()) - whenPageLoaded)/(1000 * 3600 * 24)
$('.mw-rcfilters-ui-markSeenButtonWidget-container')
if( days < 0 ) days = 0.01 //negative might happen when adjusting local time
.appendTo(hideInterfaceCSS.disabled ?
e.target.href = /[?&]days=/.test(url)
'.mw-rcfilters-ui-watchlistTopSectionWidget-savedLinksTable .mw-rcfilters-ui-cell:first-child' :
? url.replace(/([?&]days=)[^&]*/, '$1'+days)
'.mw-rcfilters-ui-watchlistTopSectionWidget-watchlistDetails');
: url + (url.indexOf('?') < 0 ? '?':'&') + 'days=' + days
}
return true
}
}

if (e) {

if (!hideInterfaceCSS.disabled) {

var cookieDate = new Date($.now() + SECONDS_IN_A_DAY * 90).toGMTString();
function showAllStars(e){
document.cookie = 'wlmax=1; expires=' + cookieDate + '; path=/';
if( !allStarsOn ){
} else {
mw.util.$content.find('a[href*="action=history"]')
document.cookie = 'wlmax=0; expires=' + (new Date()).toGMTString() + '; path=/';
.each( function(i, lnk){ updateStar( getRow(this) ) } )
}
document.cookie = 'wlunw=1; path=/'
}
allStarsOn = true
}
}else{ //otherwise remove
mw.util.$content.find('a.aj-unwatch').not('unwatched').remove()
function getTitleNamespace(title) { // returns namespace number
document.cookie = 'wlunw=0; expires=' + (new Date()).toGMTString() + '; path=/'
var prefix = /^(.+?):/.exec(title);
if( !hoverOnTitlesDone ) bindHoverOnTitles()
if (!prefix) {
allStarsOn = false
return 0; // no prefix means article
}
}
return false
return mw.config.get('wgNamespaceIds')[ prefix[1].toLowerCase().replace(/ /g, '_') ] || 0;
}
}

function sortWatchlist(e){
function getLinkTitle($link) { // gets title for unwatch/watch links & common page links
e.preventDefault()
// Titles can be absent for .mw-changeslist-title elements because of Popups extension.
if( sortingDone ) return alert(mm.sortDone)
var title = $link.filter('.mw-changeslist-title').attr('title');
mw.util.$content.find('h4').each(function(){ //sort all days separately
var container = $(this).next('div, ul')
if (!title) {
var rows = container.children('li, table')
title = mw.util.getParamValue('title', $link.attr('href'));
//create sorting keys
title = title && title.replace(/_/g, ' ');
var key
}
rows.each( function(i){
//use built-in class: either li.watchlist-5-<title> or table.mw-changeslist-ns100-<title> in enhanced RC
if (!title) {
key = /(\d+)-(\S+)/.exec( this.className ) || ['', 0, ' '] //logs might not have this class
title = $link.filter('.mw-changeslist-title').text();
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]
return title;
})
}
//sort array and then HTML
rows.sort(function(a,b){ return a.skey > b.skey ? 1 : ( a.skey < b.skey ? -1 : 0 ) })
function zzz(s) { // 5 -> 005
for( i=0; i<rows.length; i++ ) container.append( rows.eq(i) )
s = s.toString();
})
if (s.length === 1) {
sortingDone = true
return '00' + s;
}
} else if (s.length === 2) {

return '0' + s;
function expandMultipleEdits(e){
} else {
e.preventDefault()
return s;
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 zeroPad(n, p) {

return ('0000' + n).slice(-p);
function switchRevert(e) {
}
if (e) {
e.preventDefault();
function generateTimestamp(date) {
}
return (
if (!rvOffCSS) {
zeroPad(date.getUTCFullYear(), 4) +
rvOffCSS = mw.util.addCSS('.mw-rollback-link { display:none; }')
zeroPad(date.getUTCMonth() + 1, 2) +
document.cookie = 'wlrvoff=1; expires=' + (new Date($.now() + 1000 * 60 * 60 * 24 * 365)).toGMTString() + '; path=/';
zeroPad(date.getUTCDate(), 2) +
} else {
zeroPad(date.getUTCHours(), 2) +
rvOffCSS.disabled = !rvOffCSS.disabled;
zeroPad(date.getUTCMinutes(), 2) +
document.cookie = 'wlrvoff=0; expires=' + (new Date()).toGMTString() + '; path=/';
zeroPad(date.getUTCSeconds(), 2)
}
);
}
}


function bindHoverOnTitles(){ //find all "titles" links and assign hover event
/* MAIN BLOCK */
if( hoverOnTitlesDone ) return
//$('#mw-content-text').find( isEnhanced ? 'table' : 'li')
// add 5–20 seconds just in case (for example, the scripting could take too much)
mw.util.$content.find('a[href*="action=history"]')
whenPageLoadedOrUpdated = generateTimestamp(new Date(new Date().getTime() - (firstRun ? 20000 : 5000)));
.each( function(){
getRow(this).find('a[href^="/wiki/"]:first').hover( hoverOnTitle )
sortingDone = false;
})
allStarsShown = false;
hoverOnTitlesDone = true
hoverOnTitlesDone = false;
}

// UNWATCH LINKS

// on every line
function hoverOnTitle(e){ //on hover: add "unwatch" star after 1s
if (document.cookie.includes('wlunw=1')) {
var lnk = $(this)
showAllStars();
if( e.type == 'mouseenter' )
} else {
lnk.data( 'uwTimeout', setTimeout( function(){showStarOnHover(lnk)}, 1000 ) )
bindHoverOnTitles(); // mouseover on title
else
}
clearTimeout( lnk.data( 'uwTimeout' ) )
}
if (firstRun) {

if (document.cookie.includes('wlrvoff=1')) {

switchRevert();
function showStarOnHover(lnk){
}
var row = getRow(lnk)
updateStar(row)
// find insertion point for links
//attach mouseleave to remove the star
$linksIn = $('<div>')
if( row.attr('leaveAssigned') ) return
.addClass('mw-rcfilters-ui-cell gadgetWatchlist-linksContainer')
row.attr('leaveAssigned', true)
.insertBefore($(
row.mouseleave( function(e){
!mw.user.options.get('wlenhancedfilters-disable') ?
var uw = $(this).find('.aj-unwatch')
'.mw-rcfilters-ui-watchlistTopSectionWidget-savedLinks' :
if( uw.length
'.wlinfo'
&& /unwatch/.test( uw.attr('href') )
));
&& !/waiting|failure/.test( uw.attr('class') )
&& !allStarsOn
// "show all stars" link
)
addLink('', strings.unwatchTip, 'gadgetWatchlist-icon gadgetWatchlist-icon-unwatched')
uw.remove()
.click(showAllStars);
})

// FUNCTION LINKS
}
// "sort" link

addLink('↑↓', strings.sortTip).click(sortWatchlist);


// "expand all" link

// Auto-update could be used, so even if there is no $('.mw-enhancedchanges-arrow') elements, we keep
function updateStar(row){
// the button.
var star = row.find('a.aj-unwatch')
if (isEnhanced) {
if( !star.length ){ //create
addLink('±', strings.expandAll).click(expandMultipleEdits);
star = $('<a class=aj-unwatch href="'
}
+ row.find('a[href*="action=history"]').attr('href').replace(/&curid=\d+/,'')
+ '">x</a>')
// "switch revert" link
.click(ajaxUnwatch)
if (mw.config.get('wgUserGroups').indexOf('rollbacker') !== -1 ||
.insertBefore( row.find('a[href^="/wiki/"]:first') )
mw.config.get('wgUserGroups').indexOf('sysop') !== -1
.after(' ')
) {
}
addLink('⎌', strings.switchRevert).click(switchRevert);
//update
}
var isUnwatched = row.hasClass('unwatched')
var state = isUnwatched ? 'watch' : 'unwatch'
// "new only" link
star.attr( 'title', mw.msg(state) )
addLink(strings.newOnly, strings.newOnlyTip, undefined, generateNewEntriesOnlyUrl())
.attr( 'href', star.attr('href').replace(/&action=\w+/, '&action='+ state) )
.mousedown(newEntriesOnly);
.html('<img alt=x style="width:0.6em" '
+ 'src="/upwiki/wikipedia/commons/'
if (mw.util.getParamValue('from') !== null) {
+ ( isUnwatched
// "all" link
? 'a/a4/Vector_skin_-_page_not_in_the_watchlist.png'
addLink(strings.all, strings.allTip, undefined, generateAllEntriesUrl()).mousedown(allEntries);
: 'f/f2/Vector_skin_-_page_in_the_watchlist.png'
}
)
+ '" />')
// TABS
}
if (!window.wlNoTabs) {

var $mainTab = $('#ca-nstab-special').first(); // "Special" tab
function getRow(el){
return $(el).closest(isEnhanced ? 'tr' : 'li')
// change main tab into "watchlist Δ"
}
var watchlistTitle = $.trim($('#firstHeading').text());

$mainTab
.find('a') // replace "Special" with "Watchlist" as tab text

.text(watchlistTitle + ' △') // Δ is good but monobook makes is lowercase

.attr('title', strings.watchlistTabNewOnlyTip)
.attr('href', generateNewEntriesOnlyUrl())

.on('mousedown keydown', newEntriesOnly);

// add "hideInterface" tab

$mainTab

.clone(true)
function ajaxUnwatch(e) {
.attr('id', '')
var xLnk = $(this), errMsg = ''
.attr('href', '')
var req = { token: mw.user.tokens.get('watchToken'),
.removeClass('selected')
title: getLinkTitle(xLnk) }
.click(hideInterface)
if( /&action=unwatch/.test(xLnk.attr('href')) ) req.unwatch = ''
.appendTo($mainTab.parent())
xLnk.addClass('waiting')
.find('a')
$.ajax({
.text('↸')
type:'POST', dataType: 'json',
.attr('title', strings.fullPage)
url: mw.util.wikiScript('api') + '?action=watch&format=json',
.attr('accesskey', '');
data: req,
}
timeout: 5000,
success: function(resp){
if( resp.error ) errMsg = resp.error.info
// OTHER TASKS
else if( !resp.watch ) errMsg = 'empty response'
if (document.cookie.includes('wlmax=1')) {
else if( typeof resp.watch.unwatched == 'string') unwatchSuccess( req.title, true )
hideInterface();
else if( typeof resp.watch.watched == 'string') unwatchSuccess( req.title, false )
}
else errMsg = 'unrecognized response'
},
mw.util.addCSS('\
error: function(xhr, status, err) {
.mw-special-Watchlist .mw-changeslist .mw-rollback-link.mw-rollback-link {\
errMsg = status + ':' + err
visibility: visible;\
},
}\
complete: function(){ //update X link
');
xLnk.removeClass('waiting')
if( errMsg ) xLnk.attr( 'title', 'API error: ' + errMsg ).addClass('failure')
firstRun = false;
else xLnk.removeClass('failure')
}
}
})
return false
return;
}
}

// Occurs in watchlist when mediawiki.rcfilters.filters.ui module for some reason fires

// wikipage.content for the second time with an element that is not in the DOM,

// fieldset#mw-watchlist-options (in mw.rcfilters.ui.FormWrapperWidget.prototype.onChangesModelUpdate
function unwatchSuccess(name, isUnwatched) {
// function).
//find full name of associated talk page (or vice versa)
if (!$content.parent().length) return;
var ns = getTitleNamespace(name)
var name2 = name
var $changeslist = $content.find('.mw-changeslist');
if( ns > 0 ) name2 = name2.replace(/^.+?:/,'') //remove old prefix
if( ns % 2 ) ns--; else ns++ //switch to "other" namespace
// Recent changes type in preferences. Don't confuse enhanced recent changes with enhanced filters.
if( ns > 0 ) name2 = mw.config.get( 'wgFormattedNamespaces' )[ns] + ':' + name2 //add new prefix
// mw.user.options.get('usenewrc') shouldn't be used here: RC mode could be set from URL.
//mark all rows that are either name or name2
var isEnhanced = !$changeslist.find('ul.special').length;
mw.util.$content.find('a[href*="action=history"]').each(function(){
var ttl = getLinkTitle(this)
if (mw.util.getParamValue('from') !== null) {
if( ttl != name && ttl != name2 ) return
$('#mw-watchlist-form input[name="from"]').remove();
var row = getRow(this)
}
row.toggleClass('unwatched', isUnwatched || false)
updateStar(row)
if (!mw.user.options.get('wlenhancedfilters-disable')) {
if( !isUnwatched && !allStarsOn) row.find('a.aj-unwatch').remove()
mw.hook('structuredChangeFilters.ui.initialized').add(main);
})
} else {
}
if (!navigator.userAgent.toLowerCase().includes('firefox')) {

main();

} else {

// Cure for Firefox

setTimeout(main, 0);

}

}

});
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 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

Текущая версия от 04:06, 15 августа 2021

(function () {

var firstRun = true;

if (mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist') {
	// Polyfill
	if (!String.prototype.includes) {
		String.prototype.includes = function (search, start) {
			'use strict';
			if (typeof start !== 'number') {
				start = 0;
			}
			
			if (start + search.length > this.length) {
				return false;
			} else {
				return this.indexOf(search, start) !== -1;
			}
		};
	}
	
	var SECONDS_IN_A_DAY = 1000 * 60 * 60 * 24;
	
	var whenPageLoadedOrUpdated;
	var hideInterfaceCSS, rvOffCSS;
	var sortingDone, allStarsShown, hoverOnTitlesDone;
	
	// ruwiki strings
	var strings = {
		watch: 'Следить',
		unwatch: 'Не следить',
		sortTip: 'Сортировать изменения по пространствам имён',
		sortDone: 'Изменения уже отсортированы',
		unwatchTip: 'Добавить/убрать звёздочки для вычёркивания страниц из списка наблюдения',
		newOnly: 'Только новые',
		newOnlyTip: 'Изменения с момента загрузки этой страницы',
		watchlistTabNewOnlyTip: 'По клику откроются изменения с момента загрузки этой страницы',
		all: 'Все',
		allTip: 'Все изменения',
		expandAll: 'Показать/спрятать все свёрнутые правки',
		switchRevert: 'Спрятать/показать ссылки «откатить»',
		fullPage: 'Спрятать/показать элементы интерфейса',
		error: 'Ошибка',
		unrecognizedReponse: 'ответ не распознан'
	};
	
	mw.hook('wikipage.content').add(function ($content) {
		function main() {
			/* FUNCTIONS */
			
			function addLink(contents, tip, classes, href) {
				return $('<a>')
					.attr('href', href || 'javascript:')
					.attr('title', tip)
					.addClass(classes)
					.append(contents)
					.appendTo($linksIn);
			}
			
			function generateNewEntriesOnlyUrl() {
				var uri = new mw.Uri();
				uri.query.from = whenPageLoadedOrUpdated;
				delete uri.query.days;
				return uri.toString();
			}
			
			function generateAllEntriesUrl() {
				var uri = new mw.Uri();
				delete uri.query.from;
				return uri.toString();
			}
			
			function newEntriesOnly(e) {
				e.target.href = generateNewEntriesOnlyUrl();
			}
			
			function allEntries(e) {
				e.target.href = generateAllEntriesUrl();
			}
			
			function showAllStars(e) {
				if (e) {
					e.preventDefault();
				}
				// cookie set = stars are on
				if (!allStarsShown) {
					$changeslist.find('.mw-title')
						.each(function (i, link) {
							updateStar(getRow($(this)));
						});
					if (e) {
						var cookieDate = new Date($.now() + SECONDS_IN_A_DAY * 90).toGMTString();
						document.cookie = 'wlunw=1; expires=' + cookieDate + '; path=/';
					}
					allStarsShown = true;
				} else {  // otherwise remove
					$changeslist.find('.gadgetWatchlist-unwatchLink').remove();
					document.cookie = 'wlunw=0; expires=' + (new Date()).toGMTString() + '; path=/';
					if (!hoverOnTitlesDone) {
						bindHoverOnTitles();
					}
					allStarsShown = false;
				}
			}
			
			function sortWatchlist(e) {
				e.preventDefault();
				if (sortingDone) {
					alert(strings.sortDone);
					return;
				}
				$changeslist.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));
					}
				});
				$('.mw-rcfilters-ui-changesListWrapperWidget-previousChangesIndicator').remove();
				sortingDone = true;
			}
			
			function expandMultipleEdits(e) {
				e.preventDefault();
				var $collapsibles = $('.mw-changeslist .mw-collapsible:not(.mw-changeslist-legend)');
				// If at lease one branch is collapsed, we expand everything.
				$collapsibles
					[$collapsibles.hasClass('mw-collapsed') ? 'filter' : 'not']('.mw-collapsed')
					.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) {
					document.cookie = 'wlrvoff=0; expires=' + (new Date()).toGMTString() + '; path=/';
				} else if (e) {
					var cookieDate = (new Date($.now() + SECONDS_IN_A_DAY * 90)).toGMTString();
					document.cookie = 'wlrvoff=1; expires=' + cookieDate + '; path=/';
				}
			}
			
			function bindHoverOnTitles() {  // find all title links and assign hover event
				if (hoverOnTitlesDone) return;
				$changeslist.find('.mw-title')
					.each(function () {
						getRow($(this))
							.find('.mw-title')
							.children('a')
							.hover(hoverOnTitle);
					});
				hoverOnTitlesDone = true;
			}
			
			function hoverOnTitle(e) {  // on hover: add "unwatch" star after 1 second
				var $link = $(this);
				if (e.type === 'mouseenter') {
					$link.data('unwatchTimeout', setTimeout(function () {
						showStarOnHover($link);
					}, 1000));
				} else {
					clearTimeout($link.data('unwatchTimeout'));
				}
			}
			
			function showStarOnHover($link) {
				var $row = getRow($link);
				updateStar($row);
				// attach mouseleave to remove the star
				if ($row.data('leaveAssigned')) return;
				$row.data('leaveAssigned', true);
				$row.mouseleave(function (e) {
					var $unwatchLink = $(this).find('.gadgetWatchlist-unwatchLink');
					if ($unwatchLink.length &&
						!($unwatchLink.hasClass('gadgetWatchlist-failure') ||
							$unwatchLink.hasClass('gadgetWatchlist-waiting')
						) &&
						!allStarsShown
					) {
						$unwatchLink.remove();
					}
				});
			}
			
			function updateStar($row) {
				var action = $row.hasClass('gadgetWatchlist-unwatchedRow') ? 'watch' : 'unwatch';
				var $star = $row.find('.gadgetWatchlist-unwatchLink, .gadgetWatchlist-watchLink');
				if (!$star.length) {  // create
					var $link = $row.find('.mw-title').children('a');
					if (!$link.length) return;
					$star = $('<a>')
						.attr('href', $link.attr('href').replace(/\/wiki\//, '/ruwiki/w/index.php?title=') + '&action=' +
							action)
						.addClass('gadgetWatchlist-' + action + 'Link gadgetWatchlist-icon')
						.click(changeWatchState)
						.insertBefore($link);
				} else {  // update
					$star
						.attr('href', $star
							.attr('href')
							.replace(/&action=\w+/, '&action=' + action))
						.removeClass('gadgetWatchlist-' + (action === 'unwatch' ? 'watch' : 'unwatch') + 'Link')
						.addClass('gadgetWatchlist-' + action + 'Link');
				}
				$star.attr('title', mw.messages.get(action) || strings[action]);
			}
			
			function getRow($el) {
				return $el.closest(isEnhanced ? 'tr' : 'li');
			}
			
			function changeWatchState(e) {
				var $star = $(this), errorMsg = '';
				var action;
				var unwatch;
				if ($star.attr('href').includes('&action=unwatch')) {
					unwatch = '';
					action = 'unwatch';
				} else {
					action = 'watch';
				}
				var titles = getLinkTitle($star);
				$star
					.removeClass('gadgetWatchlist-failure')
					.addClass('gadgetWatchlist-waiting');
				new mw.Api().postWithToken('watch', {
					action: 'watch',
					titles: titles,
					unwatch: unwatch,
					formatversion: 2,
				}).then(
					function (resp) {
						if (!resp.watch || !resp.watch[0]) {
							errorMsg = 'empty response';
						} else if (typeof resp.watch[0].unwatched === 'boolean') {
							changeWatchStateSuccess(titles, true);
						} else if (typeof resp.watch[0].watched === 'boolean') {
							changeWatchStateSuccess(titles, false);
						} else {
							errorMsg = strings.unrecognizedReponse;
						}
					},
					function (code, data) {
						var errorMsg = (
							// HTTP error
							data.textStatus ||

							// API error
							(data.error && data.error.info)
						);
						if (errorMsg) {
							errorMsg += ' (' + code + ')';
						} else {
							errorMsg = code;
						}
					}
				).always(function () {  // update unwatch link
					$star.removeClass('gadgetWatchlist-waiting');
					if (errorMsg) {
						$star
							.attr('title', strings.error + ': ' + errorMsg)
							.addClass('gadgetWatchlist-failure gadgetWatchlist-' + action + 'Link');
					}
				});
				e.preventDefault();
			}
			
			function changeWatchStateSuccess(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
				}
				ns = ns % 2 ? ns - 1 : ns + 1;  // switch to associated namespace
				if (ns > 0) {
					name2 = mw.config.get('wgFormattedNamespaces')[ns] + ':' + name2;  // add new prefix
				}
				// mark all rows that are either name or name2
				$changeslist.find('.mw-changeslist-title').each(function () {
					var title = getLinkTitle($(this));
					if (title !== name && title !== name2) return;
					var $row = getRow($(this));
					$row.toggleClass('gadgetWatchlist-unwatchedRow', isUnwatched);
					if (!isUnwatched && !allStarsShown) {
						$row.find('.gadgetWatchlist-watchLink').remove();
					} else {
						updateStar($row);
					}
				});
			}
			
			function hideInterface(e) {
				if (e) {
					e.preventDefault();
				}
				
				var hideInterfaceCSSCode = '\
					#firstHeading,\
					div#siteNotice,\
					#contentSub,\
					.mw-wlheader-showupdated,\
					fieldset#mw-watchlist-options,\
					div.mw-rc-label-legend,\
					#mw-fr-watchlist-pending-notice,\
					.mw-indicators,\
					.mw-rcfilters-ui-filterTagMultiselectWidget,\
					.mw-rcfilters-ui-watchlistTopSectionWidget-savedLinksTable,\
					.mw-rcfilters-ui-watchlistTopSectionWidget-editWatchlistButton,\
					.mw-rcfilters-ui-watchlistTopSectionWidget-separator {\
						display: none;\
					}\
					\
					.client-js .mw-special-Watchlist .rcfilters-head.rcfilters-head {\
						min-height: auto;\
					}\
				';
				
				if (!hideInterfaceCSS) {
					// If the new filters are on, wait until the interface is initialized.
					if (!mw.user.options.get('wlenhancedfilters-disable')) {
						hideInterfaceCSS = mw.util.addCSS(hideInterfaceCSSCode);
						$('.mw-rcfilters-ui-markSeenButtonWidget')
							.wrap('<div>')
							.parent()
							.addClass('mw-rcfilters-ui-markSeenButtonWidget-container')
							.appendTo('.mw-rcfilters-ui-watchlistTopSectionWidget-watchlistDetails');
					} else {
						hideInterfaceCSS = mw.util.addCSS(hideInterfaceCSSCode);
					}
				} else {
					hideInterfaceCSS.disabled = !hideInterfaceCSS.disabled;
					if (!mw.user.options.get('wlenhancedfilters-disable')) {
						$('.mw-rcfilters-ui-markSeenButtonWidget-container')
							.appendTo(hideInterfaceCSS.disabled ?
								'.mw-rcfilters-ui-watchlistTopSectionWidget-savedLinksTable .mw-rcfilters-ui-cell:first-child' :
								'.mw-rcfilters-ui-watchlistTopSectionWidget-watchlistDetails');
					}
				}
				
				if (e) {
					if (!hideInterfaceCSS.disabled) {
						var cookieDate = new Date($.now() + SECONDS_IN_A_DAY * 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($link) {  // gets title for unwatch/watch links & common page links
				// Titles can be absent for .mw-changeslist-title elements because of Popups extension.
				var title = $link.filter('.mw-changeslist-title').attr('title');
				
				if (!title) {
					title = mw.util.getParamValue('title', $link.attr('href'));
					title = title && title.replace(/_/g, ' ');
				}
				
				if (!title) {
					title = $link.filter('.mw-changeslist-title').text();
				}
				
				return title;
			}
			
			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;
				}
			}
			
			function zeroPad(n, p) {
				return ('0000' + n).slice(-p);
			}
			
			function generateTimestamp(date) {
				return (
					zeroPad(date.getUTCFullYear(), 4) +
					zeroPad(date.getUTCMonth() + 1, 2) +
					zeroPad(date.getUTCDate(), 2) +
					zeroPad(date.getUTCHours(), 2) +
					zeroPad(date.getUTCMinutes(), 2) +
					zeroPad(date.getUTCSeconds(), 2)
				);
			}
			
			
			/* MAIN BLOCK */
			
			// add 5–20 seconds just in case (for example, the scripting could take too much)
			whenPageLoadedOrUpdated = generateTimestamp(new Date(new Date().getTime() - (firstRun ? 20000 : 5000)));
			
			sortingDone = false;
			allStarsShown = false;
			hoverOnTitlesDone = false;
			
			// UNWATCH LINKS
			// on every line
			if (document.cookie.includes('wlunw=1')) {
				showAllStars();
			} else {
				bindHoverOnTitles();  // mouseover on title
			}
			
			if (firstRun) {
				if (document.cookie.includes('wlrvoff=1')) {
					switchRevert();
				}
				
				// find insertion point for links
				$linksIn = $('<div>')
					.addClass('mw-rcfilters-ui-cell gadgetWatchlist-linksContainer')
					.insertBefore($(
						!mw.user.options.get('wlenhancedfilters-disable') ?
							'.mw-rcfilters-ui-watchlistTopSectionWidget-savedLinks' :
							'.wlinfo'
					));
				
				// "show all stars" link
				addLink('', strings.unwatchTip, 'gadgetWatchlist-icon gadgetWatchlist-icon-unwatched')
					.click(showAllStars);
				
				// FUNCTION LINKS
				// "sort" link
				addLink('↑↓', strings.sortTip).click(sortWatchlist);
				
				// "expand all" link
				// Auto-update could be used, so even if there is no $('.mw-enhancedchanges-arrow') elements, we keep
				// the button.
				if (isEnhanced) {
					addLink('±', strings.expandAll).click(expandMultipleEdits);
				}
				
				// "switch revert" link
				if (mw.config.get('wgUserGroups').indexOf('rollbacker') !== -1 ||
					mw.config.get('wgUserGroups').indexOf('sysop') !== -1
				) {
					addLink('⎌', strings.switchRevert).click(switchRevert);
				}
				
				// "new only" link
				addLink(strings.newOnly, strings.newOnlyTip, undefined, generateNewEntriesOnlyUrl())
					.mousedown(newEntriesOnly);
				
				if (mw.util.getParamValue('from') !== null) {
					// "all" link
					addLink(strings.all, strings.allTip, undefined, generateAllEntriesUrl()).mousedown(allEntries);
				}
				
				// TABS
				if (!window.wlNoTabs) {
					var $mainTab = $('#ca-nstab-special').first();  // "Special" tab
					
					// change main tab into "watchlist Δ"
					var watchlistTitle = $.trim($('#firstHeading').text());
					$mainTab
						.find('a')  // replace "Special" with "Watchlist" as tab text
						.text(watchlistTitle + ' △')  // Δ is good but monobook makes is lowercase
						.attr('title', strings.watchlistTabNewOnlyTip)
						.attr('href', generateNewEntriesOnlyUrl())
						.on('mousedown keydown', newEntriesOnly);
					
					// add "hideInterface" tab
					$mainTab
						.clone(true)
							.attr('id', '')
							.attr('href', '')
							.removeClass('selected')
							.click(hideInterface)
							.appendTo($mainTab.parent())
						.find('a')
							.text('↸')
							.attr('title', strings.fullPage)
							.attr('accesskey', '');
				}
				
				
				// OTHER TASKS
				if (document.cookie.includes('wlmax=1')) {
					hideInterface();
				}
				
				mw.util.addCSS('\
					.mw-special-Watchlist .mw-changeslist .mw-rollback-link.mw-rollback-link {\
						visibility: visible;\
					}\
				');
				
				firstRun = false;
			}
			
			return;
		}
		
		// Occurs in watchlist when mediawiki.rcfilters.filters.ui module for some reason fires
		// wikipage.content for the second time with an element that is not in the DOM,
		// fieldset#mw-watchlist-options (in mw.rcfilters.ui.FormWrapperWidget.prototype.onChangesModelUpdate
		// function).
		if (!$content.parent().length) return;
		
		var $changeslist = $content.find('.mw-changeslist');
		
		// Recent changes type in preferences. Don't confuse enhanced recent changes with enhanced filters.
		// mw.user.options.get('usenewrc') shouldn't be used here: RC mode could be set from URL.
		var isEnhanced = !$changeslist.find('ul.special').length;
		
		if (mw.util.getParamValue('from') !== null) {
			$('#mw-watchlist-form input[name="from"]').remove();
		}
		
		if (!mw.user.options.get('wlenhancedfilters-disable')) {
			mw.hook('structuredChangeFilters.ui.initialized').add(main);
		} else {
			if (!navigator.userAgent.toLowerCase().includes('firefox')) {
				main();
			} else {
				// Cure for Firefox
				setTimeout(main, 0);
			}
		}
	});
}

})();