Открыть меню
Переключить меню настроек
Открыть персональное меню
Вы не представились системе
Ваш IP-адрес будет виден всем, если вы внесёте какие-либо изменения.

MediaWiki:Gadget-develika-compare.js

Страница интерфейса MediaWiki
Версия от 13:31, 1 июля 2026; Admin (обсуждение | вклад) (Библиотека компонентов)
(разн.) ← Предыдущая версия | Текущая версия (разн.) | Следующая версия → (разн.)

Замечание: Возможно, после публикации вам придётся очистить кэш своего браузера, чтобы увидеть изменения.

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