您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Shows elements' color below their ID in search results
// ==UserScript== // @name Pick-A-Brick Color Line // @name:en Pick-A-Brick Color Line // @description Shows elements' color below their ID in search results // @namespace Violentmonkey Scripts // @match https://www.lego.com/en-us/pick-and-build/pick-a-brick* // @grant none // @version 1.3 // @author The0x539 // @run-at document-start // @license AGPL-3.0 // ==/UserScript== /* jshint esversion: 11 */ let initialEnrichmentDone = false; const ELEMENT_SELECTOR = 'li[class^=ElementsList_leaf_]'; const elementColors = {}; // Modify a PaB search result to include the color name below the element/design ID line. function enrich(element) { const idElem = element.querySelector('p[data-test=pab-item-elementId]'); const [elemId, partId] = idElem.innerHTML.split(' ')[1].split('/'); const elemColor = elementColors[elemId]; if (!elemColor) { return; } idElem.insertAdjacentHTML('afterend', `<p class="elem-color ds-body-xs-regular">${elemColor}</p>`); } // Intercept HTTP requests to a LEGO GraphQL endpoint that tells us the colors of the element IDs currently on the page. // Store data from the response in the `elementColors` dictionary, which maps element IDs to color names. const actualFetch = window.fetch; window.fetch = async (...args) => { const response = await actualFetch(...args); if (args[0] !== 'https://www.lego.com/api/graphql/PickABrickQuery') { return response; } const body = await response.json(); try { for (const element of body.data.searchElements.results) { elementColors[element.id] = element.facets.color.name; } // The elements shown on initial page load are included in the initial HTML response, // so we can't add the color line until the GraphQL request is made. if (!initialEnrichmentDone) { document.querySelectorAll(ELEMENT_SELECTOR).forEach(enrich); initialEnrichmentDone = true; } } catch {} // Reconstruct the response because we already consumed the body of the original response object. const { status, statusText, headers } = response; const options = { status, statusText, headers }; return new Response(JSON.stringify(body), options); } const observeOptions = { childList: true, subtree: true }; // Searches performed after initial page load mutate the DOM after making the GraphQL request, // so we need to catch that mutation in order to add color based on data intercepted from the response. function onUpdateSearchResults(records, observer) { for (const record of records) { for (const node of record.addedNodes) { if (node.matches(ELEMENT_SELECTOR)) { enrich(node); } else { for (const leaf of node.querySelectorAll(ELEMENT_SELECTOR)) { enrich(leaf); } } } } } function onUpdatePage(records, observer) { for (const record of records) { for (const node of record.addedNodes) { const pabResultsWrapper = node.parentNode?.querySelector('#pab-results-wrapper'); if (!pabResultsWrapper) continue; observer.disconnect(); // we found the element, so stop looking for it new MutationObserver(onUpdateSearchResults).observe(pabResultsWrapper, observeOptions); return; } } } document.addEventListener('readystatechange', (event) => { // This script needs to run at document-start in order to monkey-patch window.fetch, // but that's too early for us to perform these two DOM operations. if (document.readyState === 'interactive') { document.head.insertAdjacentHTML('beforeend', ` <style> .elem-color { color: var(--ds-color-text-subdued); margin: 0; } </style> `); new MutationObserver(onUpdatePage).observe(document.body, observeOptions); } });