您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Upgrades AliExpress with an advanced search UI for filtering products by keywords, phrases, and logical operators.
// ==UserScript== // @name AliExpress Advanced Search UI // @name:lt AliExpress išplėstinės paieškos vartotojo sąsaja // @namespace http://tampermonkey.net/ // @version 1.0 // @description Upgrades AliExpress with an advanced search UI for filtering products by keywords, phrases, and logical operators. // @description:lt Praplečia AliExpress su išplėstine paieškos vartotojo sąsaja, skirta filtruoti produktus pagal raktinius žodžius, frazes ir loginius operatorius. // @author ABC // @license MIT // @match *://www.aliexpress.com/w/wholesale* // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; const SCRIPT_ID = 'ali-advanced-filter'; const CONTAINER_ID = `${SCRIPT_ID}-container`; const FILTER_INPUT_ID = `${SCRIPT_ID}-input`; const CLEAR_BTN_ID = `${SCRIPT_ID}-clear-btn`; const TOGGLE_ID = `${SCRIPT_ID}-toggle`; const HELP_ID = `${SCRIPT_ID}-help`; const TOOLTIP_ID = `${SCRIPT_ID}-tooltip`; function addStyles() { GM_addStyle(` #_global_header_23_ { min-height: 100px !important; } .pc-header--search--3hnHLKw { position: relative; } /* Anchor for our filter bar */ #${CONTAINER_ID} { position: absolute; top: 51px; /* Position it directly below the original search bar */ left: 0; width: 100%; display: flex; align-items: center; z-index: 999; } #${FILTER_INPUT_ID} { flex-grow: 1; height: 34px; border: 1px solid #ccc; border-radius: 17px; padding: 0 85px 0 15px; /* Make space for all buttons */ font-size: 14px; color: #000; } #${CLEAR_BTN_ID} { position: absolute; right: 75px; /* Adjust position relative to the end */ top: 50%; transform: translateY(-50%); cursor: pointer; font-size: 24px; color: #999; display: none; z-index: 10; } #${CLEAR_BTN_ID}:hover { color: #333; } .ali-filter-switch { position: absolute; right: 35px; /* Position next to clear button */ top: 50%; transform: translateY(-50%); width: 40px; height: 22px; } .ali-filter-switch input { opacity: 0; width: 0; height: 0; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 22px; } .slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 2px; bottom: 2px; background-color: white; transition: .4s; border-radius: 50%; } input:checked + .slider { background-color: #2196F3; } input:checked + .slider:before { transform: translateX(18px); } #${HELP_ID} { position: absolute; right: 5px; /* Position at the very end */ top: 50%; transform: translateY(-50%); width: 20px; height: 20px; border-radius: 50%; background-color: #f0f0f0; color: #666; display: flex; align-items: center; justify-content: center; font-weight: bold; cursor: pointer; } #${TOOLTIP_ID} { display: none; position: absolute; top: 34px; right: 0; width: 300px; background: #FFFFE0; color: #000; border: 1px solid #ccc; border-radius: 6px; padding: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.2); z-index: 99999 !important; } #${TOOLTIP_ID} ul { margin: 0; padding: 0 0 0 18px; } #${TOOLTIP_ID} li { margin-bottom: 5px; } #${HELP_ID}:hover + #${TOOLTIP_ID}, #${TOOLTIP_ID}:hover { display: block; } .kt_x { display: flex; flex-direction: column; flex-grow: 1; } .kt_ki { white-space: normal !important; text-overflow: clip !important; height: auto !important; display: -webkit-box; -webkit-line-clamp: 5; -webkit-box-orient: vertical; overflow: hidden; } `); } function setupUI(anchorElement) { if (document.getElementById(CONTAINER_ID)) return; const filterContainer = document.createElement('div'); filterContainer.id = CONTAINER_ID; filterContainer.innerHTML = ' \ <input type="text" id="' + FILTER_INPUT_ID + '" placeholder="Filter: (a OR b) AND c -d"> \ <span id="' + CLEAR_BTN_ID + '">×</span> \ <label class="ali-filter-switch"> \ <input type="checkbox" id="' + TOGGLE_ID + '"> \ <span class="slider round"></span> \ </label> \ <span id="' + HELP_ID + '">?</span> \ <div id="' + TOOLTIP_ID + '"> \ <strong>Advanced Filtering Rules:</strong> \ <ul> \ <li><b>Implicit AND:</b> <code>word1 word2</code></li> \ <li><b>Exact Phrase:</b> <code>"exact phrase"</code></li> \ <li><b>Exclude:</b> <code>-word</code> or <code>-"phrase"</code></li> \ <li><b>OR Logic:</b> <code>word1 OR word2</code></li> \ <li><b>Grouping:</b> <code>(word1 OR word2) AND word3</code></li> \ </ul> \ </div> \ '; anchorElement.appendChild(filterContainer); } function parseQueryToRPN(query) { if (!query) return []; // 1. Tokenize the query string more robustly const tokens = query.match(/-?"[^"]+"|\bAND\b|\bOR\b|[\w-]+|[()]/g) || []; // 2. Insert implicit AND operators const processedTokens = []; for (let i = 0; i < tokens.length; i++) { processedTokens.push(tokens[i]); const current = tokens[i].toUpperCase(); const next = (tokens[i + 1] || '').toUpperCase(); const isCurrentTerm = current !== '(' && current !== 'AND' && current !== 'OR'; const isNextTerm = next && next !== ')' && next !== 'AND' && next !== 'OR'; if (i < tokens.length - 1 && isCurrentTerm && isNextTerm) { processedTokens.push('AND'); } } // 3. Shunting-yard algorithm const precedence = { 'OR': 1, 'AND': 2 }; const output = []; const operators = []; for (const token of processedTokens) { const upperToken = token.toUpperCase(); if (precedence[upperToken]) { while ( operators.length && operators[operators.length - 1] !== '(' && precedence[operators[operators.length - 1]] >= precedence[upperToken] ) { output.push(operators.pop()); } operators.push(upperToken); } else if (token === '(') { operators.push(token); } else if (token === ')') { while (operators.length && operators[operators.length - 1] !== '(') { output.push(operators.pop()); } operators.pop(); // Pop the '(' } else { output.push(token); // This is a term } } return output.concat(operators.reverse()); } function evaluateRPN(rpn, title) { if (!rpn || rpn.length === 0) return true; const stack = []; for (const token of rpn) { if (token === 'AND' || token === 'OR') { if (stack.length < 2) return false; // Invalid expression const b = stack.pop(); const a = stack.pop(); stack.push(token === 'AND' ? (a && b) : (a || b)); } else { let term = token.toLowerCase(); const isNegative = term.startsWith('-'); if (isNegative) term = term.substring(1); const isPhrase = term.startsWith('"') && term.endsWith('"'); if (isPhrase) term = term.slice(1, -1); // If the term is empty after all stripping, treat as a neutral match. if (term === '') { stack.push(true); continue; } const match = title.includes(term); stack.push(isNegative ? !match : match); } } return stack.length === 1 ? stack.pop() : false; // Should be a single value left } function runFilter() { const toggle = document.getElementById(TOGGLE_ID); const input = document.getElementById(FILTER_INPUT_ID); if (!toggle || !input) return; const isEnabled = toggle.checked; const query = input.value; const productElements = document.querySelectorAll('div[data-tticheck="true"]'); if (!isEnabled) { productElements.forEach(p => { p.style.display = 'block'; }); return; } const rpn = parseQueryToRPN(query); productElements.forEach(productEl => { const titleEl = productEl.querySelector('h3'); let title = ''; if (titleEl) { title = titleEl.textContent.toLowerCase(); } else { const imgEl = productEl.querySelector('img.product-img'); if (imgEl) { title = imgEl.alt.toLowerCase(); } } if (title) { try { const shouldShow = query.trim() ? evaluateRPN(rpn.slice(), title) : true; productEl.style.display = shouldShow ? 'block' : 'none'; } catch (e) { console.error("Filtering error:", e); productEl.style.display = 'block'; // Failsafe } } else { productEl.style.display = 'block'; // Failsafe if no title } }); } function updateUI() { const input = document.getElementById(FILTER_INPUT_ID); const clear = document.getElementById(CLEAR_BTN_ID); if (!input || !clear) return; const hasText = input.value.length > 0; clear.style.display = hasText ? 'block' : 'none'; input.style.backgroundColor = hasText ? 'lightyellow' : 'white'; } function main() { const searchContainer = document.querySelector('.pc-header--search--3hnHLKw'); if (!searchContainer) { console.error("AliExpress Filter: Search container not found."); return; } addStyles(); setupUI(searchContainer); const filterInput = document.getElementById(FILTER_INPUT_ID); const clearBtn = document.getElementById(CLEAR_BTN_ID); const toggleCheckbox = document.getElementById(TOGGLE_ID); filterInput.value = sessionStorage.getItem('ali-filter-query') || ''; toggleCheckbox.checked = (sessionStorage.getItem('ali-filter-enabled') || 'true') === 'true'; updateUI(); filterInput.addEventListener('input', () => { sessionStorage.setItem('ali-filter-query', filterInput.value); updateUI(); runFilter(); }); clearBtn.addEventListener('click', () => { filterInput.value = ''; sessionStorage.removeItem('ali-filter-query'); updateUI(); runFilter(); }); toggleCheckbox.addEventListener('change', () => { sessionStorage.setItem('ali-filter-enabled', toggleCheckbox.checked); runFilter(); }); const observer = new MutationObserver(runFilter); const targetNode = document.getElementById('root'); if (targetNode) { setTimeout(runFilter, 1500); observer.observe(targetNode, { childList: true, subtree: true }); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', main); } else { main(); } })();