MediaWiki:Gadget-develika-compare.js
Страница интерфейса MediaWiki
Дополнительные действия
Замечание: Возможно, после публикации вам придётся очистить кэш своего браузера, чтобы увидеть изменения.
- Firefox / Safari: Удерживая клавишу Shift, нажмите на панели инструментов Обновить либо нажмите Ctrl+F5 или Ctrl+R (⌘+R на Mac)
- Google Chrome: Нажмите Ctrl+Shift+R (⌘+Shift+R на Mac)
- Edge: Удерживая Ctrl, нажмите Обновить либо нажмите Ctrl+F5
- Opera: Нажмите Ctrl+F5.
/* Корзина сравнения устройств «Девелика».
- кнопка «В сравнение» на страницах категории «Обзоры устройств»;
- плавающий счётчик-кнопка «Сравнить» (localStorage, между страницами);
- страница «Сравнение»: карточки устройств (фото + рейтинг) в шапке и таблица
характеристик бок-о-бок через Cargo API + тумблер «только различающиеся».
Подключается как гаджет (см. MediaWiki:Gadgets-definition). */
( function () {
'use strict';
var KEY = 'dvlkCompareList';
var COMPARE_PAGE = 'Сравнение';
var DEVICE_CATEGORY = 'Обзоры устройств';
function getList() { try { return JSON.parse( localStorage.getItem( KEY ) || '[]' ); } catch ( e ) { return []; } }
function setList( l ) { localStorage.setItem( KEY, JSON.stringify( l ) ); updateBadge(); }
function has( p ) { return getList().indexOf( p ) > -1; }
function toggle( p ) { var l = getList(), i = l.indexOf( p ); if ( i > -1 ) { l.splice( i, 1 ); } else { l.push( p ); } setList( l ); }
function stars( r ) {
var n = Number( r ) || 0, full = Math.round( n ), s = '';
for ( var i = 0; i < 5; i++ ) { s += i < full ? '★' : '☆'; }
return '<span class="dvlk-cmp-card__stars">' + s + '</span> ' + n.toFixed( 1 );
}
function fileUrl( name ) { return mw.util.getUrl( 'Special:Redirect/file/' + name ); }
function updateBadge() {
var n = getList().length;
var b = document.getElementById( 'dvlk-cmp-badge' );
if ( !b ) {
b = document.createElement( 'a' );
b.id = 'dvlk-cmp-badge';
b.className = 'dvlk-cmp-badge';
document.body.appendChild( b );
}
b.href = mw.util.getUrl( COMPARE_PAGE );
b.innerHTML = '⚖ Сравнить <span class="dvlk-cmp-badge__n"></span>';
b.querySelector( '.dvlk-cmp-badge__n' ).textContent = n;
b.style.display = n > 0 ? '' : 'none';
}
function initDeviceButton() {
if ( mw.config.get( 'wgAction' ) !== 'view' ) { return; }
var cats = mw.config.get( 'wgCategories' ) || [];
if ( cats.indexOf( DEVICE_CATEGORY ) === -1 ) { return; }
var page = mw.config.get( 'wgPageName' ).replace( /_/g, ' ' );
// Кнопку кладём в начало содержимого статьи, а не вплотную к заголовку.
var content = document.querySelector( '.mw-parser-output' );
if ( !content || document.getElementById( 'dvlk-cmp-add' ) ) { return; }
var btn = document.createElement( 'button' );
btn.id = 'dvlk-cmp-add';
btn.type = 'button';
btn.className = 'dvlk-cmp-add';
function render() {
btn.textContent = has( page ) ? '✓ В сравнении' : '➕ В сравнение';
btn.classList.toggle( 'is-on', has( page ) );
}
render();
btn.addEventListener( 'click', function () { toggle( page ); render(); } );
content.insertBefore( btn, content.firstChild );
}
function initComparePage() {
var container = document.getElementById( 'dvlk-compare' );
if ( !container ) { return; }
var devices = getList();
if ( !devices.length ) {
container.innerHTML = '<p class="dvlk-cmp-empty">Список сравнения пуст. Откройте страницу устройства (категория «' + DEVICE_CATEGORY + '») и нажмите «➕ В сравнение».</p>';
return;
}
container.textContent = 'Загрузка…';
var where = 'device IN (' + devices.map( function ( d ) { return '"' + d.replace( /"/g, '' ) + '"'; } ).join( ',' ) + ')';
var api = new mw.Api();
var meta = {};
// Метаданные устройств (фото/рейтинг) — необязательно; таблица строится в любом случае
api.get( { action: 'cargoquery', format: 'json', tables: 'Devices', fields: 'device,name,image,rating,summary', where: where, limit: 100 } )
.done( function ( md ) { ( md.cargoquery || [] ).forEach( function ( r ) { meta[ r.title.device ] = r.title; } ); } )
.always( function () {
api.get( { action: 'cargoquery', format: 'json', tables: 'DeviceSpecs', fields: 'device,section,param,value,sort', where: where, order_by: 'sort', limit: 500 } )
.done( function ( data ) { renderTable( container, devices, meta, ( data.cargoquery || [] ).map( function ( r ) { return r.title; } ) ); } )
.fail( function () { container.textContent = 'Ошибка загрузки данных сравнения.'; } );
} );
}
function renderTable( container, devices, meta, rows ) {
var sections = [], map = {};
rows.forEach( function ( r ) {
var s = r.section || 'Прочее', p = r.param || '';
if ( !map[ s ] ) { map[ s ] = { params: [], pm: {} }; sections.push( s ); }
if ( !map[ s ].pm[ p ] ) { map[ s ].pm[ p ] = { sort: Number( r.sort ) || 100, vals: {} }; map[ s ].params.push( p ); }
map[ s ].pm[ p ].vals[ r.device ] = r.value;
} );
var onlyDiff = false;
var table = document.createElement( 'table' );
table.className = 'dvlk-cmp-table';
function deviceCard( d ) {
var th = document.createElement( 'th' );
th.className = 'dvlk-cmp-card';
var m = meta[ d ] || {};
if ( m.image ) {
var img = document.createElement( 'img' );
img.className = 'dvlk-cmp-card__img'; img.src = fileUrl( m.image ); img.alt = '';
th.appendChild( img );
}
var a = document.createElement( 'a' );
a.className = 'dvlk-cmp-card__name'; a.href = mw.util.getUrl( d ); a.textContent = m.name || d;
th.appendChild( a );
if ( m.rating ) {
var rt = document.createElement( 'div' );
rt.className = 'dvlk-cmp-card__rating'; rt.innerHTML = stars( m.rating );
th.appendChild( rt );
}
var rm = document.createElement( 'button' );
rm.className = 'dvlk-cmp-rm'; rm.type = 'button'; rm.textContent = '✕'; rm.title = 'Убрать из сравнения';
rm.addEventListener( 'click', function () { toggle( d ); location.reload(); } );
th.appendChild( rm );
return th;
}
function build() {
table.innerHTML = '';
var head = document.createElement( 'tr' );
head.className = 'dvlk-cmp-head';
head.appendChild( document.createElement( 'th' ) );
devices.forEach( function ( d ) { head.appendChild( deviceCard( d ) ); } );
table.appendChild( head );
sections.forEach( function ( s ) {
var params = map[ s ].params.slice().sort( function ( a, b ) { return map[ s ].pm[ a ].sort - map[ s ].pm[ b ].sort; } );
var trs = [];
params.forEach( function ( p ) {
var vals = devices.map( function ( d ) { return map[ s ].pm[ p ].vals[ d ] !== undefined ? map[ s ].pm[ p ].vals[ d ] : '—'; } );
var same = vals.every( function ( v ) { return v === vals[ 0 ]; } );
if ( onlyDiff && same ) { return; }
var tr = document.createElement( 'tr' );
var k = document.createElement( 'td' ); k.className = 'dvlk-cmp-k'; k.textContent = p; tr.appendChild( k );
vals.forEach( function ( v ) { var td = document.createElement( 'td' ); td.innerHTML = v; tr.appendChild( td ); } );
trs.push( tr );
} );
if ( trs.length ) {
var sec = document.createElement( 'tr' ); sec.className = 'dvlk-cmp-sec';
var th = document.createElement( 'th' ); th.colSpan = devices.length + 1; th.textContent = s; sec.appendChild( th );
table.appendChild( sec );
trs.forEach( function ( t ) { table.appendChild( t ); } );
}
} );
}
var controls = document.createElement( 'div' );
controls.className = 'dvlk-cmp-controls';
var lbl = document.createElement( 'label' );
var cb = document.createElement( 'input' ); cb.type = 'checkbox';
cb.addEventListener( 'change', function () { onlyDiff = cb.checked; build(); } );
lbl.appendChild( cb ); lbl.appendChild( document.createTextNode( ' Только различающиеся' ) );
var clear = document.createElement( 'button' );
clear.type = 'button'; clear.className = 'dvlk-cmp-clear'; clear.textContent = 'Очистить список';
clear.addEventListener( 'click', function () { setList( [] ); location.reload(); } );
controls.appendChild( lbl ); controls.appendChild( clear );
container.innerHTML = '';
container.appendChild( controls );
container.appendChild( table );
build();
}
mw.loader.using( [ 'mediawiki.api', 'mediawiki.util' ] ).then( function () {
updateBadge();
initDeviceButton();
initComparePage();
} );
}() );