/**
* Top Categories
*
* Author: Serhio Magpie
* Licenses: MIT, CC BY-SA
*/
( function () {
var _config = {
linkContainer: $( '#contentSub' ),
infoContainer: $( '.mw-parser-output' ),
action: mw.config.get( 'wgAction' ),
nameSpace: mw.config.get( 'wgNamespaceNumber' ),
pageTitle: mw.config.get( 'wgPageName' ).replaceAll( '_', ' ' ),
articlePath : mw.config.get( 'wgArticlePath' ),
separator: ' | ',
outputCount: 50
},
_strings = {
'top-categories-label': 'Отобразить самые включаемые категории',
'top-categories-link-self': 'по собственному числу',
'top-categories-link-total': 'по общему числу',
'top-categories-link-preliminary': 'предварительные результаты',
'top-categories-status-count': 'Обработано категорий: $1 / $2',
'top-categories-status-error': 'Ошибка при запросе, данные могут быть неполными! Ошибок: $1',
'top-categories-header-self': 'Топ $1 самых включаемых категорий',
'top-categories-header-total': 'Топ $1 самых включаемых категорий по общему числу',
'top-categories-action-self': 'Отобразить по собственному числу',
'top-categories-action-total': 'Отобразить по общему числу',
'top-categories-members': '($1)',
'top-categories-subcats': '$1 кат.',
'top-categories-pages': '$1 с.'
},
_api,
_nodes = {},
_mode = 'self',
_statusCount,
_errorsCount,
_category,
_categories = {},
_categoriesList = [];
function mergeArrays( a, b ) {
var c;
if ( 'Set' in window ) {
c = Array.from(
new Set( [ ...a, ...b ] )
);
} else {
c = a.slice();
b.forEach( function( item ) {
if( !c.includes( item ) ) {
c.push( item );
}
} );
}
return c;
}
function Category( title, info ) {
this.title = title;
this.info = $.extend( {
pages: 0,
subcats: 0
}, info );
this.pages = [];
this.pagesList = [];
this.subcats = [];
this.subcatsList = [];
this.mode = 'subcat';
this.hasInfo = !!info;
this.hasPagesData = false;
this.hasSubcatsData = false;
this.isRequesting = false;
if ( _categories[ this.title ] ) {
return _categories[ this.title ];
}
_categories[ this.title ] = this;
_categoriesList.push( this );
}
Category.prototype.request = function( mode ) {
if ( this.isRequesting || this.checkData( mode ) ) {
return true;
}
this.isRequesting = true;
this.mode = [ 'subcat', 'all' ].includes( mode ) ? mode : 'subcat';
return $.when( this.getInfo() )
.then( this.getMembers.bind( this ) )
.then( this.onSuccess.bind( this ) )
.fail( this.onError.bind( this ) );
};
Category.prototype.checkData = function( mode ) {
return ( mode === 'subcat' && this.hasSubcatsData )
|| ( mode === 'all' && this.hasSubcatsData && this.hasPagesData );
};
Category.prototype.onSuccess = function() {
this.isRequesting = false;
renderStatus();
};
Category.prototype.onError = function() {
this.isRequesting = false;
renderError();
};
Category.prototype.getInfo = function() {
if ( this.hasInfo ) {
return true;
}
var params = {
action: 'query',
prop: 'categoryinfo',
titles: this.title,
redirects: true,
format: 'json',
formatversion: 2
};
return _api
.get( params )
.then( this.onGetInfoDone.bind( this ) )
.fail( renderError );
};
Category.prototype.onGetInfoDone = function( data ) {
if ( !data ) {
return;
}
this.hasInfo = true;
this.info = $.extend( this.info, data.query.pages[0].categoryinfo );
if ( this.info.pages === 0 ) {
this.hasPagesData = true;
}
if ( this.info.subcats === 0 ) {
this.hasSubcatsData = true;
}
return true;
};
Category.prototype.getMembers = function() {
if ( this.checkData( this.mode ) ) {
return true;
}
var params = {
action: 'query',
prop: 'categoryinfo',
generator: 'categorymembers',
gcmtitle: this.title,
gcmtype: this.mode === 'subcat' ? 'subcat' : undefined,
gcmlimit: 500,
gcmcontinue: this.continue,
redirects: true,
format: 'json',
formatversion: 2
};
return _api
.get( params )
.then( this.onGetMembersDone.bind( this ) )
.fail( renderError );
};
Category.prototype.onGetMembersDone = function( data ) {
if ( !data ) {
return;
}
var that = this;
var promises = [];
var members = data.query ? data.query.pages : [];
members.forEach( function( item ) {
if ( item.ns === 14 ) {
var category = _categories[ item.title ];
if( !category ) {
category = new Category( item.title, item.categoryinfo );
}
promises.push( category.request( that.mode ) );
if( !that.subcats.includes( category ) ) {
that.subcats.push( category );
}
if( !that.subcatsList.includes( item.title ) ) {
that.subcatsList.push( item.title );
}
} else {
if( !that.pagesList.includes( item.title ) ) {
that.pagesList.push( item.title );
}
}
} );
this.continue = data.continue ? data.continue.gcmcontinue : undefined;
if ( this.continue ) {
if ( promises.length > 0 ) {
return $.when
.apply( $, promises )
.always( this.getMembers.bind( this ) );
}
return this.getMembers();
} else {
this.hasSubcatsData = true;
if ( this.mode === 'all' ) {
this.hasPagesData = true;
}
}
if ( promises.length > 0 ) {
return $.when.apply( $, promises );
}
return true;
};
function init() {
if ( _config.nameSpace !== 14 || _config.action !== 'view' ) {
return;
}
// Config
_api = new mw.Api();
// Set interface strings
mw.messages.set( _strings );
// Render structure
_nodes.$container = $( '<div>' )
.addClass( 'hlist' )
.css( 'margin-top', '0.5em' )
.appendTo( _config.linkContainer );
_nodes.$ul = $( '<dl>' )
.appendTo( _nodes.$container );
_nodes.$li = $( '<li>' )
.text( mw.msg( 'top-categories-label' ) )
.appendTo( _nodes.$ul );
_nodes.$ulLinks = $( '<ul>' )
.appendTo( _nodes.$li );
_nodes.$liSelf = $( '<li>' )
.appendTo( _nodes.$ulLinks );
_nodes.$linkSelf = $( '<a>' )
.text( mw.msg( 'top-categories-link-self' ) )
.attr( 'role', 'button' )
.attr( 'tabindex', '0' )
.on( 'click', function() {
_mode = 'self';
request();
} )
.appendTo( _nodes.$liSelf );
_nodes.$liTotal = $( '<li>' )
.appendTo( _nodes.$ulLinks );
_nodes.$linkTotal = $( '<a>' )
.text( mw.msg( 'top-categories-link-total' ) )
.attr( 'role', 'button' )
.attr( 'tabindex', '0' )
.on( 'click', function() {
_mode = 'total';
request();
} )
.appendTo( _nodes.$liTotal );
_nodes.$content = $( '<div>' )
.css( 'clear', 'both' )
.appendTo( _config.infoContainer );
}
function request() {
_statusCount = -1;
_errorsCount = 0;
if ( _nodes.$statusError ) {
_nodes.$statusError.remove();
_nodes.$statusError = null;
}
if ( !_nodes.$status ) {
_nodes.$status = $( '<li>' )
.css( 'font-style', 'italic' )
.appendTo( _nodes.$ul );
}
renderStatus();
if( !_category ){
_category = new Category( _config.pageTitle );
}
var requestMode = _mode === 'total' ? 'all' : 'subcat';
$.when( _category.request( requestMode ) )
.always( renderInfo )
.fail( renderError );
}
function renderInfo() {
if ( _mode === 'total' ) {
renderInfoTotal();
} else {
renderInfoSelf();
}
}
function renderInfoSelf() {
var outputCount = Math.min( _categoriesList.length, _config.outputCount );
var $title = $( '<h2>' )
.text( mw.msg( 'top-categories-header-self', _config.outputCount ) );
var $columns = $ ( '<div>' )
.addClass( 'columns' )
.css( 'column-count', '3' );
var $list = $( '<ol>' )
.appendTo( $columns );
_categoriesList.sort( function( a, b ) {
return b.info.pages - a.info.pages;
} );
for( var i = 0; i < outputCount; i++ ) {
$list.append(
renderLineSelf( _categoriesList[i] )
);
}
_nodes.$content
.empty()
.append( $title )
.append( $columns );
}
function renderLineSelf( item ) {
var label = item.title.split( ':' ).pop();
var href = _config.articlePath.replace( '$1', item.title );
var pagesMessage = mw.msg( 'top-categories-pages', item.info.pages );
var subcatsMessage = item.info.subcats > 0 ? mw.msg( 'top-categories-subcats', item.info.subcats ) : null;
var message = pagesMessage + ( subcatsMessage ? ', ' + subcatsMessage : '' );
var $li = $( '<li>' );
var $link = $( '<a>' )
.attr( 'href', href )
.text( label )
.appendTo( $li );
var $sep = $( document.createTextNode(' ') )
.appendTo( $li );
var $count = $( '<span>' )
.text( mw.msg( 'top-categories-members', message ) )
.appendTo( $li );
return $li;
}
function renderInfoTotal() {
var outputCount = Math.min( _categoriesList.length, _config.outputCount );
var $title = $( '<h2>' )
.text( mw.msg( 'top-categories-header-total', _config.outputCount ) );
var $titleBeta = $( '<sup title="Бета">(β)</sup>' )
.appendTo( $title );
var $columns = $ ( '<div>' )
.addClass( 'columns' )
.css( 'column-count', '3' );
var $list = $( '<ol>' )
.appendTo( $columns );
calculateTotalMemebrs( [ _category ] );
_categoriesList.sort( function( a, b ) {
return b.info.pagesTotal - a.info.pagesTotal;
} );
for( var i = 0; i < outputCount; i++ ) {
$list.append(
renderLineTotal( _categoriesList[i] )
);
}
_nodes.$content
.empty()
.append( $title )
.append( $columns );
}
function renderLineTotal( item ) {
var label = item.title.split( ':' ).pop();
var href = _config.articlePath.replace( '$1', item.title );
var pagesMessage = mw.msg( 'top-categories-pages', item.info.pagesTotal );
var subcatsMessage = item.info.subcatsTotal > 0 ? mw.msg( 'top-categories-subcats', item.info.subcatsTotal ) : null;
var message = pagesMessage + ( subcatsMessage ? ', ' + subcatsMessage : '' );
var $li = $( '<li>' );
var $link = $( '<a>' )
.attr( 'href', href )
.text( label )
.appendTo( $li );
var $sep = $( document.createTextNode(' ') )
.appendTo( $li );
var $count = $( '<span>' )
.text( mw.msg( 'top-categories-members', message ) )
.appendTo( $li );
return $li;
}
function calculateTotalMemebrs( list ) {
var total = {
'pagesList' : [],
'subcatsList' : []
};
list.forEach( function( item ) {
if ( !item.pagesTotalList ) {
item.pagesTotalList = [];
if ( item.pagesList.length > 0 ) {
item.pagesTotalList = item.pagesList.slice();
}
if ( !item.subcatsTotalList ) {
item.subcatsTotalList = [];
if ( item.subcatsList.length > 0 ) {
item.subcatsTotalList = item.subcatsList.slice();
var itemTotal = calculateTotalMemebrs( item.subcats );
item.pagesTotalList = mergeArrays( item.pagesTotalList, itemTotal.pagesList );
item.subcatsTotalList = mergeArrays( item.subcatsTotalList, itemTotal.subcatsList );
}
}
item.info.pagesTotal = item.pagesTotalList.length;
item.info.subcatsTotal = item.subcatsTotalList.length;
}
total.pagesList = mergeArrays( total.pagesList, item.pagesTotalList );
total.subcatsList = mergeArrays( total.subcatsList, item.subcatsTotalList );
} );
return total;
}
function renderStatus() {
_statusCount++;
_nodes.$status
.empty()
.text( mw.msg( 'top-categories-status-count', _statusCount, _categoriesList.length ) );
return _nodes.$status;
}
function renderError() {
_errorsCount++;
if ( !_nodes.$statusError ) {
_nodes.$statusError = $( '<li>' )
.text( mw.msg( 'top-categories-status-error', _errorsCount ) )
.addClass( 'error' )
.css( 'font-size', 'inherit' )
.appendTo( _nodes.$ul );
}
return _nodes.$statusError;
}
mw.hook( 'wikipage.content' ).add( init );
} )();