(function() { 'use strict'; // Ensure EbayParser is loaded if (typeof EbayParser === 'undefined') { console.error("Ebay Cost/TB: CRITICAL - ebay_core.js was not loaded. @require path might be incorrect."); return; } const DEBUG_MODE = true; // Set to true to run unit tests on load // --- Global Variables (UI/State specific) --- let originalOrderMap = new Map(); let isSorted = false; let mainListParentElement = null; let observer = null; let observerTargetNode = null; const observerConfig = { childList: true, subtree: true }; let processTimer = null; // --- UI & DOM Functions --- function addStyles() { /* ... (Keep existing addStyles function) ... */ const css = ` li.s-item, li.srp-results__item { position: relative !important; overflow: visible !important; padding-bottom: 5px !important; } .cost-per-tb-info { position: absolute; right: 10px; top: 40px; background-color: rgba(0, 100, 0, 0.85); color: white; padding: 4px 8px; border-radius: 5px; z-index: 1000; font-size: 0.9em; font-weight: bold; text-align: center; box-shadow: 1px 1px 3px rgba(0,0,0,0.5); transition: transform 0.2s ease; } .cost-per-tb-info:hover { transform: scale(1.05); } .cost-per-tb-info small.check-desc-note { color: #FFD700; font-weight: bold; } #costPerTbSortControl { display: inline-block; margin-left: 20px; padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px; background-color: #f0f0f0; vertical-align: middle; color: #333; cursor: pointer; font-size: 14px; } #costPerTbSortControl:hover { background-color: #e9e9e9; } #costPerTbSortControl input[type="checkbox"] { margin-right: 8px; vertical-align: middle; } #costPerTbSortControl label { cursor: pointer; vertical-align: middle; color: #333; } `; try { const styleSheet = document.createElement("style"); styleSheet.type = "text/css"; styleSheet.innerText = css; document.head.appendChild(styleSheet); } catch (e) { console.error("Ebay Cost/TB: Failed to add styles:", e); } } function getItemSelector() { /* ... (Keep existing getItemSelector function) ... */ if (document.querySelector('li.s-item')) return 'li.s-item'; if (document.querySelector('li.srp-results__item')) return 'li.srp-results__item'; if (document.querySelector('div.s-item')) return 'div.s-item'; return 'li[class*="s-item"], div[class*="s-item"]'; } function getMainListParent() { /* ... (Keep existing getMainListParent function) ... */ if (mainListParentElement && document.body.contains(mainListParentElement)) { return mainListParentElement; } mainListParentElement = document.querySelector('ul.srp-results, div#srp-river-results ul.srp-results'); if (!mainListParentElement) { mainListParentElement = document.querySelector('div#srp-river-results ul'); } if (!mainListParentElement) { const itemSelector = getItemSelector(); const containers = document.querySelectorAll('ul, div'); for (const container of containers) { if (container.querySelector(itemSelector)) { const directChildren = Array.from(container.children).filter(child => child.matches(itemSelector)); if (directChildren.length > 1) { mainListParentElement = container; break; } } } } return mainListParentElement; } function storeOriginalOrder(itemsNodeList) { /* ... (Keep existing storeOriginalOrder function) ... */ if (originalOrderMap.size > 0 && itemsNodeList.length === originalOrderMap.size) { return; } originalOrderMap.clear(); itemsNodeList.forEach((itemElement, index) => { let itemId = itemElement.id; if (!itemId) { const linkWithItemId = itemElement.querySelector('a[href*="/itm/"]'); if (linkWithItemId && linkWithItemId.href) { const match = linkWithItemId.href.match(/\/itm\/(\d+)/); if (match && match[1]) { itemId = "ebayitem_" + match[1]; itemElement.id = itemId; } } } if (itemId && !originalOrderMap.has(itemId)) originalOrderMap.set(itemId, index); }); } function processResults() { const parent = getMainListParent(); if (!parent) return; const itemSelector = getItemSelector(); const itemsOnPage = parent.querySelectorAll(itemSelector + ':not(.cost-per-tb-processed-flag)'); itemsOnPage.forEach(item => { const titleElement = item.querySelector('.s-item__title, .srp-results__title'); const priceElement = item.querySelector('.s-item__price, .srp-results__price'); if (titleElement && priceElement) { const title = titleElement.innerText; const priceText = priceElement.innerText; // --- Use Core Parser --- const price = EbayParser.parsePrice(priceText); const parsedInfo = EbayParser.parseSizeAndQuantity(title); // ----------------------- const totalTB = parsedInfo.totalTB; const needed_description_check = parsedInfo.needed_description_check; item.dataset.neededDescriptionCheck = String(needed_description_check); let displayElement = item.querySelector('.cost-per-tb-info'); if (!displayElement) { displayElement = document.createElement('div'); displayElement.className = 'cost-per-tb-info'; const imageContainer = item.querySelector('.s-item__image-section, .s-item__image'); if (imageContainer && imageContainer.parentNode) { imageContainer.parentNode.style.position = 'relative'; imageContainer.parentNode.appendChild(displayElement); } else { item.appendChild(displayElement); } } if (price !== null && totalTB > 0) { const costPerTB = price / totalTB; if (needed_description_check) { item.dataset.costPerTb = '9999999'; displayElement.innerHTML = `$${costPerTB.toFixed(2)}* / TB
(${totalTB.toFixed(2)} TB, Check Desc.)`; } else { item.dataset.costPerTb = costPerTB; displayElement.innerHTML = `$${costPerTB.toFixed(2)} / TB
(${totalTB.toFixed(2)} TB)`; } } else { item.dataset.costPerTb = '9999999'; let ambiguousNote = needed_description_check || (parsedInfo.quantity > 1 && totalTB === 0) ? "(Check Desc.)" : "(Details N/A)"; let tbDisplay = totalTB > 0 ? `(${totalTB.toFixed(2)} TB, Price N/A)` : ambiguousNote; if (totalTB === 0) tbDisplay = ambiguousNote; displayElement.innerHTML = `Details unclear
${tbDisplay}`; } if(displayElement.querySelector('span')) displayElement.querySelector('span').style.fontSize = '0.8em'; if(displayElement.querySelector('small')) displayElement.querySelector('small').style.fontSize = '0.7em'; } else { item.dataset.costPerTb = '9999999'; } item.classList.add('cost-per-tb-processed-flag'); item.dataset.costPerTbProcessed = 'true'; }); const allItems = parent.querySelectorAll(itemSelector); if (originalOrderMap.size === 0 || Math.abs(originalOrderMap.size - allItems.length) > 5) { if (allItems.length > 0) storeOriginalOrder(allItems); } if (isSorted) sortResults(); } function sortResults() { /* ... (Keep existing sortResults function) ... */ const parent = getMainListParent(); if (!parent) return; const itemSelector = getItemSelector(); let itemsArray = Array.from(parent.querySelectorAll(itemSelector)); if (originalOrderMap.size === 0 && itemsArray.length > 0) storeOriginalOrder(itemsArray); itemsArray.sort((a, b) => parseFloat(a.dataset.costPerTb || '9999999') - parseFloat(b.dataset.costPerTb || '9999999')); if (observer) observer.disconnect(); itemsArray.forEach(item => parent.appendChild(item)); if (observer && observerTargetNode) observer.observe(observerTargetNode, observerConfig); } function restoreOriginalOrder() { /* ... (Keep existing restoreOriginalOrder function) ... */ const parent = getMainListParent(); if (!parent) return; if (originalOrderMap.size === 0) return; let itemsToReorder = []; originalOrderMap.forEach((originalIndex, itemId) => { const itemElement = document.getElementById(itemId) || parent.querySelector(`[id="${itemId}"]`); if (itemElement && parent.contains(itemElement)) itemsToReorder.push({ element: itemElement, originalIndex: originalIndex }); }); const itemSelector = getItemSelector(); const currentItemsInDOM = parent.querySelectorAll(itemSelector); let newItemsCount = 0; currentItemsInDOM.forEach(domItem => { if (domItem.id && !originalOrderMap.has(domItem.id)) itemsToReorder.push({ element: domItem, originalIndex: 900000 + newItemsCount++ }); }); itemsToReorder.sort((a, b) => a.originalIndex - b.originalIndex); if (observer) observer.disconnect(); itemsToReorder.forEach(entry => parent.appendChild(entry.element)); if (observer && observerTargetNode) observer.observe(observerTargetNode, observerConfig); } function handleSortToggle(event) { /* ... (Keep existing handleSortToggle function) ... */ isSorted = event.currentTarget.checked; if (isSorted) sortResults(); else restoreOriginalOrder(); } function addSortControl() { /* ... (Keep existing addSortControl function) ... */ let controlsContainer = document.querySelector('.srp-controls__sort- σήμερα') || document.querySelector('.srp-controls__sort') || document.querySelector('div[class*="srp-controls__sort"]'); if (!controlsContainer) controlsContainer = document.querySelector('.srp-sort, .srp-controls'); if (!controlsContainer || document.getElementById('costPerTbSortControlWrapper')) return; const controlWrapper = document.createElement('div'); controlWrapper.id = 'costPerTbSortControlWrapper'; controlWrapper.style.display = 'inline-block'; controlWrapper.style.marginLeft = '20px'; controlWrapper.style.verticalAlign = 'middle'; const controlDiv = document.createElement('div'); controlDiv.id = 'costPerTbSortControl'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.id = 'costPerTbSortCheckbox'; checkbox.checked = isSorted; checkbox.style.verticalAlign = 'middle'; const label = document.createElement('label'); label.htmlFor = 'costPerTbSortCheckbox'; label.innerText = 'Sort by $/TB'; label.style.verticalAlign = 'middle'; label.style.marginLeft = '5px'; controlDiv.appendChild(checkbox); controlDiv.appendChild(label); controlWrapper.appendChild(controlDiv); controlDiv.addEventListener('click', (e) => { if (e.target !== checkbox) { checkbox.checked = !checkbox.checked; checkbox.dispatchEvent(new Event('change', { bubbles: true })); } }); checkbox.addEventListener('change', handleSortToggle); if (controlsContainer.nextSibling) controlsContainer.parentNode.insertBefore(controlWrapper, controlsContainer.nextSibling); else controlsContainer.parentNode.appendChild(controlWrapper); } function setupObserver() { /* ... (Keep existing setupObserver function but without needsProcessing/callback logic, just call process/addControl) ... */ observerTargetNode = getMainListParent() || document.body; if (!observerTargetNode) return; const itemSelector = getItemSelector(); const callback = function(mutationsList, obs) { let needsProcessing = false; for(const mutation of mutationsList) { if (mutation.type === 'childList') { if (mutation.addedNodes.length > 0) { mutation.addedNodes.forEach(node => { if (node.nodeType === 1 && !node.classList.contains('cost-per-tb-info') && (node.matches && node.matches(itemSelector) || (node.querySelector && node.querySelector(itemSelector)))) { needsProcessing = true; } }); } } if (!document.getElementById('costPerTbSortControlWrapper') && document.querySelector('.srp-controls__sort')) { needsProcessing = true; } } if (needsProcessing) { clearTimeout(processTimer); processTimer = setTimeout(() => { processResults(); addSortControl(); }, 750); } }; if (observer) observer.disconnect(); observer = new MutationObserver(callback); observer.observe(observerTargetNode, observerConfig); } // --- Main Execution --- function init() { console.log("Ebay Cost/TB V2.7 (Refactored) starting..."); if (DEBUG_MODE) { // --- Use Core Unit Tests --- EbayParser.runUnitTests(); // -------------------------- } addStyles(); setTimeout(() => { processResults(); addSortControl(); setupObserver(); }, 2000); } init(); // Run-at document-idle handles load state })();