您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add brand exclusion filtering to ModularGrid module browser
// ==UserScript== // @name ModularGrid Manufacturer Exception Filter // @namespace http://tampermonkey.net/ // @version 1.0 // @description Add brand exclusion filtering to ModularGrid module browser // @author Your name // @match https://modulargrid.net/e/modules/browser* // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; const SELECTORS = { MARKETPLACE: '#SearchMarketplace', VENDOR: '#SearchVendor', SUBMIT_BOX: '.submit-box', CHECKBOXES: '#form-search .grid.last-row .input.cb', MODULES: '.box-module', VENDOR_LINK: '.lnk-vendor', MODULE_COUNT: '.browser .count', RESET_BUTTON: '#btn-reset-search', BRAND_EXCLUSION: '.brand-exclusion' }; const STYLES = { CHECKBOX_CONTAINER: { display: 'flex', gap: '20px', marginTop: '10px' }, HIDDEN_SPAN: { visibility: 'hidden', position: 'absolute', whiteSpace: 'nowrap' }, ADDITIONAL_FILTER: { marginBottom: '5px' }, ADDITIONAL_WRAPPER: { marginTop: '5px' } }; class ModuleFilter { constructor() { this.init = this.init.bind(this); this.applyAllFilters = this.applyAllFilters.bind(this); this.resetFilters = this.resetFilters.bind(this); this.init(); } init() { const elements = this.getRequiredElements(); if (!elements) return; const { marketplaceContainer, submitBox, checkboxes } = elements; const exclusionContainer = this.createMainContainer(); const additionalFiltersWrapper = this.createAdditionalFiltersWrapper(); const mainFilter = this.createMainFilter(additionalFiltersWrapper); exclusionContainer.appendChild(this.createLabel()); exclusionContainer.appendChild(mainFilter); exclusionContainer.appendChild(additionalFiltersWrapper); this.insertFilter(exclusionContainer, marketplaceContainer); this.setupCheckboxes(checkboxes, submitBox); this.setupResetButton(); } appendElements(parent, elements) { elements.forEach(element => { if (element) parent.appendChild(element); }); } getRequiredElements() { const marketplaceSelect = document.querySelector(SELECTORS.MARKETPLACE); const marketplaceContainer = marketplaceSelect?.closest('.input.select'); const submitBox = document.querySelector(SELECTORS.SUBMIT_BOX); const checkboxes = Array.from(document.querySelectorAll(SELECTORS.CHECKBOXES)); if (!marketplaceContainer || !submitBox || !checkboxes.length) { console.warn('Required elements not found'); return null; } return { marketplaceContainer, submitBox, checkboxes }; } createMainContainer() { const container = document.createElement('div'); container.className = 'input select'; return container; } createLabel() { const label = document.createElement('label'); label.textContent = 'Exclude Manufacturer'; return label; } createAdditionalFiltersWrapper() { const wrapper = document.createElement('div'); Object.assign(wrapper.style, STYLES.ADDITIONAL_WRAPPER); return wrapper; } createMainFilter(additionalFiltersWrapper) { const inputAppend = document.createElement('div'); inputAppend.className = 'input input-append'; const select = this.createDropdown(); const addButton = this.createButton('+', () => this.addNewFilter(additionalFiltersWrapper)); inputAppend.appendChild(select); inputAppend.appendChild(addButton); this.adjustSelectWidth(select, { min: 100, max: 300, exact: null }); select.addEventListener('change', () => this.applyAllFilters()); return inputAppend; } createDropdown() { const select = document.createElement('select'); select.className = 'brand-exclusion'; const defaultOption = document.createElement('option'); defaultOption.value = ''; defaultOption.text = '-'; select.appendChild(defaultOption); const manufacturerSelect = document.querySelector(SELECTORS.VENDOR); if (manufacturerSelect) { Array.from(manufacturerSelect.options) .filter(opt => opt.value !== '') .forEach(opt => { const option = document.createElement('option'); option.value = opt.value; option.text = opt.text; select.appendChild(option); }); } return select; } createButton(text, onClick) { const button = document.createElement('button'); button.textContent = text; button.className = 'btn'; button.type = 'button'; button.addEventListener('click', (e) => { try { onClick(e); } catch (error) { console.warn('Button click handler failed:', error); } }); return button; } addNewFilter(container) { const inputAppend = document.createElement('div'); inputAppend.className = 'input input-append'; Object.assign(inputAppend.style, STYLES.ADDITIONAL_FILTER); const select = this.createDropdown(); const removeButton = this.createButton('×', () => { inputAppend.remove(); this.applyAllFilters(); }); inputAppend.appendChild(select); inputAppend.appendChild(removeButton); container.appendChild(inputAppend); this.adjustSelectWidth(select, { min: 100, max: 300, exact: null }); select.addEventListener('change', () => this.applyAllFilters()); } adjustSelectWidth(select, { min = 100, max = 300, exact = null } = {}) { const span = document.createElement('span'); Object.assign(span.style, { ...STYLES.HIDDEN_SPAN, font: window.getComputedStyle(select).font }); document.body.appendChild(span); const maxWidth = Array.from(select.options).reduce((max, option) => { span.textContent = option.text; return Math.max(max, span.offsetWidth); }, 0); span.remove(); const calculatedWidth = Math.min(Math.max(maxWidth + 30, min), max); select.style.width = exact ? `${exact}px` : `${calculatedWidth}px`; } setupCheckboxes(checkboxes, submitBox) { const container = document.createElement('div'); Object.assign(container.style, STYLES.CHECKBOX_CONTAINER); checkboxes.forEach(checkbox => { checkbox.remove(); container.appendChild(checkbox); }); submitBox.parentNode.insertBefore(container, submitBox.nextSibling); } setupResetButton() { const resetButton = document.querySelector(SELECTORS.RESET_BUTTON); if (!resetButton) return; const originalClick = resetButton.onclick; resetButton.onclick = (e) => { if (originalClick) originalClick.call(resetButton, e); this.resetFilters(); }; } insertFilter(filter, referenceNode) { referenceNode.parentNode.insertBefore(filter, referenceNode.nextSibling); } applyAllFilters() { const excludedBrandIds = Array.from(document.querySelectorAll(SELECTORS.BRAND_EXCLUSION)) .map(select => select.value) .filter(Boolean); excludedBrandIds.length ? this.hideExcludedModules(excludedBrandIds) : this.showAllModules(); } showAllModules() { document.querySelectorAll(SELECTORS.MODULES) .forEach(module => module.style.display = ''); this.updateModuleCount(); } hideExcludedModules(excludedBrandIds) { let visibleCount = 0; document.querySelectorAll(SELECTORS.MODULES).forEach(module => { const manufacturerLink = module.querySelector(SELECTORS.VENDOR_LINK); if (manufacturerLink) { const vendorId = manufacturerLink.href.split('/').pop(); module.style.display = excludedBrandIds.includes(vendorId) ? 'none' : ''; if (module.style.display === '') visibleCount++; } }); this.updateModuleCount(); } updateModuleCount() { const countElement = document.querySelector(SELECTORS.MODULE_COUNT); if (countElement) { const visibleCount = document.querySelectorAll(`${SELECTORS.MODULES}[style="display: ;"]`).length; countElement.textContent = `${visibleCount} module${visibleCount !== 1 ? 's' : ''}`; } } resetFilters() { document.querySelectorAll(SELECTORS.BRAND_EXCLUSION) .forEach(filter => filter.value = ''); this.showAllModules(); } } function initializeFilter() { try { new ModuleFilter(); } catch (error) { console.warn('ModuleFilter initialization failed:', error); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializeFilter); } else { setTimeout(initializeFilter, 100); } })();