(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
})();