254 lines
14 KiB
JavaScript
254 lines
14 KiB
JavaScript
(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)}*<span> / TB</span><br><small class="check-desc-note">(${totalTB.toFixed(2)} TB, Check Desc.)</small>`;
|
|
} else {
|
|
item.dataset.costPerTb = costPerTB;
|
|
displayElement.innerHTML = `$${costPerTB.toFixed(2)}<span> / TB</span><br><small>(${totalTB.toFixed(2)} TB)</small>`;
|
|
}
|
|
} 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 = `<i>Details unclear</i><br><small class="check-desc-note">${tbDisplay}</small>`;
|
|
}
|
|
|
|
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
|
|
|
|
})();
|