Refactor URL pattern matching and improve image loading for product thumbnails

This commit is contained in:
2025-09-10 23:08:38 +02:00
parent ef15f16e18
commit 17d69ac9df

238
apilo.js
View File

@@ -4,163 +4,207 @@ const patterns = [
/^https:\/\/projectpro\.apilo\.com\/order\/order\/in-progress\/?$/, /^https:\/\/projectpro\.apilo\.com\/order\/order\/in-progress\/?$/,
/^https:\/\/projectpro\.apilo\.com\/order\/order\/to-send\/?$/, /^https:\/\/projectpro\.apilo\.com\/order\/order\/to-send\/?$/,
/^https:\/\/projectpro\.apilo\.com\/order\/order\/completed\/?$/, /^https:\/\/projectpro\.apilo\.com\/order\/order\/completed\/?$/,
/^https:\/\/projectpro\.apilo\.com\/order\/order\/all\/?$/ /^https:\/\/projectpro\.apilo\.com\/order\/order\/status\/?$/,
/^https:\/\/projectpro\.apilo\.com\/order\/order\/(news|in-progress|to-send|completed|all)\/?$/,
/^https:\/\/projectpro\.apilo\.com\/order\/order\/status\/[^\/?#]+\/?$/
]; ];
if (patterns.some(pattern => pattern.test(currentUrl))) { console.log('test'); if (patterns.some(pattern => pattern.test(currentUrl))) {
console.log('test');
waitForTableAndSetImage(); waitForTableAndSetImage();
attachTableReloadListener(); attachTableReloadListener();
} else { } else {
console.log('Nie pasuje do żadnego wzorca URL'); console.log('Nie pasuje do żadnego wzorca URL');
} }
// --- 2) Czekanie na tabelę i start przetwarzania ---
function waitForTableAndSetImage() { function waitForTableAndSetImage() {
const start = Date.now();
const maxWait = 10000; // 10s
const intervalId = setInterval(() => { const intervalId = setInterval(() => {
let dataTables_scrollBody = document.getElementsByClassName('dataTables_scrollBody'); const tbody = document.querySelector('#DataTables_Table_0 tbody');
if (dataTables_scrollBody.length > 0) { if (tbody && tbody.querySelector('tr')) {
let dataTables_tbody = dataTables_scrollBody[0].getElementsByTagName('tbody')[0];
let rows = dataTables_tbody.getElementsByTagName('tr');
if (rows.length > 0) {
clearInterval(intervalId); clearInterval(intervalId);
setImageToProduct(); setImageToProduct();
} else if (Date.now() - start > maxWait) {
clearInterval(intervalId);
} }
} }, 120);
}, 100);
} }
// --- 3) Obserwator zmian w tabeli (przeładowanie/paginacja) ---
function attachTableReloadListener() { function attachTableReloadListener() {
const table = document.querySelector('.dataTables_scrollBody table'); const tbody = document.querySelector('#DataTables_Table_0 tbody');
if (table) { if (!tbody) return;
const observer = new MutationObserver((mutationsList, observer) => { const observer = new MutationObserver(mutations => {
for (const mutation of mutationsList) { for (const m of mutations) {
if (mutation.type === 'childList') { if (m.type === 'childList') {
waitForTableAndSetImage(); setImageToProduct();
break; break;
} }
} }
}); });
observer.observe(tbody, { childList: true, subtree: false });
observer.observe(table.querySelector('tbody'), { childList: true });
}
} }
function setImageToProduct(img = '') { // --- 4) Główny worker: wstawianie miniatur wg SKU ---
let dataTables_scrollBody = document.getElementsByClassName('dataTables_scrollBody'); const imgCache = new Map(); // SKU -> imgURL
if (dataTables_scrollBody.length > 0) {
let dataTables_tbody = dataTables_scrollBody[0].getElementsByTagName('tbody')[0];
let rows = dataTables_tbody.getElementsByTagName('tr');
for (let i = 0; i < rows.length; i++) { function setImageToProduct() {
let cells = rows[i].getElementsByTagName('td'); const tbody = document.querySelector('#DataTables_Table_0 tbody');
if (!tbody) return;
if (cells.length > 1) { const rows = Array.from(tbody.querySelectorAll('tr'));
let secondCellText = cells[1].textContent.trim(); rows.forEach(tr => {
let domain; const cells = tr.querySelectorAll('td');
if (cells.length < 4) return;
if (secondCellText.includes('marianek.pl')) { // Kolumna 2 zawiera info z domeną (wklejone w tym samym TD co numer zamówienia itp.)
domain = 'marianek.pl'; const domain = detectDomainFromCell(cells[1]);
} else if (secondCellText.includes('pomysloweprezenty.pl')) { if (!domain) return;
domain = 'pomysloweprezenty.pl';
} else {
continue;
}
if (cells.length >= 5) { // Kolumna 4: "Dane produktu"
let fifthCell = cells[4]; const productsCell = cells[3];
let divsInFifthCell = fifthCell.children; // ============ POPRAWIONY FRAGMENT ============
for (let i = 0; i < divsInFifthCell.length; i++) { // Każdy produkt: <div data-item="all-products"><div data-item="product">...</div></div>
let currentDiv = divsInFifthCell[i]; const productBlocks = productsCell.querySelectorAll('[data-item="all-products"] [data-item="product"]');
let imgDiv = currentDiv.getElementsByTagName('div')[0];
let dataDiv = currentDiv.getElementsByTagName('div')[1];
if (dataDiv) { productBlocks.forEach(block => {
let skuText = dataDiv.innerHTML.match(/SKU:\s*([A-Za-z0-9-]+)/); // nie rób drugi raz
if (block.dataset.imgInserted === '1') return;
if (skuText && skuText[1]) { // Struktura: [data-item="product"] > .d-flex.mb-2 > [imgHolder DIV] + [dataHolder DIV]
getProductData(skuText[1], domain).then(data => { const flex =
if (!data) { block.querySelector(':scope > .d-flex.mb-2') ||
console.log('Product not found:', skuText[1]); block.querySelector('.d-flex.mb-2') ||
block.firstElementChild; // awaryjnie, gdyby klasy się zmieniły
if (!flex) return;
const imgHolder = flex.children?.[0]; // <div class="d-flex ... mr-3"><i .../></div>
const dataHolder = flex.children?.[1]; // <div> z nazwą, SKU, EAN, ceną
if (!imgHolder || !dataHolder) return;
// Jeśli już jest <img> w imgHolder, pomijamy
if (imgHolder.querySelector('img')) {
block.dataset.imgInserted = '1';
return; return;
} }
console.log('Product found:', skuText[1]); // Znajdź konkretnie DIV z SKU (bez mieszania z innymi liniami)
imgDiv.innerHTML = ''; const skuDiv = Array.from(dataHolder.querySelectorAll('div'))
const imgElement = makeImg(data); .find(d => /SKU\s*:/i.test(d.textContent || ''));
imgDiv.appendChild(imgElement);
imgElement.addEventListener('mouseover', function() { const skuMatch = (skuDiv?.textContent || '').match(/SKU\s*:\s*([A-Za-z0-9_-]+)/i);
showLargeImage(data, imgElement); const sku = skuMatch?.[1]?.toUpperCase();
if (!sku) return;
const placeImage = (imgUrl) => {
if (!imgUrl) return;
// czyścimy ikonę <i> i wstawiamy miniaturę
imgHolder.innerHTML = '';
const imgEl = makeImg(imgUrl);
imgHolder.appendChild(imgEl);
setupPreview(imgEl, imgUrl);
block.dataset.imgInserted = '1';
};
if (imgCache.has(sku)) {
placeImage(imgCache.get(sku));
} else {
getProductData(sku, domain)
.then(url => {
if (url) imgCache.set(sku, url);
placeImage(url);
})
.catch(() => { });
}
}); });
imgElement.addEventListener('mouseout', function() {
hideLargeImage();
}); });
});
}
}
}
}
}
}
}
} }
function makeImg(src = 'https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png') { function detectDomainFromCell(td) {
const text = (td.textContent || '').toLowerCase();
if (text.includes('marianek.pl')) return 'marianek.pl';
if (text.includes('pomysloweprezenty.pl')) return 'pomysloweprezenty.pl';
return null;
}
function makeImg(src) {
const img = document.createElement('img'); const img = document.createElement('img');
img.src = src; img.src = src;
img.alt = 'image'; img.alt = 'product';
img.className = 'img-fluid center-block'; img.className = 'img-fluid center-block';
img.style.maxWidth = '48px'; img.style.maxWidth = '48px';
img.style.maxHeight = '48px'; img.style.maxHeight = '48px';
img.style.objectFit = 'contain';
img.style.cursor = 'zoom-in';
return img; return img;
} }
function showLargeImage(src, imgElement) { // --- 5) Podgląd dużego obrazka ---
const largeImg = document.createElement('img'); function setupPreview(imgElement, src) {
let largeImg = null;
const onMove = (ev) => {
if (!largeImg) return;
const pad = 10;
largeImg.style.top = (ev.pageY - largeImg.height / 2) + 'px';
largeImg.style.left = (ev.pageX + pad) + 'px';
};
const onOver = (ev) => {
if (largeImg) return;
largeImg = document.createElement('img');
largeImg.id = 'largeImagePreview';
largeImg.src = src; largeImg.src = src;
largeImg.style.position = 'absolute'; largeImg.style.position = 'absolute';
largeImg.style.maxWidth = '400px'; largeImg.style.maxWidth = '400px';
largeImg.style.maxHeight = '400px'; largeImg.style.maxHeight = '400px';
largeImg.style.border = '1px solid #ccc'; largeImg.style.border = '1px solid #ccc';
largeImg.style.background = '#fff'; largeImg.style.background = '#fff';
largeImg.style.zIndex = '1000'; largeImg.style.zIndex = '10000';
largeImg.style.top = imgElement.getBoundingClientRect().top + window.scrollY + 'px'; largeImg.style.pointerEvents = 'none';
largeImg.style.left = imgElement.getBoundingClientRect().left + imgElement.offsetWidth + 10 + 'px';
largeImg.id = 'largeImagePreview';
document.body.appendChild(largeImg); document.body.appendChild(largeImg);
} onMove(ev);
document.addEventListener('mousemove', onMove);
};
function hideLargeImage() { const onOut = () => {
const largeImg = document.getElementById('largeImagePreview');
if (largeImg) { if (largeImg) {
document.removeEventListener('mousemove', onMove);
largeImg.remove(); largeImg.remove();
largeImg = null;
} }
};
imgElement.addEventListener('mouseover', onOver);
imgElement.addEventListener('mouseout', onOut);
imgElement.addEventListener('mouseleave', onOut);
} }
// --- 6) API: pobranie URL obrazka po SKU i domenie ---
async function getProductData(sku, domain) { async function getProductData(sku, domain) {
let url; let url = null;
if (domain === 'marianek.pl') { if (domain === 'marianek.pl') {
url = `https://marianek.pl/api/v1/product.php?sku=${sku}`; url = `https://marianek.pl/api/v1/product.php?sku=${encodeURIComponent(sku)}`;
} else if (domain === 'pomysloweprezenty.pl') { } else if (domain === 'pomysloweprezenty.pl') {
url = `https://pomysloweprezenty.pl/api/v1/product.php?sku=${sku}`; url = `https://pomysloweprezenty.pl/api/v1/product.php?sku=${encodeURIComponent(sku)}`;
} else { } else {
console.error('Unsupported domain:', domain);
return null; return null;
} }
try { try {
const response = await fetch(url); const resp = await fetch(url, { credentials: 'omit' });
if (!response.ok) { if (!resp.ok) {
throw new Error(`HTTP error! status: ${response.status}`); throw new Error(`HTTP error! status: ${resp.status}`);
} }
const data = await resp.json();
const data = await response.json(); console.log(data); // zakładam, że API zwraca { img: "https://..." }
return data.img; return data?.img || null;
} catch (error) { } catch (err) {
console.error('Error fetching product data: ' + url, error); console.error('Error fetching product data:', url, err);
return null; return null;
} }
} }
@@ -223,7 +267,7 @@ async function handleShipment(method, dropoffPoint = null) {
if (dropoffPoint) { if (dropoffPoint) {
await setDropoffPoint(dropoffPoint); await setDropoffPoint(dropoffPoint);
} }
await setContent( method ); await setContent(method);
await setParcelWeight('1'); await setParcelWeight('1');
await submitShipment(); await submitShipment();
} catch (error) { } catch (error) {
@@ -252,8 +296,8 @@ function retryUntilSuccess(fn, interval = 1000, retries = 10) {
}); });
} }
function setContent( method ) { function setContent(method) {
if ( method == 'D2D.apaczka' || method == 'D2P.apaczka' || method == 'P2P.apaczka' || method == 'P2P.orlen' ) { if (method == 'D2D.apaczka' || method == 'D2P.apaczka' || method == 'P2P.apaczka' || method == 'P2P.orlen') {
return retryUntilSuccess(() => { return retryUntilSuccess(() => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const selectElement = document.getElementById('warehousebundle_shipment_preferences_content'); const selectElement = document.getElementById('warehousebundle_shipment_preferences_content');
@@ -271,7 +315,7 @@ function selectPackageType(method) {
return retryUntilSuccess(() => { return retryUntilSuccess(() => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if ( method == 'P2P.orlen' ) { if (method == 'P2P.orlen') {
//warehousebundle_shipment_carrierAccount //warehousebundle_shipment_carrierAccount
const selectElement = document.getElementById('warehousebundle_shipment_carrierAccount'); const selectElement = document.getElementById('warehousebundle_shipment_carrierAccount');
@@ -300,7 +344,7 @@ function selectPackageType(method) {
}, 1000); }, 1000);
}, 1000); }, 1000);
} else if ( method == 'D2P.apaczka' || method == 'P2P.apaczka' ) { } else if (method == 'D2P.apaczka' || method == 'P2P.apaczka') {
//warehousebundle_shipment_carrierAccount //warehousebundle_shipment_carrierAccount
const selectElement = document.getElementById('warehousebundle_shipment_carrierAccount'); const selectElement = document.getElementById('warehousebundle_shipment_carrierAccount');
@@ -330,7 +374,7 @@ function selectPackageType(method) {
}, 1000); }, 1000);
} else if ( method == 'D2D.apaczka' ) { } else if (method == 'D2D.apaczka') {
const selectElement = document.getElementById('warehousebundle_shipment_carrierAccount'); const selectElement = document.getElementById('warehousebundle_shipment_carrierAccount');
selectElement.value = 17; selectElement.value = 17;
@@ -375,7 +419,7 @@ function selectPackageType(method) {
} }
function setShipmentMethod(method) { function setShipmentMethod(method) {
if ( method == 'D2D.apaczka' || method == 'D2P.apaczka' || method == 'P2P.apaczka' || method == 'P2P.orlen' ) { if (method == 'D2D.apaczka' || method == 'D2P.apaczka' || method == 'P2P.apaczka' || method == 'P2P.orlen') {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
resolve(); resolve();
}); });