<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
	<id>https://develika-wiki.marus.team/index.php?action=history&amp;feed=atom&amp;title=MediaWiki%3AGadget-develika-compare.js</id>
	<title>MediaWiki:Gadget-develika-compare.js - История изменений</title>
	<link rel="self" type="application/atom+xml" href="https://develika-wiki.marus.team/index.php?action=history&amp;feed=atom&amp;title=MediaWiki%3AGadget-develika-compare.js"/>
	<link rel="alternate" type="text/html" href="https://develika-wiki.marus.team/index.php?title=MediaWiki:Gadget-develika-compare.js&amp;action=history"/>
	<updated>2026-07-01T18:22:47Z</updated>
	<subtitle>История изменений этой страницы в вики</subtitle>
	<generator>MediaWiki 1.43.9</generator>
	<entry>
		<id>https://develika-wiki.marus.team/index.php?title=MediaWiki:Gadget-develika-compare.js&amp;diff=66&amp;oldid=prev</id>
		<title>Admin: Библиотека компонентов</title>
		<link rel="alternate" type="text/html" href="https://develika-wiki.marus.team/index.php?title=MediaWiki:Gadget-develika-compare.js&amp;diff=66&amp;oldid=prev"/>
		<updated>2026-07-01T10:31:44Z</updated>

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