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




































