Skip to content

Instantly share code, notes, and snippets.

@andre-st
Last active November 2, 2021 19:08
Show Gist options
  • Save andre-st/ae556e9966738a5b3d7d2ff773196207 to your computer and use it in GitHub Desktop.
Save andre-st/ae556e9966738a5b3d7d2ff773196207 to your computer and use it in GitHub Desktop.
Adds price-filter UI controls to your own Amazon wishlist pages #injectjs
//////////////////////////////////////////////////////////////////////////////
//
// AMAZON.DE WISHLESS - WISHLIST PRICE FILTERING
//
// - adds UI elements to every Amazon wishlist control bar
// - optionally filter by price range
// - optionally filter by free shipping
// - keeps filter settings across wishlists
//
mixin( ["https://www.amazon.de/hz/wishlist/", "https://www.amazon.de/gp/registry/wishlist/"], () =>
{
const DEFAULT_MAXPRICE = 10.0;
const DEFAULT_MINPRICE = 0.0;
const STOR_PREFIX = 'wishless_';
const COLOR_ENABLED = '#dfffef';
const COLOR_DISABLED = '#ffeeee';
const LOCALE = opts.language.replace( '_', '-' ); // 'opts' from Amazon JS, "de_DE" -> "de-DE"
const LOCALE_DECIMAL_SEP = Intl.NumberFormat( LOCALE ).formatToParts( 1.1 ).find( part => part.type === 'decimal' ).value;
const priceSymbolElm = document.querySelector( '.icp-currency-symbol' ) ||
document.querySelector( '.a-price-symbol' );
const LOCALE_CURRENCY = priceSymbolElm.innerText;
const FREESHIP_TXT_FOR = {
'de-DE': [ 'kostenfrei', 'kostenlos' ],
'en-GB': [ 'Free' ],
'nl-NL': [ 'Gratis' ],
'pl-PL': [ 'Darmowa' ],
'tr-TR': [ 'Bedava' ],
'cs-CZ': [ 'Zdarma' ]};
const LOCALE_FREESHIP_TXT = FREESHIP_TXT_FOR[LOCALE] || ['Error'];
if( !FREESHIP_TXT_FOR[LOCALE] )
console.log( '[Wishless]: Please add "' + LOCALE + '" to FREESHIP_TXT_FOR' );
if( !document.querySelector( '#my-lists-tab.a-active' ))
return console.log( "[Wishless] Not your wishlist. Will not filter any prices." );
// Add filter-control so user can easily disable, enable or modify price-filtering:
const container = document.querySelector( '#control-bar > .a-span-last' );
if( !container )
return console.log( "[Wishless] Cannot find control-bar on your wishlist site" );
const priceWidget = document.createElement( 'SPAN' );
const cbPrice = document.createElement( 'INPUT' );
const inMinPrice = document.createElement( 'INPUT' );
const inMaxPrice = document.createElement( 'INPUT' );
const cbFreeShip = document.createElement( 'INPUT' );
const lbFreeShip = document.createElement( 'LABEL' );
cbFreeShip.setAttribute( 'type', 'checkbox' );
cbPrice .setAttribute( 'type', 'checkbox' );
inMinPrice.setAttribute( 'type', 'number' );
inMinPrice.setAttribute( 'min', 0.00 );
inMinPrice.setAttribute( 'step', 0.10 );
inMaxPrice.setAttribute( 'type', 'number' );
inMaxPrice.setAttribute( 'min', 0.00 );
inMaxPrice.setAttribute( 'step', 0.10 );
// Keep filter settings when visiting other wishlists:
// localStorage saves strings only, so boolean converted
cbFreeShip.checked = localStorage.getItem( STOR_PREFIX + 'beFreeShip' ) == 'true';
cbPrice .checked = localStorage.getItem( STOR_PREFIX + 'bePrice' ) == 'true';
inMinPrice.value = localStorage.getItem( STOR_PREFIX + 'minPrice' ) || DEFAULT_MINPRICE;
inMaxPrice.value = localStorage.getItem( STOR_PREFIX + 'maxPrice' ) || DEFAULT_MAXPRICE;
saveSettings = () => {
localStorage.setItem( STOR_PREFIX + 'beFreeShip', cbFreeShip.checked );
localStorage.setItem( STOR_PREFIX + 'bePrice', cbPrice .checked );
localStorage.setItem( STOR_PREFIX + 'minPrice', inMinPrice.value );
localStorage.setItem( STOR_PREFIX + 'maxPrice', inMaxPrice.value );
};
cbFreeShip.addEventListener( 'change', saveSettings );
cbPrice .addEventListener( 'change', saveSettings );
inMinPrice.addEventListener( 'input', saveSettings );
inMaxPrice.addEventListener( 'input', saveSettings );
lbFreeShip .appendChild( cbFreeShip );
lbFreeShip .appendChild( document.createTextNode( LOCALE_FREESHIP_TXT[0] ));
priceWidget.appendChild( cbPrice );
priceWidget.appendChild( inMinPrice );
priceWidget.appendChild( document.createTextNode( '\u2013' ));
priceWidget.appendChild( inMaxPrice );
priceWidget.appendChild( document.createTextNode( LOCALE_CURRENCY ));
cbFreeShip .style.cssText = 'vertical-align: unset; margin-right: 0.25em';
cbPrice .style.cssText = 'vertical-align: unset';
inMinPrice .style.cssText = 'width: 4em; margin: 0 0 0 0.35em';
inMaxPrice .style.cssText = 'width: 4em; margin: 0 0.35em 0 0';
priceWidget.style.cssText = 'vertical-align: middle; display: inline-block; font: unset; margin-right: 15px; padding: 5px;';
lbFreeShip .style.cssText = 'vertical-align: middle; display: inline-block; font: unset; margin-right: 15px; padding: 0 5px; line-height: 41px';
container.insertBefore( lbFreeShip, container.children[0] );
container.insertBefore( priceWidget, container.children[0] );
// Adjust the control bar layout:
const barCols = document.querySelectorAll( '#control-bar .a-column' );
if( barCols.length >= 2 )
{
barCols[0].classList.replace( 'a-span6', 'a-span2' );
barCols[1].classList.replace( 'a-span6', 'a-span10' );
}
setInterval( ()=>
{
priceWidget.style.backgroundColor = cbPrice .checked ? COLOR_ENABLED : COLOR_DISABLED;
lbFreeShip .style.backgroundColor = cbFreeShip.checked ? COLOR_ENABLED : COLOR_DISABLED;
const prods = document.querySelectorAll( 'li[data-price]' );
for( var i = 0; i < prods.length; i++ )
{
const usedPriceElm = cbFreeShip.checked ? null : prods[i].querySelector( '.itemUsedAndNewPrice' );
const freeShipElm = prods[i].querySelector( '.wl-item-delivery-badge' );
var price = parseFloat( prods[i].getAttribute( 'data-price' ) || -1 );
if( usedPriceElm )
{
const usedPriceStr = usedPriceElm.innerText;
const usedPriceMat = /.*?(?<amount>[0-9,.']+).*/.exec( usedPriceStr );
const amountParts = usedPriceMat.groups.amount.split( LOCALE_DECIMAL_SEP );
const amountMain = amountParts[0].replace( /[^0-9]/, '' ); // Remove group separator (thousand etc)
const amountFract = amountParts[1];
const usedPrice = parseFloat( amountMain + '.' + amountFract );
if( usedPrice < price )
price = usedPrice;
}
const isFreeShip = freeShipElm && LOCALE_FREESHIP_TXT.some( t => freeShipElm.innerText.toLowerCase().includes( t.toLowerCase() ));
const isInBudget = price >= inMinPrice.value && price <= inMaxPrice.value;
const canDisplay = ( !cbPrice .checked || isInBudget ) &&
( !cbFreeShip.checked || isFreeShip );
if( canDisplay )
{
prods[i].style.display = 'list-item';
if( usedPriceElm )
usedPriceElm.style.fontSize = cbPrice.checked ? '1.7em' : 'unset';
}
else
{
prods[i].style.display = 'none';
}
}
if( !cbPrice.checked && !cbFreeShip.checked ) return; // Don't load full wishlist
// Force loading next pages by triggering Amazon's scroll-handler
if( document.querySelector( '.wl-see-more-spinner' ))
{
// Amazon's scroll-handler seems to check the scroll-position.
// Scrolling forward and back immediately is faster than the renderer, so we won't notice it.
// That speed might be the reason why we still need to trigger the scroll-handler in between.
// It's ugly but works. Ideally we could call Amazon's page loader function directly,
// but it seems burried somewhere deep in minified code with lot of indirection:
// maybe sth near window.AmazonUIPageJS._namespace( "NavSharedAssets" ).trigger( "wl-load-more-items" );
//
const SCROLLBY = 1500;
const scrollEvent = document.createEvent( 'Event' );
scrollEvent.initEvent( "scroll", true, false );
window.scrollBy( 0, SCROLLBY );
window.dispatchEvent( scrollEvent );
window.scrollBy( 0, -SCROLLBY );
}
}, 1500 );
});
@andre-st
Copy link
Author

andre-st commented Feb 18, 2021

screenshot-2021 11 02-200532

Can be used with any Javascript injection extension such as https://github.com/andre-st/chrome-injectjs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment