After saving, you may need to bypass your browser's cache to see the changes. For further information, see Wikipedia:Bypass your cache.

  • In most Windows and Linux browsers: Hold down Ctrl and press F5.
  • In Safari: Hold down ⇧ Shift and click the Reload button.
  • In Chrome and Firefox for Mac: Hold down both ⌘ Cmd+⇧ Shift and press R.
/**
 * Adds links for compare popups
 * 
 * @author Quarenon
 * @author Ryan PM
 * @author Joeytje50
 * @author Cqm
 * @author JaydenKieran
 * 
 * @license GPLv3 <https://www.gnu.org/licenses/gpl-3.0.html>
 *
 * @todo try to find a standard img url domain to use
 * @todo re-center (vertical & horizontally) with new items added, or find a way to do it with pure CSS
 *	 might require overhaul to #overlay structure/styles
 */

'use strict';

var modalOpenedPrev = false;

var conf = mw.config.get( [
		'stylepath',
		'wgTitle'
	] ),

	self = {
		/**
		 * Inital loading method
		 */
		init: function () {
			self.buildModal();
			
			var $compare = $( '.cioCompareLink' ),
				$ibox = $( '.infobox-bonuses' );

			$compare.each( function () {
				var $this = $( this ),
					props = ( $this.attr( 'title' ) || '' ).split( '|' ),
					text = props[0] !== '' ? props[0] : 'Compare items',
					items = props.length >= 2 ? props.slice( 1 ) : [conf.wgTitle],
					$a = $( '<a>' )
						.attr( {
							href: '#',
							title: 'Compare this item with other items',
							'data-items': items.join( '|' )
						} )
						.text( text )
						.on( 'click', self.open );

					$this
						.empty()
						.append( $a )
						.parent()
							.show();
			} );

			$ibox.each( function () {
				var $this = $( this )
				// insert new row with compare link
				var button = new OO.ui.ButtonWidget( {
					label: 'Compare',
					title: 'Compare this item with other items',
					flags: 'primary'
				} );

				$this.after( button.$element
						.attr( {
							'data-items': conf.wgTitle
						})
						.on( 'click', self.open )
					);
			} );

		},

		/**
		 * Images
		 *
		 * These are functions to avoid us having to use .clone()
		 * and to avoid potential memory leaks
		 */
		img: {
			/**
			 * Delete image
			 *
			 * @return {jquery object}
			 */
			del: function () {
				return $( '<img>' )
					.attr( {
						src: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23d33'%3E%3Cpath d='m4.3 2.9 12.8 12.8-1.4 1.4L2.9 4.3z'/%3E%3Cpath d='M17.1 4.3 4.3 17.1l-1.4-1.4L15.7 2.9z'/%3E%3C/g%3E%3C/svg%3E%0A",
						width: 16,
						height: 16,
						alt: 'Delete'
					} );
			},

			/**
			 * Loading image
			 *
			 * @return {jquery object}
			 */
			loading: function () {
				return $( '<img>' )
					.attr( {
						// .gif can't be converted to data: URI
						src: 'https://oldschool.runescape.wiki/images/2/23/Progress-wheel.gif?0a2fe',
						width: 16,
						height: 16,
						alt: '...'
					} );
			},

			/**
			 * Error image
			 *
			 * @return {jquery object}
			 */
			error: function () {
				return $( '<img>' )
					.attr( {
						src: "data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23d33'%3E%3Cpath d='M13.728 1H6.272L1 6.272v7.456L6.272 19h7.456L19 13.728V6.272zM11 15H9v-2h2zm0-4H9V5h2z'/%3E%3C/g%3E%3C/svg%3E%0A",
						width: 16,
						height: 16,
						alt: 'Error'
					} );
			}
		},

		/**
		 * Modal open method
		 *
		 * Callback to on click event
		 *
		 * @param e {jquery.event}
		 */
		open: function ( e ) {
			e.preventDefault();
			window.OOUIWindowManager.openWindow( 'compare' );
			
			if (!modalOpenedPrev) { // avoid init-ing
				modalOpenedPrev = true;
				var items = $( this ).attr( 'data-items' ).split( '|' );
				items.forEach( self.submit );
			}
		},

		/**
		 * Builds the compare modal
		 *
		 * @return {jquery object}
		 */
		buildModal: function () {
			var init = function (modal) {
			  modal.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );

				var button1 = new OO.ui.ButtonWidget( {
				  flags: [ 'destructive' ],
				  label: 'Cancel'
				} );
				var b1click = ('click', function(modal) {
					window.OOUIWindowManager.closeWindow(modal);
				});
				button1.on('click', b1click, [modal]);
				
				var button2 = new OO.ui.ButtonWidget( {
					label: 'Submit',
					flags: [ 'progressive' ],
				});
				button2.on('click', function() {
					self.submit();  
				});
				
				var input1 = new OO.ui.TextInputWidget({ id: 'cioItem' });
				input1.on('enter', function(){
					self.submit();
				});
				
				// Create OOUI JS fieldset
				var fieldset = new OO.ui.FieldsetLayout( { 
				  label: 'Comparing ' + conf.wgTitle,
				  id: 'cioCompare'
				} );
				
				fieldset.addItems( [ 
				  new OO.ui.ActionFieldLayout(
					  input1,
					  button2,
					  { label: 'Compare with', align: 'inline', notices: [new OO.ui.HtmlSnippet('<div id="cioStatus"></div>')] }
				  )
				] );

			  modal.content.$element.append($('<div>').append(fieldset.$element).append(
					$( '<table>' )
						.addClass( 'wikitable' )
						.attr( 'id', 'cioItems' )
						.append(
							$( '<thead>' )
								.append(
									$( '<tr>' )
										.append(
											$( '<th>' )
												.attr( 'rowspan', '2' )
												.text( 'Name' ),
											$( '<th>' )
												.attr( 'colspan', '5' )
												.text( 'Attack bonuses' ),
											$( '<th>' )
												.attr( 'colspan', '5' )
												.text( 'Defence bonuses' ),
											$( '<th>' )
												.attr( 'colspan', '4' )
												.text( 'Other bonuses' ),
											$( '<th>' )
												.attr( 'width', '30' )
												.text( 'Speed' ),
											$( '<th>' )
												.attr( 'width', '30' )
												.text( 'Weight' ),
											$( '<th>' )
												.attr( 'width', '30' )
												.text( 'GE' )
										),
									$( '<tr>' )
										.attr( 'height', '35' )
										.append(
											$( '<th>' )
												.attr( {
													class: 'cioIcon-stab',
													title: 'Stab bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-slash',
													title: 'Slash bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-crush',
													title: 'Crush bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-magic',
													title: 'Magic bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-ranged',
													title: 'Ranged bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-stab',
													title: 'Stab bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-slash',
													title: 'Slash bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-crush',
													title: 'Crush bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-magic',
													title: 'Magic bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-ranged',
													title: 'Ranged bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-strength',
													title: 'Strength bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-rangedstrength',
													title: 'Ranged Strength bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-magicdamage',
													title: 'Magic Damage bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-prayer',
													title: 'Prayer bonus',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-speed',
													title: 'Speed',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-weight',
													title: 'Weight (kg)',
													width: '35'
												} ),
											$( '<th>' )
												.attr( {
													class: 'cioIcon-price',
													title: 'Grand Exchange Price',
													width: '35'
												} )
										)
								),
							$( '<tbody>' )
								.append(
									$( '<tr>' )
										.attr( 'id', 'cioTotals' )
										// .addClass('table-bg-green')
								)
						),
					button1.$element
			  ));
			  modal.$body.append( modal.content.$element );
			};
			rs.createOOUIWindow('compare', 'Compare with other items', {size: 'larger', classes: ['rs-compare-modal', 'oo-ui-compare-width']}, init);
		},

		/**
		 * Initial callback for adding new items to the UI
		 *
		 * @param elem {string} (optional)
		 */
		submit: function ( elem ) {
			var item = elem || $( '#cioItem > input' ).val();

			$( '#cioStatus' )
				.empty()
				.attr( 'class', 'cioLoading' )
				.append(
					self.img.loading(),
					' Loading...'
				);

			// make sure first letter of item is uppercase
			// otherwise price data won't be found
			item = item.charAt( 0 ).toUpperCase() + item.slice( 1 );
			
			var mwApiResult, excg, main, excgData;

			( new mw.Api() )
				.get( {
					action: 'query',
					prop: 'revisions',
					titles: item + '|Module:Exchange/' + item,
					rvprop: 'content',
					redirects: ''
				} )
				.then( function (data) {
					mwApiResult = data;
					
					for ( var x in mwApiResult.query.pages ) {
						if ( mwApiResult.query.pages.hasOwnProperty( x ) ) {
							if ( x < 0 ) {
								// the page does not exist
								mw.log( mwApiResult.query.pages[x] );
								continue;
							} else if ( mwApiResult.query.pages[x].ns === 828 ) {
								excg = mwApiResult.query.pages[x];
							} else if ( mwApiResult.query.pages[x].ns === 0 ) {
								main = mwApiResult.query.pages[x];
							}
						}
					}
					
					if ( excg ) {
						excgData = rs.parseExchangeModule( excg.revisions[0]['*'] );
						excgData.itemId = excgData.itemId || excgData.itemid; // make this more robust?

						$.getJSON("https://api.weirdgloop.org/exchange/history/osrs/latest?id=" + excgData.itemId)
							.done( function (res) {
								self.done(main, res[excgData.itemId]);
							} )
							.fail( self.fail );
					} else {
						self.done(main, {});
					}
				} )
				.fail( self.fail );

			return false;
		},

		/**
		 * Success callback for `jQuery.ajax` promise
		 */
		done: function ( main, apiRes ) {
			var bonuses = [
					'astab',
					'aslash',
					'acrush',
					'amagic',
					'arange',
					'dstab',
					'dslash',
					'dcrush',
					'dmagic',
					'drange',
					'str',
					'rstr',
					'mdmg',
					'prayer',
					'speed'
				],
				main,
				x,
				title,
				content,
				bonusData,
				itemData,
				$tr;

			mw.log( main, apiRes );

			if ( !main ) {
				self.showError( 'Could not find that item.' );
				return;
			}

			title = main.title;
			content = main.revisions[0]['*'];
			bonusData = rs.parseTemplate( 'infobox bonuses', content );
			itemData = rs.parseTemplate( 'infobox item', content );

			if ( $.isEmptyObject( bonusData ) ) {
				self.showError( 'No bonus data found for the item.' );
				return;
			}

			$tr = $( '<tr>' )
				.append(
					$( '<th>' )
						.append(
							$( '<a>' )
								.attr( {
									href: '#',
									title: 'Remove this row'
								} )
								.on( 'click', function () {
									$( this ).closest( 'tr' ).fadeOut( 'slow', function () {
										$( this ).remove();
										self.calcTotals();
										window.OOUIWindowManager.getCurrentWindow().updateSize();
									} );

									return false;
								} )
								.append( self.img.del() ),
							'&nbsp;',
							$( '<a>' )
								.attr( {
									href: mw.util.getUrl( title ),
									title: title
								} )
								.text( title )
						)
				);

			bonuses.forEach( function ( el ) {
				// Use default version if defined, otherwise check if bonus has a version1
				var defaultVersion = $.isEmptyObject( itemData ) || (itemData.defver === undefined) ? '1' : itemData.defver;
				var versionSpecificBonus = bonusData[el + defaultVersion]; 
				$tr.append( self.format( versionSpecificBonus === undefined ? bonusData[el] : versionSpecificBonus ) );
			} );

			$tr.append( self.format( !$.isEmptyObject( itemData ) ? itemData.weight : null ) );
			$tr.append( self.format( !$.isEmptyObject( apiRes ) ? rs.addCommas( apiRes.price ) : null ) );

			$( '#cioTotals' ).before( $tr );

			self.calcTotals();
			$( '#cioStatus' ).empty();
			$( '#cioItem > input' ).val( '' );
			
			window.OOUIWindowManager.getCurrentWindow().updateSize();
		},

		/**
		 * Error callback for `jQuery.ajax` promise
		 */
		fail: function ( _, error ) {
			self.showError( 'Error: ' + error );
		},

		/**
		 * Outputs error to the UI
		 *
		 * @param str {string} Error to display
		 */
		showError: function ( str ) {
			$( '#cioStatus' )
				.empty()
				.attr( 'class', 'cioError' )
				.append(
					self.img.error(),
					' ' + str
				);
		},

		/**
		 * Formats each attribute's value and inserts it into a td cell
		 *
		 * @param str {string} Attribute value to format
		 *
		 * @return {jquery object} td cell to insert into the associated item's row
		 */
		format: function ( str ) {
			var $td = $( '<td>' ),
				first;

			// set `null` or `undefined` to an empty string
			/*jshint eqnull:true */
			if ( str == null ) {
			/* jshint eqnull:false */
				str = '';
			}

			// remove comments
			str = str.replace( /no|<!--.*?-->/gi, '' ).trim();

			// cache first character of `str`
			first = str.substring( 0, 1 );

			if ( !str ) {
				$td
					.addClass( 'cioEmpty' )
					.text( '--' );
			} else if ( /\d/.test( first ) ) {
				$td
					.addClass( 'cioPos' )
					.text( '+' + str );
			} else if ( first === '-' ) {
				$td
					.addClass( 'cioNeg' )
					.text( str );
			} else {
				$td
					.text( str );
			}

			return $td;
		},
		
		formatTotals: function (str, index) {
			var $td = $( '<td>' ),
				first;

			// set `null` or `undefined` to an empty string
			/*jshint eqnull:true */
			if ( str == null || str === "null" ) {
			/* jshint eqnull:false */
				str = '';
			}

			// remove comments
			str = str.replace( /no|<!--.*?-->/gi, '' ).trim();

			// cache first character of `str`
			first = str.substring( 0, 1 );
			
			// lower is better for speed and weight - reverse colors
			var lowerBetter = [14, 15].includes(index);

			if ( !str ) {
				$td
					.addClass( 'cioEmpty' )
					.text( '--' );
			} else if (parseFloat(str, 10) === 0) {
				$td
					.addClass( 'table-bg-yellow ')
					.text(str);
			} else if ( /\d/.test( first ) ) {
				$td
					.addClass( lowerBetter ? 'table-bg-red' : 'table-bg-green' )
					.text( '+' + str );
			} else if ( first === '-' ) {
				$td
					.addClass( lowerBetter ? 'table-bg-green' : 'table-bg-red' )
					.text( str );
			} else {
				$td.text( str );
			}
			
			// context dependent whether higher or lower price is better - just don't color it
			if (index === 16) {
				$td.removeClass("table-bg-green table-bg-yellow table-bg-red");
			}

			return $td;
		},

		/**
		 * Calculate bonus totals
		 */
		calcTotals: function () {
				// 19 0's, one for each attribute
			var totals = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
				$totals = $( '#cioTotals' );
				
			// don't show totals row when not comparing 2 or more items
			if ($( '#cioItems tbody tr:not( #cioTotals )' ).length < 2) {
				$totals.empty();
				return;
			}
			
			$( '#cioItems tbody tr:not( #cioTotals ):first td' ).each(function(i) {
				var num = parseFloat($(this).text().replace(/,/g, ""));
				totals[i] = isNaN(num) ? null : num;
			});

			$( '#cioItems tbody tr:not( #cioTotals ):not(:first)' ).each( function () {
				$( this ).children( 'td' ).each( function ( i ) {
					if (totals[i] !== null) {
						var num = parseFloat($(this).text().replace(/,/g, ""));
						if (isNaN(num)) {
							totals[i] = null;
						}
						else {
							totals[i] -= num;
						}
					}
				} );
			} );

			$totals
				.empty()
				.append(
					$( '<th>' )
						.text( 'Diff' )
				);
				
			totals.forEach( function ( elem, index ) {
				$totals.append(
					self.formatTotals(
						// don't total speed
						// 14th index/column respectively
						// [14].indexOf( index ) > -1 ? null : rs.addCommas( elem )
						rs.addCommas(elem), index
					)
				);
			} );
		},
		checkSign: function (value) {
			return value === 0 ? true : (value > 0 ? true : false);
		}
	};

$(function(){mw.loader.using( ['mediawiki.util', 'mediawiki.api', 'ext.gadget.rsw-util'], self.init )});