;(function() { "use strict"; const VERSION = "1.0.6"; const CSS_PREFIX = "ed-"; const SETTINGS = { galleryDetectionStrategies: [ { type: "selector", options: { selectors: [ ".eyedream-gallery", ".gallery-container", "#gallery", "[data-gallery]" ] } } ], filenameStrategy: "src", filenameDataAttribute: "data-filename", filenameCallback: null, currency: { symbol: "€", position: "after" }, language: "el", minimumPhysicalOrder: 15, sidebarPosition: "right", debug: false, enableAnimations: true, enableUpsell: true, upsellThreshold: 2, PRINTS: [ { id: "10x15", label: "10×15", description: "", price: 3, sort: 1, enabled: true }, { id: "15x20", label: "15×20", description: "", price: 5, sort: 2, enabled: true } ], GIFTS: [ { id: "pin", label: "Κονκάρδα με καρφίτσα", description: "", price: 4, sort: 1, enabled: true }, { id: "magneticPin", label: "Μαγνητική κονκάρδα", description: "", price: 5, sort: 2, enabled: true }, { id: "framedMagnet", label: "Μαγνητάκι με πλαίσιο 9×5cm", description: "", price: 6, sort: 3, enabled: true }, { id: "keychain", label: "Μπρελόκ (διάφανο)", description: "", price: 7, sort: 4, enabled: true } ], DIGITAL_PACKAGES: [ { id: "single", label: "1 Φωτογραφία", description: "Ψηφιακό αρχείο για μία φωτογραφία", price: 10, minPhotos: 1, maxPhotos: 1, sort: 1, enabled: true }, { id: "small", label: "2-5 Φωτογραφίες", description: "Ψηφιακό πακέτο για 2 έως 5 φωτογραφίες", price: 35, minPhotos: 2, maxPhotos: 5, sort: 2, enabled: true }, { id: "medium", label: "6-10 Φωτογραφίες", description: "Ψηφιακό πακέτο για 6 έως 10 φωτογραφίες", price: 60, minPhotos: 6, maxPhotos: 10, sort: 3, enabled: true }, { id: "all", label: "Όλες οι Φωτογραφίες", description: "Ψηφιακό πακέτο για όλες τις φωτογραφίες", price: 100, minPhotos: 11, maxPhotos: Infinity, sort: 4, enabled: true } ] }; const TEXTS = { summary: "Σύνοψη Παραγγελίας", prints: "Εκτυπώσεις", gifts: "Φωτογραφικά Δώρα", digital: "Ψηφιακό αρχείο (Email)", upsellMessageTemplate: "Με ακόμη {0} φωτογραφία(ες) μεταβαίνετε στο επόμενο πακέτο και εξοικονομείτε χρήματα.", minimumOrderMessage: `Για παραγγελίες με εκτυπώσεις ή δώρα απαιτείται ελάχιστη αξία {0} {1}.`, customerInfoTitle: "Στοιχεία Παραγγελίας", nameLabel: "Όνομα *", surnameLabel: "Επώνυμο *", emailLabel: "Email *", phoneLabel: "Τηλέφωνο (προαιρετικό)", notesLabel: "Σημειώσεις (προαιρετικά)", submitBtn: "Υποβολή Παραγγελίας", requiredFieldMessage: "Παρακαλώ συμπληρώστε όλα τα υποχρεωτικά πεδία.", noPhotosSelectedMsg: "Παρακαλώ επιλέξτε τουλάχιστον μία φωτογραφία.", minOrderValidationMsg: `Η παραγγελία σας δεν πληροί το ελάχιστο όριο αξίας των ${SETTINGS.minimumPhysicalOrder} ${SETTINGS.currency.symbol}.`, orderSuccessMessage: "Η παραγγελία σας υποβλήθηκε επιτυχώς.", orderFailureMessage: "Προέκυψε σφάλμα κατά την υποβολή της παραγγελίας σας." }; const APP_STATE = { initialized: false, bound: false, images: [], products: { prints: new Map(), gifts: new Map(), digitalPackages: new Map() }, digitalSelectedImageIds: new Set(), digital: { currentPackage: null }, summary: { totalPrintsQty: 0, totalGiftsQty: 0, totalDigitalQty: 0, totalPrintsAmount: 0, totalGiftsAmount: 0, totalDigitalAmount: 0, grandTotal: 0 }, customer: { name: "", surname: "", email: "", phone: "", notes: "" }, ui: { expandedPrintsImageIds: new Set(), expandedGiftsImageIds: new Set() } }; function querySelectorSafe(selector) { try { return document.querySelector(selector) || null; } catch { return null; } } function extractFilenameFromUrl(url) { if (!url || typeof url !== "string") return ""; const cleanUrl = url.split("?")[0].split("#")[0]; const segments = cleanUrl.split("/"); return segments.length ? segments[segments.length - 1] : ""; } function sanitizeText(text) { if (!text || typeof text !== "string") return ""; return text.trim(); } function escapeHtml(text) { if (!text) return ""; return text.replace(/[&<>"']/g, (m) => { switch (m) { case "&": return "&"; case "<": return "<"; case ">": return ">"; case '"': return """; case "'": return "'"; default: return m; } }); } function sanitizeUrl(url) { if (!url) return ""; return url.replace(/\s/g, "%20"); } function generateUUID() { if (window.crypto && window.crypto.randomUUID) { return window.crypto.randomUUID(); } return `ed-${Date.now()}-${Math.floor(Math.random() * 1e6)}`; } function mapToObject(map) { const obj = {}; if (!(map instanceof Map)) return obj; map.forEach((val, key) => (obj[key] = val)); return obj; } function hasAnyQuantity(map) { if (!(map instanceof Map)) return false; for (const qty of map.values()) if (qty > 0) return true; return false; } function injectStyles() { if (document.getElementById(`${CSS_PREFIX}styles`)) return; const style = document.createElement("style"); style.id = `${CSS_PREFIX}styles`; style.textContent = ` .${CSS_PREFIX}order-controls{margin-top:10px;font-family:Arial,sans-serif;font-size:14px;} .${CSS_PREFIX}filename{font-weight:bold;margin-bottom:6px;} .${CSS_PREFIX}digital-checkbox-wrapper{display:flex;align-items:center;margin-bottom:8px;cursor:pointer;} .${CSS_PREFIX}digital-checkbox{margin-right:6px;} .${CSS_PREFIX}toggle{color:#0073aa;cursor:pointer;user-select:none;margin:6px 0;display:flex;align-items:center;} .${CSS_PREFIX}toggle svg{margin-left:6px;vertical-align:middle;fill:currentColor;} .${CSS_PREFIX}prints-section, .${CSS_PREFIX}gifts-section{margin-left:12px;margin-bottom:10px;} .${CSS_PREFIX}collapsed{display:none;} .${CSS_PREFIX}quantity-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:4px;} .${CSS_PREFIX}product-label{flex-grow:1;} .${CSS_PREFIX}qty-btn{background-color:#0073aa;border:none;color:white;width:26px;height:26px;cursor:pointer;border-radius:3px;} .${CSS_PREFIX}qty-btn:active{background-color:#005177;} .${CSS_PREFIX}quantity-value{padding:0 10px;min-width:20px;text-align:center;display:inline-block;} .${CSS_PREFIX}sidebar{ background:#f9f9f9;border:1px solid #ddd;padding:15px;font-family:Arial,sans-serif; font-size:14px;overflow-y:auto;position:sticky;top:10px;box-sizing:border-box; } .${CSS_PREFIX}sidebar-left{float:left;margin-right:20px;width:300px;} .${CSS_PREFIX}sidebar-right{float:right;margin-left:20px;width:300px;} .${CSS_PREFIX}summary-image{margin-bottom:10px;border-bottom:1px solid #ccc;padding-bottom:10px;display:flex;} .${CSS_PREFIX}summary-thumb{width:60px;height:60px;object-fit:cover;margin-right:10px;} .${CSS_PREFIX}summary-filename{font-weight:bold;flex-grow:1;line-height:1.3;} .${CSS_PREFIX}summary-digital, .${CSS_PREFIX}summary-prints, .${CSS_PREFIX}summary-gifts{font-size:12px;color:#555;} .${CSS_PREFIX}digital-package-info{font-weight:bold;margin-bottom:10px;} .${CSS_PREFIX}upsell-message{color:#d35400;margin-top:10px;} .${CSS_PREFIX}min-order-msg{color:#c0392b;margin-top:10px;} .${CSS_PREFIX}price-breakdown{margin-top:15px;border-top:1px solid #ccc;padding-top:10px;font-weight:bold;} .${CSS_PREFIX}form-field{margin-bottom:10px;} .${CSS_PREFIX}form-field label{display:block;margin-bottom:4px;font-weight:bold;} .${CSS_PREFIX}form-field input, .${CSS_PREFIX}form-field textarea{width:100%;padding:6px;box-sizing:border-box;} .${CSS_PREFIX}submit-btn{background-color:#0073aa;color:#fff;border:none;padding:10px 15px;cursor:pointer;border-radius:4px;font-size:16px;} .${CSS_PREFIX}submit-btn:hover{background-color:#005177;} .${CSS_PREFIX}form-message{margin-top:10px;color:#c0392b;} @media (max-width: 1024px) and (min-width: 601px) { .${CSS_PREFIX}sidebar{width:100%;position:relative;float:none;margin-top:20px;} } @media (max-width: 600px) { .${CSS_PREFIX}sidebar{width:100%;position:relative;float:none;margin-top:20px;padding:15px 10px;font-size:16px;} .${CSS_PREFIX}qty-btn{width:36px;height:36px;font-size:20px;} } `; document.head.appendChild(style); } function createToggleElement(labelText, imageId, type) { const div = document.createElement("div"); div.className = `${CSS_PREFIX}toggle ${CSS_PREFIX}${type}-toggle`; div.dataset.imageId = imageId; const textSpan = document.createElement("span"); textSpan.className = `${CSS_PREFIX}toggle-text`; textSpan.textContent = labelText; div.appendChild(textSpan); const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("width", "10"); svg.setAttribute("height", "10"); svg.setAttribute("viewBox", "0 0 24 24"); svg.setAttribute("aria-hidden", "true"); svg.style.marginLeft = "6px"; svg.style.verticalAlign = "middle"; const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); path.setAttribute("d", "M8 5v14l11-7z"); svg.appendChild(path); div.appendChild(svg); return div; } function createQuantityRow(imageId, category, product) { const row = document.createElement("div"); row.className = `${CSS_PREFIX}quantity-row`; row.dataset.imageId = imageId; row.dataset.category = category; row.dataset.productId = product.id; const label = document.createElement("span"); label.className = `${CSS_PREFIX}product-label`; label.textContent = product.label; row.appendChild(label); const minusBtn = document.createElement("button"); minusBtn.type = "button"; minusBtn.className = `${CSS_PREFIX}qty-btn ${CSS_PREFIX}minus`; minusBtn.dataset.imageId = imageId; minusBtn.dataset.category = category; minusBtn.dataset.productId = product.id; minusBtn.title = `Μείωση ${product.label}`; minusBtn.textContent = "−"; row.appendChild(minusBtn); const qtySpan = document.createElement("span"); qtySpan.className = `${CSS_PREFIX}quantity-value`; qtySpan.textContent = "0"; row.appendChild(qtySpan); const plusBtn = document.createElement("button"); plusBtn.type = "button"; plusBtn.className = `${CSS_PREFIX}qty-btn ${CSS_PREFIX}plus`; plusBtn.dataset.imageId = imageId; plusBtn.dataset.category = category; plusBtn.dataset.productId = product.id; plusBtn.title = `Αύξηση ${product.label}`; plusBtn.textContent = "+"; row.appendChild(plusBtn); return row; } function createQuantitySection(imageId, category, productsMap) { const container = document.createElement("div"); container.className = `${CSS_PREFIX}${category}-section ${CSS_PREFIX}collapsed`; container.dataset.imageId = imageId; Array.from(productsMap.values()) .filter((p) => p.enabled) .sort((a, b) => a.sort - b.sort) .forEach((product) => { const row = createQuantityRow(imageId, category, product); container.appendChild(row); }); return container; } function renderImageControls() { APP_STATE.images.forEach((imgObj) => { if ( imgObj.element.nextElementSibling && imgObj.element.nextElementSibling.classList.contains(`${CSS_PREFIX}order-controls`) ) return; const container = document.createElement("div"); container.className = `${CSS_PREFIX}order-controls`; const filenameEl = document.createElement("div"); filenameEl.className = `${CSS_PREFIX}filename`; filenameEl.textContent = imgObj.filename; container.appendChild(filenameEl); const labelDigital = document.createElement("label"); labelDigital.className = `${CSS_PREFIX}digital-checkbox-wrapper`; const inputDigital = document.createElement("input"); inputDigital.type = "checkbox"; inputDigital.className = `${CSS_PREFIX}digital-checkbox`; inputDigital.dataset.imageId = imgObj.id; labelDigital.appendChild(inputDigital); labelDigital.appendChild(document.createTextNode(" " + TEXTS.digital)); container.appendChild(labelDigital); const printsToggle = createToggleElement(TEXTS.prints, imgObj.id, "prints"); container.appendChild(printsToggle); const printsSection = createQuantitySection(imgObj.id, "prints", APP_STATE.products.prints); container.appendChild(printsSection); const giftsToggle = createToggleElement(TEXTS.gifts, imgObj.id, "gifts"); container.appendChild(giftsToggle); const giftsSection = createQuantitySection(imgObj.id, "gifts", APP_STATE.products.gifts); container.appendChild(giftsSection); imgObj.element.parentNode.insertBefore(container, imgObj.element.nextSibling); }); } function createSidebar() { let sidebar = document.getElementById(`${CSS_PREFIX}sidebar`); if (sidebar) return sidebar; sidebar = document.createElement("aside"); sidebar.id = `${CSS_PREFIX}sidebar`; sidebar.className = `${CSS_PREFIX}sidebar`; sidebar.classList.add( SETTINGS.sidebarPosition === "left" ? `${CSS_PREFIX}sidebar-left` : `${CSS_PREFIX}sidebar-right` ); const heading = document.createElement("h2"); heading.textContent = TEXTS.summary; sidebar.appendChild(heading); const digitalPackageInfo = document.createElement("div"); digitalPackageInfo.className = `${CSS_PREFIX}digital-package-info`; sidebar.appendChild(digitalPackageInfo); const upsellMessage = document.createElement("div"); upsellMessage.className = `${CSS_PREFIX}upsell-message`; sidebar.appendChild(upsellMessage); const summaryContent = document.createElement("div"); summaryContent.id = `${CSS_PREFIX}summary-content`; sidebar.appendChild(summaryContent); const priceBreakdown = document.createElement("div"); priceBreakdown.className = `${CSS_PREFIX}price-breakdown`; sidebar.appendChild(priceBreakdown); const minOrderMsg = document.createElement("div"); minOrderMsg.className = `${CSS_PREFIX}min-order-msg`; sidebar.appendChild(minOrderMsg); createCustomerForm(sidebar); const galleryContainer = detectGalleryContainer(); if (galleryContainer && galleryContainer.parentNode) { galleryContainer.parentNode.insertBefore(sidebar, galleryContainer.nextSibling); } else { document.body.appendChild(sidebar); } return sidebar; } function createInputField(labelText,type,required,name){ const wrapper = document.createElement('div'); wrapper.className = `${CSS_PREFIX}form-field`; const label = document.createElement('label'); label.textContent = labelText; label.htmlFor = `${CSS_PREFIX}${name}`; wrapper.appendChild(label); const input = document.createElement('input'); input.type = type; input.id = `${CSS_PREFIX}${name}`; input.name = name; if(required) input.required = true; wrapper.appendChild(input); return wrapper; } function createCustomerForm(sidebar){ if(!sidebar.querySelector(`.${CSS_PREFIX}customer-form`)){ const form = document.createElement('form'); form.className = `${CSS_PREFIX}customer-form`; form.setAttribute('novalidate',''); const title = document.createElement('h3'); title.textContent = TEXTS.customerInfoTitle; form.appendChild(title); form.appendChild(createInputField(TEXTS.nameLabel, 'text', true, 'name')); form.appendChild(createInputField(TEXTS.surnameLabel, 'text', true, 'surname')); form.appendChild(createInputField(TEXTS.emailLabel, 'email', true, 'email')); form.appendChild(createInputField(TEXTS.phoneLabel, 'tel', false, 'phone')); const notesWrapper = document.createElement('div'); notesWrapper.className = `${CSS_PREFIX}form-field`; const notesLabel = document.createElement('label'); notesLabel.textContent = TEXTS.notesLabel; notesLabel.htmlFor = `${CSS_PREFIX}notes`; notesWrapper.appendChild(notesLabel); const notesTextarea = document.createElement('textarea'); notesTextarea.id = `${CSS_PREFIX}notes`; notesTextarea.name = 'notes'; notesTextarea.rows = 3; notesWrapper.appendChild(notesTextarea); form.appendChild(notesWrapper); const submitBtn = document.createElement('button'); submitBtn.type = 'submit'; submitBtn.textContent = TEXTS.submitBtn; submitBtn.className = `${CSS_PREFIX}submit-btn`; form.appendChild(submitBtn); const messageDiv = document.createElement('div'); messageDiv.className = `${CSS_PREFIX}form-message`; form.appendChild(messageDiv); form.addEventListener('submit', handleFormSubmission); sidebar.appendChild(form); } } function handleFormSubmission(event){ const form = event.target; const msgDiv = form.querySelector(`.${CSS_PREFIX}form-message`); if(!msgDiv) return; msgDiv.textContent = ''; if(!validateForm(form,msgDiv)) return; const orderObj = generateOrderObject(form); if(!orderObj) return; const emailHtml = generateOrderEmail(orderObj); sendOrder(orderObj,emailHtml,msgDiv,form); } function validateForm(form,messageDiv){ const name=form.querySelector(`#${CSS_PREFIX}name`).value.trim(); const surname=form.querySelector(`#${CSS_PREFIX}surname`).value.trim(); const email=form.querySelector(`#${CSS_PREFIX}email`).value.trim(); if(!name||!surname||!email){ messageDiv.textContent=TEXTS.requiredFieldMessage; return false; } const emailPattern=/^[^\s@]+@[^\s@]+\.[^\s@]+$/; if(!emailPattern.test(email)){ messageDiv.textContent="Παρακαλώ εισάγετε έγκυρη διεύθυνση email."; return false; } return true; } function sendOrder(orderObj,emailHtml,msgDiv,form){ if(typeof window.fetch !== 'function'){ msgDiv.style.color = "red"; msgDiv.textContent = "Το περιβάλλον δεν υποστηρίζει την αποστολή της παραγγελίας."; return; } const payload = new FormData(); payload.append("action", "eyedream_submit_order"); payload.append("order_data", JSON.stringify(orderObj)); payload.append("email_html", emailHtml); payload.append("customer", JSON.stringify(orderObj.customer)); payload.append("order_id", orderObj.id); payload.append("nonce", window.EyeDreamAjaxNonce || ""); fetch(window.EyeDreamAjaxURL || "/wp-admin/admin-ajax.php", { method: "POST", credentials: "same-origin", body: payload, }) .then(response=>{ if(!response.ok) throw new Error("Network response was not ok"); return response.json(); }) .then(data=>{ if(data.success){ msgDiv.style.color = "green"; msgDiv.textContent = TEXTS.orderSuccessMessage; form.reset(); } else { throw new Error(data.data || TEXTS.orderFailureMessage); } }) .catch(error=>{ msgDiv.style.color = "red"; msgDiv.textContent = error.message || TEXTS.orderFailureMessage; }); }