// ==UserScript== // @name Remember BrickLink Settings // @namespace https://github.com/pedicino // @version 2.2 // @description Adds a settings wheel to save your BrickLink catalog preferences between sessions. Includes US-based defaults for shipping destination, seller location, and currency. // @author pedicino // @match https://www.bricklink.com/* // @run-at document-start // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; if (typeof GM_addStyle !== 'undefined') { GM_addStyle(` .blp-adv-search__form { width: calc(100% - 48px) !important; transition: none !important; max-width: 100% !important; } .blp-adv-search { width: calc(100% - 48px) !important; transition: none !important; box-sizing: border-box !important; } body .blp-header .blp-adv-search__form { width: calc(100% - 48px) !important; } #bl-preferences-placeholder { width: 40px !important; min-width: 40px !important; display: inline-flex !important; align-items: center !important; justify-content: center !important; visibility: hidden !important; } .blp-icon-nav { min-width: 130px !important; display: flex !important; align-items: center !important; justify-content: flex-end !important; } .blp-icon-nav__item-container { min-width: 40px !important; } `); } else { const injectCSS = () => { if (document.head) { const style = document.createElement('style'); style.id = 'bl-preferences-style'; style.textContent = ` .blp-adv-search__form { width: calc(100% - 48px) !important; transition: none !important; } `; document.head.appendChild(style); } else { setTimeout(injectCSS, 5); } }; injectCSS(); } const defaultPrefs = { ss: "US", loc: "US", ca: "1", iconly: 0 }; const PREF_KEY = "BL_preferences"; function savePrefs(prefsToSave) { try { const cleanPrefs = {}; for (const key in prefsToSave) { if (prefsToSave[key] !== undefined) { cleanPrefs[key] = prefsToSave[key]; } } localStorage.setItem(PREF_KEY, JSON.stringify(cleanPrefs)); } catch (e) { console.error("Error saving BL_preferences to localStorage:", e); } } function getPrefs() { let prefsFromStorage = {}; let storedSuccessfully = false; let wasStorageEmpty = true; try { const stored = localStorage.getItem(PREF_KEY); if (stored) { wasStorageEmpty = false; prefsFromStorage = JSON.parse(stored); storedSuccessfully = true; } } catch (e) { console.error(`Error reading/parsing ${PREF_KEY}:`, e); localStorage.removeItem(PREF_KEY); } let effectivePrefs = {}; for (const key in defaultPrefs) { if (prefsFromStorage.hasOwnProperty(key)) { effectivePrefs[key] = prefsFromStorage[key]; } else if (storedSuccessfully) { effectivePrefs[key] = undefined; } else { effectivePrefs[key] = defaultPrefs[key]; } } if (effectivePrefs.iconly === undefined) { effectivePrefs.iconly = defaultPrefs.iconly; } if (wasStorageEmpty || !storedSuccessfully) { savePrefs(effectivePrefs); } return effectivePrefs; } function buildOptionsObject(prefs) { const options = {}; for (const key in prefs) { if (prefs[key] !== undefined && defaultPrefs.hasOwnProperty(key)) { options[key] = prefs[key]; } } options.iconly = prefs.iconly !== undefined ? prefs.iconly : defaultPrefs.iconly; return options; } function buildHashString(options) { const orderedOptions = {}; const order = ['ss', 'loc', 'ca', 'iconly']; order.forEach(key => { if (options.hasOwnProperty(key)) { orderedOptions[key] = options[key]; } }); for(const key in options) { if (!orderedOptions.hasOwnProperty(key)) { orderedOptions[key] = options[key]; } } if (Object.keys(orderedOptions).length === 0) { orderedOptions.iconly = defaultPrefs.iconly; } return "#T=S&O=" + JSON.stringify(orderedOptions); } const isCatalogPage = /\/v2\/catalog\/catalogitem\.page/i.test(window.location.pathname); if (isCatalogPage) { const prefsToEnforce = getPrefs(); const desiredOptions = buildOptionsObject(prefsToEnforce); const desiredHash = buildHashString(desiredOptions); const currentHash = window.location.hash; const decodedHash = decodeURIComponent(currentHash); if (decodedHash !== desiredHash) { let needsRedirect = true; if (decodedHash.startsWith("#T=S&O=")) { try { const currentOptions = JSON.parse(decodedHash.substring(6)); const currentKeys = Object.keys(currentOptions).sort(); const desiredKeys = Object.keys(desiredOptions).sort(); if (currentKeys.length === desiredKeys.length && currentKeys.every((key, index) => key === desiredKeys[index])) { let valuesMatch = true; for (const key of currentKeys) { if (String(currentOptions[key]) !== String(desiredOptions[key])) { valuesMatch = false; break; } } if (valuesMatch) needsRedirect = false; } } catch (e) { } } if (needsRedirect) { const baseUrl = window.location.href.split("#")[0]; const newUrl = baseUrl + desiredHash; console.log(`%cEnforcing preferences via replace...`, "color: blue;"); sessionStorage.setItem("bl_refreshing", "true"); window.location.replace(newUrl); } } } function createPlaceholder() { if (document.getElementById('bl-preferences-placeholder') || document.querySelector('.blp-icon-nav__item-container--preferences')) { return; } const iconNav = document.querySelector(".blp-icon-nav"); if (!iconNav) return; const placeholder = document.createElement("div"); placeholder.id = "bl-preferences-placeholder"; placeholder.className = "blp-icon-nav__item-container"; placeholder.innerHTML = `
`; const mb = iconNav.querySelector(".blp-icon-nav__item-container--more"); if (mb) iconNav.insertBefore(placeholder, mb); else iconNav.appendChild(placeholder); } function createPreferencesUI() { const existingButton = document.querySelector('.blp-icon-nav__item-container--preferences'); const existingPanel = document.getElementById('bl-preferences-panel'); if (existingButton && existingPanel) { initializeCheckboxes(); return; } if (existingButton) existingButton.remove(); if (existingPanel) existingPanel.remove(); const gearSvg = ``; const navButtonContainer = document.createElement("div"); navButtonContainer.className = "blp-icon-nav__item-container blp-icon-nav__item-container--preferences"; navButtonContainer.style.width = "40px"; navButtonContainer.style.minWidth = "40px"; const navButton = document.createElement("button"); navButton.className = "blp-btn blp-icon-nav__item blp-icon-nav__item--preferences"; navButton.setAttribute("aria-haspopup", "dialog"); navButton.setAttribute("aria-expanded", "false"); navButton.setAttribute("data-state", "closed"); navButton.title = "Marketplace Preferences"; navButton.style.cssText = "background: transparent; transition: all 0.2s; width: 40px;"; navButton.addEventListener("mouseover", () => { const pathElement = iconDiv.querySelector('svg path'); pathElement.setAttribute('stroke', '#0055AA'); }); navButton.addEventListener("mouseout", () => { const pathElement = iconDiv.querySelector('svg path'); pathElement.setAttribute('stroke', '#000000'); }); const iconGroup = document.createElement("div"); iconGroup.className = "blp-icon-nav__item-icon-notification-group"; const iconNotification = document.createElement("span"); iconNotification.className = "blp-icon-nav__item-notification blp-icon-nav__item-notification--hidden"; const iconDiv = document.createElement("div"); iconDiv.className = "blp-icon blp-icon--large"; iconDiv.setAttribute("aria-hidden", "true"); iconDiv.style.cssText = "background: transparent; display: flex; align-items: center; justify-content: center; transform: scale(1.3);"; iconDiv.innerHTML = gearSvg; iconGroup.appendChild(iconNotification); iconGroup.appendChild(iconDiv); navButton.appendChild(iconGroup); navButtonContainer.appendChild(navButton); const placeholder = document.getElementById('bl-preferences-placeholder'); if (placeholder) { placeholder.parentNode.replaceChild(navButtonContainer, placeholder); } else { const iconNav = document.querySelector(".blp-icon-nav"); if (iconNav) { const mb = iconNav.querySelector(".blp-icon-nav__item-container--more"); if (mb) iconNav.insertBefore(navButtonContainer, mb); else iconNav.appendChild(navButtonContainer); } else { const hr = document.querySelector(".blp-header__content"); if (hr) { navButtonContainer.style.cssText = "display:inline-block;margin-right:10px;width:40px;min-width:40px;"; navButton.style.padding = "10px"; const fc = hr.firstChild; if(fc) hr.insertBefore(navButtonContainer, fc); else hr.appendChild(navButtonContainer); } else { console.error("Cannot find insert location"); return; } } } const panel = document.createElement("div"); panel.id = "bl-preferences-panel"; panel.style.cssText = "position: fixed; top: 60px; right: 10px; background: rgba(255,255,255,0.98); border: 1px solid #ccc; padding: 15px; z-index: 10001; font-size: 14px; font-family: Arial, sans-serif; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.2); display: none;"; panel.innerHTML = `