`;
content.appendChild(componentHeader);
// Find all unique text content in the component
const processedTexts = new Set();
const textElements = [];
// Get all elements with text content
const allElements = component.querySelectorAll('*:not(script):not(style):not(.component-toolbar)');
allElements.forEach(element => {
// Skip if it's a container with child elements (to avoid duplication)
if (element.children.length > 0) {
// Check if it has direct text nodes
let hasDirectText = false;
for (let node of element.childNodes) {
if (node.nodeType === Node.TEXT_NODE && node.textContent.trim()) {
hasDirectText = true;
break;
}
}
if (!hasDirectText) return;
}
// Get the text content
let textContent = '';
for (let node of element.childNodes) {
if (node.nodeType === Node.TEXT_NODE) {
textContent += node.textContent;
}
}
textContent = textContent.trim();
// Skip empty or already processed texts
if (!textContent || textContent === 'სურათის განახლება' || processedTexts.has(textContent)) {
return;
}
processedTexts.add(textContent);
// Generate a stable ID based on component and text
const stableId = 'trans_' + componentId + '_' + compIndex + '_' +
textContent.substring(0, 20).replace(/[^a-zA-Z0-9]/g, '') + '_' +
textElements.length;
element.dataset.translationId = stableId;
textElements.push({
element: element,
text: textContent,
id: stableId,
tagName: element.tagName.toLowerCase()
});
});
// Create translation items
textElements.forEach((item, index) => {
const translationItem = document.createElement('div');
translationItem.className = 'translation-item';
const elementType = getGeorgianTagName(item.tagName);
translationItem.innerHTML = `
${escapeHtml(item.text)}
`;
content.appendChild(translationItem);
});
});
console.log('Translation panel populated with', document.querySelectorAll('.translation-input').length, 'items');
}
// Add helper function for HTML escaping
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function translateAllWithAI() {
const btn = document.querySelector('.translation-ai-btn');
const originalHTML = btn.innerHTML;
// Check if button is already disabled due to no credits
if (btn.dataset.noCredits === 'true') {
showNotification('AI კრედიტების ლიმიტი ამოწურულია!', true);
return;
}
// Disable button and show loading
btn.disabled = true;
btn.classList.add('loading');
btn.innerHTML = ' თარგმანი მიმდინარეობს...';
// Collect all Georgian texts that need translation
const textsToTranslate = {};
const translationInputs = document.querySelectorAll('.translation-input');
let emptyInputs = [];
translationInputs.forEach(input => {
const translationId = input.dataset.translationId;
const translationItem = input.closest('.translation-item');
const originalText = translationItem.querySelector('.translation-original').textContent.trim();
// Only translate if input is empty and original text exists
if (!input.value && originalText) {
textsToTranslate[translationId] = originalText;
emptyInputs.push(input);
translationItem.classList.add('ai-translating');
}
});
const totalTexts = Object.keys(textsToTranslate).length;
if (totalTexts === 0) {
showNotification('არ არის ტექსტები თარგმნისთვის', true);
btn.disabled = false;
btn.classList.remove('loading');
btn.innerHTML = originalHTML;
return;
}
console.log(`Found ${totalTexts} texts to translate`);
// Process in batches of 5
const batchSize = 5;
const entries = Object.entries(textsToTranslate);
let processedCount = 0;
async function processBatch(startIndex) {
const batch = entries.slice(startIndex, startIndex + batchSize);
if (batch.length === 0) {
// All done
btn.disabled = false;
btn.classList.remove('loading');
btn.innerHTML = originalHTML;
showNotification(`✨ თარგმანი დასრულდა! (${processedCount} ტექსტი)`, false);
// Auto-save after 1 second
setTimeout(() => {
saveTranslations();
}, 1000);
return;
}
// Create batch object
const batchTexts = {};
batch.forEach(([id, text]) => {
batchTexts[id] = text;
});
// Update progress
btn.innerHTML = ` თარგმანი... ${processedCount}/${totalTexts}`;
console.log('Processing batch:', batchTexts);
// Make API call for this batch
const formData = new FormData();
formData.append('action', 'translate_with_ai');
formData.append('page_id', PAGE_ID);
formData.append('texts', JSON.stringify(batchTexts));
try {
const response = await fetch(SAVE_URL, {
method: 'POST',
body: formData
});
const responseText = await response.text();
console.log('AI translation response:', responseText);
let data;
try {
data = JSON.parse(responseText);
} catch (e) {
console.error('Failed to parse JSON:', responseText);
throw new Error('Invalid response from server');
}
if (data.success && data.translations) {
console.log('Translations received:', data.translations);
// Update AI credit count if provided
if (data.remaining !== undefined) {
updateAIButtonCount(data.remaining);
showNotification(data.message || 'თარგმანი დასრულდა!');
}
// Fill in the translations
Object.entries(data.translations).forEach(([translationId, translatedText]) => {
console.log(`Looking for input with ID: ${translationId}`);
// Find the input by data-translation-id
const input = document.querySelector(`.translation-input[data-translation-id="${translationId}"]`);
if (input) {
input.value = translatedText;
const translationItem = input.closest('.translation-item');
if (translationItem) {
translationItem.classList.remove('ai-translating');
translationItem.classList.add('ai-translated');
}
processedCount++;
console.log(`Successfully set translation for ${translationId}`);
// Store in currentTranslations for immediate access
currentTranslations[translationId] = translatedText;
} else {
console.warn(`Could not find input for translation ID: ${translationId}`);
}
});
// Process next batch after a small delay
setTimeout(() => {
processBatch(startIndex + batchSize);
}, 1000);
} else {
throw new Error(data.message || 'Translation failed');
}
} catch (error) {
console.error('AI Translation Error:', error);
// Check if it's a limit error
if (error.message && error.message.includes('ლიმიტი ამოწურულია')) {
// Disable the button permanently for this session
btn.dataset.noCredits = 'true';
btn.disabled = true;
btn.classList.remove('loading');
btn.innerHTML = ' კრედიტები ამოწურულია';
btn.style.opacity = '0.6';
btn.style.cursor = 'not-allowed';
showNotification(error.message, true);
return; // Stop all processing
}
// Remove loading states from failed batch - with null checks
batch.forEach(([id, text]) => {
// Try multiple ways to find the input
let input = document.querySelector(`[data-translation-id="${id}"]`);
if (!input) {
const allInputs = document.querySelectorAll('.translation-input');
allInputs.forEach(inp => {
if (inp.dataset.translationId === id) {
input = inp;
}
});
}
if (input) {
const translationItem = input.closest('.translation-item');
if (translationItem) {
translationItem.classList.remove('ai-translating');
}
}
});
// Show error but continue with next batch
showNotification(`❌ შეცდომა: ${error.message}`, true);
// Try next batch after delay
setTimeout(() => {
processBatch(startIndex + batchSize);
}, 2000);
}
}
// Start processing
processBatch(0);
}
function saveTranslations() {
const inputs = document.querySelectorAll('.translation-input');
const translations = {};
const textMappings = [];
inputs.forEach(input => {
const translationId = input.dataset.translationId;
const originalText = input.dataset.textContent;
const translation = input.value.trim();
if (translation) {
translations[translationId] = translation;
textMappings.push({
id: translationId,
original: originalText,
translation: translation
});
}
});
console.log('Saving translations:', translations);
// Save translations to database
const formData = new FormData();
formData.append('action', 'save_translations');
formData.append('page_id', PAGE_ID);
formData.append('translations', JSON.stringify(translations));
formData.append('mappings', JSON.stringify(textMappings));
// Show saving state
const saveBtn = document.querySelector('.translation-save-btn');
const originalHTML = saveBtn.innerHTML;
saveBtn.innerHTML = ' ინახება...';
saveBtn.disabled = true;
fetch(SAVE_URL, {
method: 'POST',
body: formData
})
.then(response => response.text())
.then(text => {
console.log('Raw response:', text);
try {
return JSON.parse(text);
} catch (e) {
console.error('JSON parse error:', e);
throw new Error('Invalid response: ' + text);
}
})
.then(data => {
if (data.success) {
currentTranslations = translations;
showNotification('თარგმანები წარმატებით შეინახა!');
console.log('Saved successfully, count:', data.count);
} else {
throw new Error(data.message || 'Save failed');
}
})
.catch(error => {
console.error('Error saving translations:', error);
showNotification('შეცდომა: ' + error.message, true);
})
.finally(() => {
saveBtn.innerHTML = originalHTML;
saveBtn.disabled = false;
});
}
function loadTranslations() {
// Load existing translations
fetch(SAVE_URL + '&action=get_translations&page_id=' + PAGE_ID)
.then(response => response.text())
.then(text => {
console.log('Load translations raw response:', text);
try {
return JSON.parse(text);
} catch (e) {
console.error('JSON parse error:', e);
return { success: false, translations: {} };
}
})
.then(data => {
if (data.success) {
currentTranslations = data.translations || {};
console.log('Loaded translations:', currentTranslations);
}
})
.catch(error => {
console.error('Error loading translations:', error);
});
}
// Component Category Functions
function toggleCategory(categoryName) {
// Always show popup immediately, no dropdown behavior
showComponentPopup(categoryName);
}
function showComponentPopup(category) {
const modal = document.getElementById('minimal-component-picker');
const body = document.getElementById('minimal-picker-body');
const title = document.getElementById('minimal-picker-heading');
// Set title
title.textContent = `${category.charAt(0).toUpperCase() + category.slice(1)} კომპონენტები`;
// Clear body
body.innerHTML = '';
// Get components for this category
const categoryComponents = availableComponents.filter(comp => comp.type.toLowerCase() === category.toLowerCase());
// Create minimalist grid
const grid = document.createElement('div');
grid.className = 'minimal-component-grid';
categoryComponents.forEach(component => {
const card = document.createElement('div');
card.className = 'minimal-component-card';
card.onclick = () => addComponentFromPicker(component.id, component.name, component.type);
card.innerHTML = `
`;
grid.appendChild(card);
});
body.appendChild(grid);
}
function closeComponentPicker() {
document.getElementById('minimal-component-picker').classList.remove('open');
document.body.style.overflow = '';
}
function editCustomComponent(componentId) {
// Close the picker
closeComponentPicker();
// Get component data
fetch(`${SAVE_URL}&action=get_component&component_id=${componentId}`)
.then(response => response.json())
.then(data => {
if (data.success) {
const component = data.component;
// Fill the form
document.getElementById('edit-component-id').value = component.id;
document.getElementById('edit-component-name').value = component.name;
document.getElementById('edit-description').value = component.description || '';
document.getElementById('edit-html-template').value = component.html_template;
document.getElementById('edit-css-styles').value = component.css_styles || '';
// Open modal
document.getElementById('component-edit-modal').classList.add('open');
} else {
showNotification('კომპონენტის ჩატვირთვა ვერ მოხერხდა', true);
}
})
.catch(error => {
console.error('Error:', error);
showNotification('შეცდომა კომპონენტის ჩატვირთვისას', true);
});
}
// Georgian to Latin transliteration
function georgianToLatin(text) {
const georgianMap = {
'ა': 'a', 'ბ': 'b', 'გ': 'g', 'დ': 'd', 'ე': 'e', 'ვ': 'v', 'ზ': 'z', 'თ': 't',
'ი': 'i', 'კ': 'k', 'ლ': 'l', 'მ': 'm', 'ნ': 'n', 'ო': 'o', 'პ': 'p', 'ჟ': 'zh',
'რ': 'r', 'ს': 's', 'ტ': 't', 'უ': 'u', 'ფ': 'f', 'ქ': 'k', 'ღ': 'gh', 'ყ': 'q',
'შ': 'sh', 'ჩ': 'ch', 'ც': 'ts', 'ძ': 'dz', 'წ': 'ts', 'ჭ': 'ch', 'ხ': 'kh', 'ჯ': 'j', 'ჰ': 'h'
};
return text.split('').map(char => georgianMap[char] || char).join('');
}
// Generate slug from text with Georgian support
function generateSlug(text) {
// First transliterate Georgian to Latin
let slug = georgianToLatin(text);
// Convert to lowercase and replace spaces and special characters
slug = slug.toLowerCase()
.replace(/[^\w\s-]/g, '') // Remove special characters
.replace(/\s+/g, '-') // Replace spaces with -
.replace(/-+/g, '-') // Replace multiple - with single -
.trim() // Trim whitespace from both ends
.replace(/^-+/, '') // Trim - from start
.replace(/-+$/, ''); // Trim - from end
return slug;
}
function deleteCustomComponent(componentId, componentName) {
if (confirm(`ნამდვილად გსურთ "${componentName}" კომპონენტის წაშლა? ეს მოქმედება ვერ გაუქმდება.`)) {
const formData = new FormData();
formData.append('action', 'delete_component');
formData.append('component_id', componentId);
// Show loading state
showNotification('კომპონენტი იშლება...');
fetch(SAVE_URL, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Remove from available components
const index = availableComponents.findIndex(c => c.id === componentId);
if (index > -1) {
availableComponents.splice(index, 1);
}
delete componentsById[componentId];
// Refresh the current view
const currentCategory = document.querySelector('.minimal-picker-title h2').textContent;
if (currentCategory.includes('Custom') || currentCategory.includes('AI')) {
showCategoryComponents(currentCategory.includes('Custom') ? 'custom' : 'ai_generated');
} else {
showAllComponentsInPicker();
}
showNotification('კომპონენტი წარმატებით წაიშალა!');
} else {
throw new Error(data.message || 'კომპონენტის წაშლა ვერ მოხერხდა');
}
})
.catch(error => {
console.error('Component Delete Error:', error);
showNotification('შეცდომა: ' + error.message, true);
});
}
}
// Update the updateComponent function to refresh the picker after save
function updateComponent(event) {
event.preventDefault();
const form = event.target;
const formData = new FormData(form);
formData.append('action', 'edit_component');
const updateBtn = document.getElementById('component-update-btn');
const originalHTML = updateBtn.innerHTML;
const componentId = document.getElementById('edit-component-id').value;
updateBtn.innerHTML = ' ინახება...';
updateBtn.disabled = true;
fetch(SAVE_URL, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Update the component in our local arrays
fetch(`${SAVE_URL}&action=get_component&component_id=${componentId}`)
.then(response => response.json())
.then(componentData => {
if (componentData.success) {
const updatedComponent = componentData.component;
// Update in availableComponents
const index = availableComponents.findIndex(c => c.id === parseInt(componentId));
if (index > -1) {
availableComponents[index] = {
...availableComponents[index],
name: updatedComponent.name,
description: updatedComponent.description,
html_template: updatedComponent.html_template,
css_styles: updatedComponent.css_styles
};
}
// Update in componentsById
if (componentsById[componentId]) {
componentsById[componentId] = {
...componentsById[componentId],
name: updatedComponent.name,
description: updatedComponent.description,
html_template: updatedComponent.html_template,
css_styles: updatedComponent.css_styles
};
}
}
});
closeComponentEditModal();
showNotification('კომპონენტი წარმატებით განახლდა!');
// Reopen the component picker to show updated component
setTimeout(() => {
showAllComponentsInPicker();
}, 500);
} else {
throw new Error(data.message || 'კომპონენტის განახლება ვერ მოხერხდა');
}
})
.catch(error => {
console.error('Component Update Error:', error);
showNotification('შეცდომა: ' + error.message, true);
})
.finally(() => {
updateBtn.innerHTML = originalHTML;
updateBtn.disabled = false;
});
}
function addComponentFromPicker(componentId, componentName, componentType) {
addComponent(componentId, componentName, componentType);
closeComponentPicker();
closeComponentsPopup(); // Close the fullscreen popup if open
showNotification(`${componentName} დაემატა გვერდზე!`);
}
// Enhanced Page Management Functions
function togglePageDropdown() {
const dropdown = document.getElementById('page-dropdown-menu');
const allDropdowns = document.querySelectorAll('.page-dropdown-menu, .cms-dropdown-menu, .scale-dropdown-menu');
// Close other dropdowns
allDropdowns.forEach(d => {
if (d !== dropdown) d.classList.remove('show');
});
dropdown.classList.toggle('show');
// Force to front when showing
if (dropdown.classList.contains('show')) {
dropdown.style.position = 'absolute';
dropdown.style.zIndex = '9999';
// Bring parent to front too
const parent = dropdown.closest('.page-dropdown');
if (parent) parent.style.zIndex = '9998';
}
}
function switchToPage(pageId) {
if (pageId === PAGE_ID) {
togglePageDropdown();
return;
}
if (confirm('Switch to this page? Any unsaved changes will be lost.')) {
window.location.href = `page-builder.php?page_id=${pageId}&lang=${CURRENT_LANGUAGE}`;
}
}
function editPageBuilder(pageId) {
event.stopPropagation();
if (pageId === PAGE_ID) {
togglePageDropdown();
return;
}
if (confirm('Switch to edit this page? Any unsaved changes will be lost.')) {
window.location.href = `page-builder.php?page_id=${pageId}&lang=${CURRENT_LANGUAGE}`;
}
}
function deletePage(pageId, pageTitle, event) {
event.stopPropagation();
if (confirm(`Are you sure you want to delete "${pageTitle}"? This action cannot be undone.`)) {
fetch('../admin/pages.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `action=delete&id=${pageId}`
})
.then(response => response.text())
.then(data => {
if (data.includes('successfully')) {
showNotification('Page deleted successfully!');
location.reload();
} else {
showNotification('Error deleting page', true);
}
})
.catch(error => {
console.error('Error:', error);
showNotification('Error deleting page', true);
});
}
}
function showNewPageModal() {
togglePageDropdown();
document.getElementById('new-page-modal').classList.add('open');
document.getElementById('new-page-title').focus();
}
function closeNewPageModal() {
document.getElementById('new-page-modal').classList.remove('open');
document.getElementById('new-page-form').reset();
}
function createNewPage(event) {
event.preventDefault();
const form = event.target;
const formData = new FormData(form);
formData.append('action', 'add');
formData.append('language', CURRENT_LANGUAGE);
const submitBtn = form.querySelector('button[type="submit"]');
const originalText = submitBtn.textContent;
submitBtn.textContent = 'Creating...';
submitBtn.disabled = true;
fetch('../admin/pages.php', {
method: 'POST',
body: formData
})
.then(response => response.text())
.then(data => {
if (data.includes('successfully')) {
showNotification('გვერდი წარმატებით შეიქმნა!');
setTimeout(() => {
location.reload();
}, 1000);
} else {
throw new Error('Failed to create page');
}
})
.catch(error => {
console.error('Error:', error);
showNotification('Error creating page', true);
})
.finally(() => {
submitBtn.textContent = originalText;
submitBtn.disabled = false;
});
}
// Enhanced Custom Component Functions
function openCustomComponentModal() {
document.getElementById('custom-component-modal').classList.add('open');
document.getElementById('custom-component-name').focus();
}
function closeCustomComponentModal() {
document.getElementById('custom-component-modal').classList.remove('open');
document.getElementById('custom-component-form').reset();
}
function createCustomComponent(event) {
event.preventDefault();
const form = event.target;
const formData = new FormData(form);
formData.append('action', 'create_custom_component');
formData.append('page_id', PAGE_ID);
const createBtn = document.getElementById('custom-create-btn');
const originalHTML = createBtn.innerHTML;
createBtn.innerHTML = ' Creating...';
createBtn.disabled = true;
fetch(SAVE_URL, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
console.log('Custom Component created:', data.component);
// Add to available components
availableComponents.push(data.component);
componentsById[data.component.id] = data.component;
// Add component to canvas immediately
addComponent(data.component.id, data.component.name, data.component.type);
// Close modal and show success
// Close modal and show success
closeCustomComponentModal();
closeComponentPicker(); // Close component picker if open
closeComponentsPopup(); // Close fullscreen popup if open
showNotification('🎉 კომპონენტი შეიქმნა და დაემატა თქვენს გვერდზე!');
} else {
throw new Error(data.message || 'Failed to create component');
}
})
.catch(error => {
console.error('Custom Component Creation Error:', error);
showNotification('❌ Error creating component: ' + error.message, true);
})
.finally(() => {
createBtn.innerHTML = originalHTML;
createBtn.disabled = false;
});
}
// Enhanced Component Edit Functions
function editComponentInBuilder(componentId) {
event.stopPropagation();
// Get component data
fetch(`${SAVE_URL}&action=get_component&component_id=${componentId}`)
.then(response => response.json())
.then(data => {
if (data.success) {
const component = data.component;
// Fill the form
document.getElementById('edit-component-id').value = component.id;
document.getElementById('edit-component-name').value = component.name;
document.getElementById('edit-description').value = component.description || '';
document.getElementById('edit-html-template').value = component.html_template;
document.getElementById('edit-css-styles').value = component.css_styles || '';
// Open modal
document.getElementById('component-edit-modal').classList.add('open');
} else {
showNotification('Error loading component data', true);
}
})
.catch(error => {
console.error('Error:', error);
showNotification('Error loading component data', true);
});
}
function closeComponentEditModal() {
document.getElementById('component-edit-modal').classList.remove('open');
document.getElementById('component-edit-form').reset();
}
function updateComponent(event) {
event.preventDefault();
const form = event.target;
const formData = new FormData(form);
formData.append('action', 'edit_component');
const updateBtn = document.getElementById('component-update-btn');
const originalHTML = updateBtn.innerHTML;
updateBtn.innerHTML = ' Updating...';
updateBtn.disabled = true;
fetch(SAVE_URL, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
closeComponentEditModal();
showNotification('Component updated successfully!');
// Reload page to reflect changes
setTimeout(() => {
location.reload();
}, 1000);
} else {
throw new Error(data.message || 'Failed to update component');
}
})
.catch(error => {
console.error('Component Update Error:', error);
showNotification('❌ Error updating component: ' + error.message, true);
})
.finally(() => {
updateBtn.innerHTML = originalHTML;
updateBtn.disabled = false;
});
}
function deleteComponentFromBuilder(componentId) {
event.stopPropagation();
if (confirm('Are you sure you want to delete this component? This action cannot be undone.')) {
const formData = new FormData();
formData.append('action', 'delete_component');
formData.append('component_id', componentId);
fetch(SAVE_URL, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
showNotification('Component deleted successfully!');
// Reload page to reflect changes
setTimeout(() => {
location.reload();
}, 1000);
} else {
throw new Error(data.message || 'Failed to delete component');
}
})
.catch(error => {
console.error('Component Delete Error:', error);
showNotification('❌ Error deleting component: ' + error.message, true);
});
}
}
// Link type management
// Additional aggressive link blocking after save
document.addEventListener('DOMContentLoaded', function() {
// Function to disable all links in canvas
function disableCanvasLinks() {
const canvas = document.getElementById('canvas');
if (!canvas) return;
// Find all clickable elements
const clickableElements = canvas.querySelectorAll('a, button, [onclick], [data-editable-link]');
clickableElements.forEach(element => {
// Remove onclick attribute
if (element.hasAttribute('onclick')) {
element.setAttribute('data-onclick-backup', element.getAttribute('onclick'));
element.removeAttribute('onclick');
}
// Disable href for links
if (element.tagName === 'A' && element.href && element.href !== '#') {
element.setAttribute('data-href-backup', element.href);
element.href = '#';
}
// Add click blocker
element.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
return false;
}, true);
});
}
// Run immediately
disableCanvasLinks();
// Run after any AJAX operations (like save)
const originalXHROpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function() {
this.addEventListener('load', function() {
setTimeout(disableCanvasLinks, 100);
});
return originalXHROpen.apply(this, arguments);
};
// Also run periodically to catch any dynamic changes
setInterval(disableCanvasLinks, 500);
});
// Override addEventListener for canvas elements
const canvas = document.getElementById('canvas');
if (canvas) {
const originalAddEventListener = Element.prototype.addEventListener;
Element.prototype.addEventListener = function(type, listener, options) {
// If this element is inside canvas and it's a click event
if (canvas.contains(this) && type === 'click') {
// Wrap the listener to prevent default
const wrappedListener = function(e) {
const link = e.target.closest('a, button, [onclick], [data-editable-link]');
if (link) {
e.preventDefault();
e.stopPropagation();
return false;
}
return listener.call(this, e);
};
return originalAddEventListener.call(this, type, wrappedListener, options);
}
return originalAddEventListener.call(this, type, listener, options);
};
}
let currentLinkType = 'custom';
function switchLinkType(type) {
currentLinkType = type;
// Update buttons
const linkTypeBtns = document.querySelectorAll('.link-type-btn');
linkTypeBtns.forEach(btn => {
btn.classList.remove('active');
});
// Find the correct button to activate
if (type === 'custom') {
linkTypeBtns[0].classList.add('active');
} else {
linkTypeBtns[1].classList.add('active');
}
// Show/hide appropriate fields
if (type === 'custom') {
document.getElementById('custom-url-group').style.display = 'block';
document.getElementById('page-selector-group').style.display = 'none';
} else {
document.getElementById('custom-url-group').style.display = 'none';
document.getElementById('page-selector-group').style.display = 'block';
}
}
function updateLinkFromPage() {
const select = document.getElementById('link-page-select');
const selectedOption = select.options[select.selectedIndex];
const url = selectedOption.value;
if (url) {
// Update the hidden URL field
document.getElementById('link-url').value = url;
// Show selected URL
document.getElementById('selected-page-url').innerHTML =
' ' + url;
} else {
document.getElementById('selected-page-url').innerHTML = '';
}
}
// Enhanced Link Editor Functions
function editElementLink() {
if (!selectedElement) {
showNotification('Please select an element first', true);
return;
}
currentEditingLink = selectedElement;
// Fill the form with current values
const url = currentEditingLink.href ||
currentEditingLink.getAttribute('data-editable-link') ||
currentEditingLink.getAttribute('href') || '';
const text = currentEditingLink.textContent || currentEditingLink.innerText || '';
const target = currentEditingLink.target ||
currentEditingLink.getAttribute('data-editable-target') ||
currentEditingLink.getAttribute('target') || '_self';
const rel = currentEditingLink.rel || currentEditingLink.getAttribute('rel') || '';
// Reset to custom URL by default
currentLinkType = 'custom';
document.querySelectorAll('.link-type-btn')[0].classList.add('active');
document.querySelectorAll('.link-type-btn')[1].classList.remove('active');
document.getElementById('custom-url-group').style.display = 'block';
document.getElementById('page-selector-group').style.display = 'none';
// Check if URL matches any page
// Check if URL matches any page
// Check if URL matches any page
const pageSelect = document.getElementById('link-page-select');
let pageFound = false;
// Clean and normalize URLs for comparison
const cleanUrl = url.replace(/\/$/, '').toLowerCase(); // Remove trailing slash and lowercase
for (let i = 0; i < pageSelect.options.length; i++) {
const option = pageSelect.options[i];
if (!option.value) continue; // Skip empty option
const optionUrl = option.value.replace(/\/$/, '').toLowerCase();
const optionSlug = option.getAttribute('data-slug');
// More comprehensive URL matching
const urlMatches =
option.value === url || // Exact match
optionUrl === cleanUrl || // Normalized match
cleanUrl.endsWith('/' + optionSlug) || // Ends with slug
cleanUrl.endsWith(optionSlug) || // Ends with slug no slash
url === '/' + optionSlug || // Relative URL
url === optionSlug; // Just slug
if (urlMatches) {
pageSelect.value = option.value; // Set to original option value
document.getElementById('selected-page-url').innerHTML =
' ' + option.value;
pageFound = true;
break;
}
}
// Restore onclick handlers when needed (for preview mode)
function restoreOnclickHandlers() {
const elementsWithTempOnclick = document.querySelectorAll('[data-temp-onclick]');
elementsWithTempOnclick.forEach(element => {
const onclick = element.dataset.tempOnclick;
if (onclick) {
element.setAttribute('onclick', onclick);
element.removeAttribute('data-temp-onclick');
}
});
}
// If page found, switch to page selector
if (pageFound && url !== '') {
switchLinkType('page');
document.querySelectorAll('.link-type-btn')[1].click();
}
document.getElementById('link-url').value = url;
document.getElementById('link-text').value = text;
document.getElementById('link-target').value = target;
document.getElementById('link-rel').value = rel;
document.getElementById('link-editor-modal').classList.add('open');
}
function closeLinkEditor() {
document.getElementById('link-editor-modal').classList.remove('open');
currentEditingLink = null;
}
function saveLinkChanges() {
if (!currentEditingLink) return;
// Get URL based on current link type
let url = document.getElementById('link-url').value;
if (currentLinkType === 'page') {
const pageSelect = document.getElementById('link-page-select');
if (pageSelect.value) {
url = pageSelect.value;
}
}
const text = document.getElementById('link-text').value;
const target = document.getElementById('link-target').value;
const rel = document.getElementById('link-rel').value;
// Debug log
console.log('Saving link:', {
url: url,
text: text,
target: target,
tagName: currentEditingLink.tagName
});
// If element is not already a link, we need to wrap it or add link attributes
if (currentEditingLink.tagName === 'A') {
currentEditingLink.href = url;
currentEditingLink.target = target;
if (rel) currentEditingLink.rel = rel;
// Force attribute update
currentEditingLink.setAttribute('href', url);
currentEditingLink.setAttribute('data-editable-link', url);
currentEditingLink.setAttribute('data-editable-target', target);
} else {
// For any other element, add data attributes
currentEditingLink.setAttribute('data-editable-link', url);
currentEditingLink.setAttribute('data-editable-target', target);
if (rel) currentEditingLink.setAttribute('rel', rel);
// Add visual indicator
currentEditingLink.style.cursor = 'pointer';
// For buttons, also set onclick
if (currentEditingLink.tagName === 'BUTTON') {
currentEditingLink.setAttribute('onclick', `window.open('${url}', '${target}')`);
}
}
if (text && text.trim() !== '') {
currentEditingLink.textContent = text;
}
// Mark the component as modified
const component = currentEditingLink.closest('.canvas-component');
if (component) {
component.setAttribute('data-modified', 'true');
}
closeLinkEditor();
showNotification('ლინკი წარმატებით განახლდა');
}
function filterPageOptions() {
const searchTerm = document.getElementById('page-search-input').value.toLowerCase();
const select = document.getElementById('link-page-select');
for (let option of select.options) {
if (option.value === '') continue; // Skip placeholder
const text = option.text.toLowerCase();
option.style.display = text.includes(searchTerm) ? '' : 'none';
}
}
// Enhanced Grid Manager Functions
function openGridManager() {
if (!selectedElement) {
showNotification('Please select a grid container first', true);
return;
}
// Find grid container (look for grid items)
const gridContainer = selectedElement.closest('[class*="grid"]') ||
selectedElement.querySelector('[class*="grid"]') ||
selectedElement;
const gridItems = gridContainer.querySelectorAll('[data-grid-item], .grid > *, [class*="grid-cols"] > *');
if (gridItems.length === 0) {
showNotification('არჩეულ ელემენტში გრიდის ელემენტები არ მოიძებნა', true);
return;
}
currentGridComponent = gridContainer;
// Update grid preview
updateGridPreview();
document.getElementById('grid-manager-modal').classList.add('open');
}
function closeGridManager() {
document.getElementById('grid-manager-modal').classList.remove('open');
currentGridComponent = null;
currentGridItemIndex = null;
}
function updateGridPreview() {
if (!currentGridComponent) return;
const gridItems = currentGridComponent.querySelectorAll('[data-grid-item], .grid > *, [class*="grid-cols"] > *');
const count = gridItems.length;
document.getElementById('grid-item-count').textContent = count;
const preview = document.getElementById('grid-preview');
preview.innerHTML = '';
gridItems.forEach((item, index) => {
const previewItem = document.createElement('div');
previewItem.className = 'grid-preview-item';
// Check if item has image
const img = item.querySelector('img');
const imgSrc = img ? img.src : 'https://static.vecteezy.com/system/resources/previews/022/059/000/non_2x/no-image-available-icon-vector.jpg';
previewItem.innerHTML = `
Item ${index + 1}
`;
preview.appendChild(previewItem);
});
}
function selectGridItemImage(index) {
currentGridItemIndex = index;
const fileInput = document.getElementById('gridImageUpload');
fileInput.click();
}
// Grid image upload handler
document.getElementById('gridImageUpload').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file && currentGridComponent && currentGridItemIndex !== null) {
const formData = new FormData();
formData.append('action', 'upload_image');
formData.append('image', file);
formData.append('page_id', PAGE_ID);
fetch(SAVE_URL, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Update the grid item image
const gridItems = currentGridComponent.querySelectorAll('[data-grid-item], .grid > *, [class*="grid-cols"] > *');
const targetItem = gridItems[currentGridItemIndex];
if (targetItem) {
const img = targetItem.querySelector('img');
if (img) {
img.src = data.url;
updateGridPreview();
showNotification('Grid item image updated!');
}
}
} else {
showNotification('Failed to upload image', true);
}
})
.catch(error => {
console.error('Error:', error);
showNotification('Error uploading image', true);
});
}
this.value = '';
});
function addGridItem() {
if (!currentGridComponent) return;
const existingItems = currentGridComponent.querySelectorAll('[data-grid-item], .grid > *, [class*="grid-cols"] > *');
if (existingItems.length === 0) return;
// Clone the first item as template
const template = existingItems[0].cloneNode(true);
// Clear content but keep structure
const textElements = template.querySelectorAll('h1, h2, h3, h4, h5, h6, p, span');
textElements.forEach(el => {
if (el.textContent.trim()) {
el.textContent = 'New Item ' + (existingItems.length + 1);
}
});
// Reset images to placeholder
const images = template.querySelectorAll('img');
images.forEach(img => {
img.src = 'https://via.placeholder.com/300x200';
img.alt = 'New Image';
});
// Add the new item
currentGridComponent.appendChild(template);
// Re-initialize editing for the new item
initializeEditingForElement(template);
updateGridPreview();
showNotification('Grid item added successfully!');
}
function removeGridItem() {
if (!currentGridComponent) return;
const gridItems = currentGridComponent.querySelectorAll('[data-grid-item], .grid > *, [class*="grid-cols"] > *');
if (gridItems.length <= 1) {
showNotification('Cannot remove the last grid item', true);
return;
}
// Remove the last item
const lastItem = gridItems[gridItems.length - 1];
lastItem.remove();
updateGridPreview();
showNotification('Grid item removed successfully!');
}
function removeSpecificGridItem(index) {
if (!currentGridComponent) return;
const gridItems = currentGridComponent.querySelectorAll('[data-grid-item], .grid > *, [class*="grid-cols"] > *');
if (gridItems.length <= 1) {
showNotification('Cannot remove the last grid item', true);
return;
}
if (gridItems[index]) {
gridItems[index].remove();
updateGridPreview();
showNotification('Grid item removed successfully!');
}
}
function applyGridChanges() {
closeGridManager();
showNotification('Grid changes applied successfully!');
}
// Enhanced Layers Management
// Enhanced Layers Management
function refreshLayers() {
const layersList = document.getElementById('style-panel-layers-list') || document.getElementById('layers-list');
if (!layersList) return;
layersList.innerHTML = '';
// Add AI Modify button at the top if component is selected
if (selectedComponent) {
const aiModifyContainer = document.createElement('div');
aiModifyContainer.style.cssText = 'padding: 1rem; border-bottom: 1px solid var(--border-color); background: var(--background-color);';
aiModifyContainer.innerHTML = `
`;
layersList.appendChild(aiModifyContainer);
}
const components = document.querySelectorAll('.canvas-component');
components.forEach((component, index) => {
const componentId = component.dataset.componentId;
const componentData = componentsById[componentId];
const componentName = componentData ? componentData.name : 'Unknown Component';
const layerItem = document.createElement('div');
layerItem.className = 'layer-item';
layerItem.dataset.componentId = component.id;
layerItem.innerHTML = `
${componentName}
`;
layersList.appendChild(layerItem);
});
// Initialize sortable for layers
initializeLayersSortable();
// Update active layer based on current selection
updateActiveLayer();
}
function selectComponentFromLayer(componentId) {
const component = document.getElementById(componentId);
if (component) {
selectComponent(component);
component.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Refresh layers to show AI button
refreshLayers();
}
}
// Initialize sortable for layers
function initializeLayersSortable() {
const layersList = document.getElementById('style-panel-layers-list');
if (layersList) {
new Sortable(layersList, {
animation: 150,
ghostClass: 'layer-ghost',
chosenClass: 'layer-chosen',
dragClass: 'layer-drag',
handle: '.layer-content',
onEnd: function(evt) {
// Reorder components in canvas based on layer order
const canvasContent = document.getElementById('canvas-content');
const components = Array.from(canvasContent.querySelectorAll('.canvas-component'));
const layerItems = Array.from(layersList.querySelectorAll('.layer-item'));
// Clear canvas content
components.forEach(comp => comp.remove());
// Add components back in new order
layerItems.forEach(layerItem => {
const componentId = layerItem.querySelector('.layer-content').getAttribute('onclick').match(/'([^']+)'/)[1];
const component = components.find(c => c.id === componentId);
if (component) {
canvasContent.appendChild(component);
}
});
updateDebugInfo();
}
});
}
}
// Update active layer highlighting
function updateActiveLayer() {
const layerItems = document.querySelectorAll('.layer-item');
layerItems.forEach(item => {
item.classList.remove('active');
const componentId = item.querySelector('.layer-content').getAttribute('onclick').match(/'([^']+)'/)[1];
if (selectedComponent && selectedComponent.id === componentId) {
item.classList.add('active');
}
});
}
// Add scroll synchronization
function syncLayerScroll() {
const canvasContent = document.getElementById('canvas-content');
const layersList = document.getElementById('style-panel-layers-list');
if (!canvasContent || !layersList) return;
// Get all components and their positions
const components = document.querySelectorAll('.canvas-component');
const canvasRect = canvasContent.getBoundingClientRect();
components.forEach(component => {
const rect = component.getBoundingClientRect();
const layerItem = layersList.querySelector(`[data-component-id="${component.id}"]`);
if (layerItem) {
// Check if component is in viewport
if (rect.top >= canvasRect.top && rect.bottom <= canvasRect.bottom) {
layerItem.classList.add('in-viewport');
} else {
layerItem.classList.remove('in-viewport');
}
}
});
}
function moveLayerUp(index) {
const components = document.querySelectorAll('.canvas-component');
if (index > 0) {
const current = components[index];
const previous = components[index - 1];
current.parentNode.insertBefore(current, previous);
refreshLayers();
}
}
function moveLayerDown(index) {
const components = document.querySelectorAll('.canvas-component');
if (index < components.length - 1) {
const current = components[index];
const next = components[index + 1];
current.parentNode.insertBefore(next, current);
refreshLayers();
}
}
function toggleLayerVisibility(componentId) {
const component = document.getElementById(componentId);
if (component) {
component.style.display = component.style.display === 'none' ? '' : 'none';
refreshLayers();
}
}
function deleteLayerComponent(componentId) {
event.stopPropagation();
if (confirm('Are you sure you want to delete this component?')) {
const component = document.getElementById(componentId);
if (component) {
component.remove();
refreshLayers();
updateDebugInfo();
showNotification('Component deleted from layers!');
const canvasContent = document.getElementById('canvas-content');
const dropZone = document.getElementById('drop-zone');
const remainingComponents = canvasContent.querySelectorAll('.canvas-component');
if (remainingComponents.length === 0 && dropZone) {
dropZone.style.display = 'flex';
}
}
}
}
// Refresh layers when components are added
const originalAddComponent = addComponent;
addComponent = function(componentId, componentName, componentType) {
originalAddComponent(componentId, componentName, componentType);
if (document.getElementById('layers-tab-content').classList.contains('active')) {
setTimeout(refreshLayers, 100);
}
};
// Animation preview function
function previewAnimation() {
const animationType = document.getElementById('animation-type').value;
const duration = document.getElementById('animation-duration').value;
const delay = document.getElementById('animation-delay').value;
const previewElement = document.getElementById('animation-preview-element');
// Remove all animation classes
previewElement.className = 'animation-preview-element';
if (animationType) {
// Apply animation with timing
previewElement.style.animationDuration = duration + 's';
previewElement.style.animationDelay = delay + 's';
// Force reflow to restart animation
void previewElement.offsetWidth;
// Add animation class
previewElement.classList.add(animationType);
// Also apply to selected element if exists
if (selectedElement) {
selectedElement.classList.remove('fade-in', 'slide-up', 'slide-down', 'slide-left', 'slide-right', 'zoom-in', 'bounce');
if (animationType) {
selectedElement.style.animationDuration = duration + 's';
selectedElement.style.animationDelay = delay + 's';
selectedElement.classList.add(animationType);
}
}
}
}
// Enhanced Component Management
function duplicateElement() {
if (!selectedElement) {
showNotification('Please select an element first', true);
return;
}
const clone = selectedElement.cloneNode(true);
// Remove any IDs to avoid duplicates
clone.removeAttribute('id');
const elementsWithIds = clone.querySelectorAll('[id]');
elementsWithIds.forEach(el => el.removeAttribute('id'));
// Insert after the original element
selectedElement.parentNode.insertBefore(clone, selectedElement.nextSibling);
// Initialize editing for the cloned element
initializeEditingForElement(clone);
showNotification('ელემენტი დაკოპირდა წარმატებით!');
}
function deleteElement() {
if (!selectedElement) {
showNotification('Please select an element first', true);
return;
}
if (confirm('Are you sure you want to delete this element?')) {
selectedElement.remove();
selectedElement = null;
closeStylePanel();
showNotification('ელემენტი წაიშალა წარმატებით!');
}
}
// AI Component Generation Functions
function openAIModal() {
document.body.classList.add('modal-open');
document.getElementById('ai-modal').classList.add('open');
document.getElementById('ai-component-name').focus();
}
function closeAIModal() {
document.body.classList.remove('modal-open');
document.getElementById('ai-modal').classList.remove('open');
document.getElementById('ai-component-form').reset();
}
function fillExample(element) {
const text = element.textContent;
const colonIndex = text.indexOf(':');
if (colonIndex !== -1) {
const name = text.substring(0, colonIndex).replace('Example: ', '').trim();
const description = text.substring(colonIndex + 1).trim().replace(/"/g, '');
document.getElementById('ai-component-name').value = name;
document.getElementById('ai-description').value = description;
}
}
function generateAIComponent(event) {
event.preventDefault();
const form = event.target;
const formData = new FormData(form);
formData.append('action', 'generate_ai_component');
formData.append('page_id', PAGE_ID);
const generateBtn = document.getElementById('ai-generate-btn');
const originalHTML = generateBtn.innerHTML;
// Show loading state
generateBtn.innerHTML = ' Generating...';
generateBtn.disabled = true;
fetch(SAVE_URL, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
console.log('AI Component generated:', data.component);
// Add to available components
availableComponents.push(data.component);
componentsById[data.component.id] = data.component;
// Add component to canvas immediately
addComponent(data.component.id, data.component.name, data.component.type);
// Close modal and show success
closeAIModal();
closeComponentPicker();
closeComponentsPopup();
// Show notification with remaining count
showNotification(data.message || '🎉 AI კომპონენტი შეიქმნა!');
// Update AI button to show new remaining count
updateAIButtonCount(data.remaining);
} else {
throw new Error(data.message || 'Failed to generate component');
}
})
.catch(error => {
console.error('AI Generation Error:', error);
showNotification('❌ ' + error.message, true);
})
.finally(() => {
// Reset button state
generateBtn.innerHTML = originalHTML;
generateBtn.disabled = false;
});
}
let currentModifyingComponent = null;
function openAIModifyModal() {
if (!selectedComponent) {
showNotification('გთხოვთ აირჩიოთ კომპონენტი მოდიფიცირებისთვის', true);
return;
}
currentModifyingComponent = selectedComponent;
const componentId = selectedComponent.dataset.componentId;
const componentData = componentsById[componentId];
document.getElementById('modify-component-name').textContent = componentData ? componentData.name : 'Unknown Component';
document.getElementById('ai-modify-name').value = (componentData ? componentData.name : 'Component') + ' - Modified';
document.getElementById('ai-modify-modal').classList.add('open');
document.getElementById('ai-modify-instructions').focus();
}
function closeAIModifyModal() {
document.getElementById('ai-modify-modal').classList.remove('open');
document.getElementById('ai-modify-form').reset();
currentModifyingComponent = null;
}
function modifyComponentWithAI(event) {
event.preventDefault();
if (!currentModifyingComponent) {
showNotification('კომპონენტი ვერ მოიძებნა', true);
return;
}
const form = event.target;
const formData = new FormData(form);
// Get current component HTML
const componentClone = currentModifyingComponent.cloneNode(true);
const toolbar = componentClone.querySelector('.component-toolbar');
if (toolbar) toolbar.remove();
const currentHTML = componentClone.querySelector('.component-content')?.innerHTML || componentClone.innerHTML;
formData.append('action', 'generate_ai_component');
formData.append('page_id', PAGE_ID);
formData.append('current_html', currentHTML);
// Modify the description to include modification instructions
const instructions = formData.get('instructions');
const modifiedDescription = `Modify this existing component with these changes: ${instructions}. Current component HTML: ${currentHTML}`;
formData.set('description', modifiedDescription);
const modifyBtn = document.getElementById('ai-modify-btn');
const originalHTML = modifyBtn.innerHTML;
modifyBtn.innerHTML = ' მოდიფიცირება...';
modifyBtn.disabled = true;
fetch(SAVE_URL, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Add to available components
availableComponents.push(data.component);
componentsById[data.component.id] = data.component;
// Add modified component after current one
const newComponent = createComponentElement(data.component, {});
currentModifyingComponent.parentNode.insertBefore(newComponent, currentModifyingComponent.nextSibling);
initializeEditingForElement(newComponent);
// Close modal and show success
closeAIModifyModal();
showNotification(data.message || '🎉 კომპონენტი წარმატებით მოდიფიცირდა!');
// Refresh layers
refreshLayers();
// Update AI button count if provided
if (data.remaining !== undefined) {
updateAIButtonCount(data.remaining);
}
} else {
throw new Error(data.message || 'მოდიფიკაცია ვერ მოხერხდა');
}
})
.catch(error => {
console.error('AI Modification Error:', error);
showNotification('❌ ' + error.message, true);
})
.finally(() => {
modifyBtn.innerHTML = originalHTML;
modifyBtn.disabled = false;
});
}
// Function to update AI button with remaining count
function updateAIButtonCount(remaining) {
const aiCard = document.querySelector('.ai-generator-card');
if (aiCard) {
const existingBadge = aiCard.querySelector('.ai-remaining-badge');
if (existingBadge) {
existingBadge.textContent = remaining > 0 ? `დარჩა: ${remaining}` : 'ლიმიტი ამოწურულია';
existingBadge.style.background = remaining > 0 ? '#10b981' : '#ef4444';
} else {
// Add badge if doesn't exist
const badge = document.createElement('div');
badge.className = 'ai-remaining-badge';
badge.style.cssText = `
position: absolute;
top: 10px;
right: 10px;
background: ${remaining > 0 ? '#10b981' : '#ef4444'};
color: white;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 600;
`;
badge.textContent = remaining > 0 ? `დარჩა: ${remaining}` : 'ლიმიტი ამოწურულია';
aiCard.style.position = 'relative';
aiCard.appendChild(badge);
}
// Disable AI button if no remaining
const aiBtn = aiCard.querySelector('.ai-generator-btn');
if (aiBtn && remaining === 0) {
aiBtn.disabled = true;
aiBtn.style.opacity = '0.6';
aiBtn.style.cursor = 'not-allowed';
aiBtn.onclick = function() {
showNotification('AI კომპონენტების ლიმიტი ამოწურულია! ლიმიტი განახლდება შემდეგ თვეში. ან შეგიძლიათ შეიძინოთ დამატებითი კრედიტები CLEANNET.GE -ის ბილინგიდან', true);
return false;
};
}
}
}
// Header/Footer Management Functions
function addHeaderMenu() {
showNotification('Header menu functionality will be available soon!');
}
function editHeaderStyle() {
showNotification('Header style editor will be available soon!');
}
function addFooterMenu() {
showNotification('Footer menu functionality will be available soon!');
}
function editFooterStyle() {
showNotification('Footer style editor will be available soon!');
}
// Auto-generate slug from title in new page modal
// Auto-generate slug from title in new page modal
document.addEventListener('DOMContentLoaded', function() {
const titleInput = document.getElementById('new-page-title');
const slugInput = document.getElementById('new-page-slug');
if (titleInput && slugInput) {
titleInput.addEventListener('input', function() {
const slug = generateSlug(this.value);
slugInput.value = slug;
});
}
});
// Helper function to strip toolbar and cleanup for saving
function stripToolbar(el) {
const clone = el.cloneNode(true);
const tb = clone.querySelector('.component-toolbar');
if (tb) tb.remove();
// Remove builder-specific classes and attributes
const editables = clone.querySelectorAll('.editable, .editable-element');
editables.forEach(editable => {
editable.classList.remove('editable', 'editable-element', 'editing', 'selected-element');
editable.removeAttribute('contenteditable');
});
// Remove any element type indicators
const indicators = clone.querySelectorAll('.element-type-indicator');
indicators.forEach(indicator => indicator.remove());
// Clean up image wrappers
const imageWrappers = clone.querySelectorAll('.editable-image');
imageWrappers.forEach(wrapper => {
const img = wrapper.querySelector('img');
const overlay = wrapper.querySelector('.image-upload-overlay');
const controls = wrapper.querySelector('.image-controls');
if (overlay) overlay.remove();
if (controls) controls.remove();
// Preserve link data attributes
// Preserve link data attributes and remove default link styling
// Preserve link data attributes and ensure they work
const links = clone.querySelectorAll('[data-editable-link], a[href]');
links.forEach(link => {
const url = link.getAttribute('data-editable-link') || link.getAttribute('href') || '';
const target = link.getAttribute('data-editable-target') || link.getAttribute('target') || '_self';
if (url && url !== '#') {
if (link.tagName === 'A') {
// Ensure anchor tags have proper href
link.href = url;
link.target = target;
link.removeAttribute('data-editable-link'); // Clean up builder attributes
link.removeAttribute('data-editable-target');
// Remove default link styling
link.style.setProperty('text-decoration', 'none', 'important');
link.style.setProperty('color', 'inherit', 'important');
} else {
// For buttons and other elements
link.setAttribute('onclick', `window.open('${url}', '${target}'); return false;`);
link.style.cursor = 'pointer';
// Keep data attributes for future editing
link.setAttribute('data-editable-link', url);
link.setAttribute('data-editable-target', target);
}
}
});
if (img && wrapper.parentNode) {
wrapper.parentNode.replaceChild(img, wrapper);
}
});
// Remove animation preview classes but keep saved animations
const animatedElements = clone.querySelectorAll('[class*="fade-"], [class*="slide-"], [class*="zoom-"], [class*="bounce"]');
// Preserve button hover styles
// Preserve button hover styles
const buttons = clone.querySelectorAll('button, a, .btn, [data-editable-link]');
buttons.forEach(btn => {
if (btn.dataset.hoverBg || btn.dataset.hoverText) {
const btnId = btn.id || 'btn-' + Date.now();
btn.id = btnId;
// Create hover styles that will work after save
const hoverBg = btn.dataset.hoverBg;
const hoverText = btn.dataset.hoverText;
const originalBg = btn.dataset.originalBg || btn.style.backgroundColor;
const originalColor = btn.dataset.originalColor || btn.style.color;
// Add inline styles for hover effect
btn.style.transition = 'all 0.2s ease';
btn.setAttribute('onmouseenter', `
this.style.backgroundColor='${hoverBg}';
this.style.color='${hoverText}';
this.style.transform='translateY(-1px)';
this.style.boxShadow='0 4px 12px rgba(0,0,0,0.15)';
`);
btn.setAttribute('onmouseleave', `
this.style.backgroundColor='${originalBg}';
this.style.color='${originalColor}';
this.style.transform='';
this.style.boxShadow='';
`);
}
});
animatedElements.forEach(el => {
// Keep the animation class if it was intentionally added
const animationClass = Array.from(el.classList).find(cls =>
['fade-in', 'slide-up', 'slide-down', 'slide-left', 'slide-right', 'zoom-in', 'bounce'].includes(cls)
);
if (animationClass && el.style.animationDuration) {
// Keep the animation
} else {
// Remove preview animations
el.classList.remove('fade-in', 'slide-up', 'slide-down', 'slide-left', 'slide-right', 'zoom-in', 'bounce');
}
});
return clone.innerHTML.trim();
}
// Initialize page builder
document.addEventListener('DOMContentLoaded', function() {
console.log('Enhanced Page Builder Initializing...');
console.log('Available Components:', availableComponents.length);
console.log('Existing Components:', existingComponents.length);
console.log('All Pages:', allPages.length);
console.log('Current Language:', CURRENT_LANGUAGE);
console.log('Is Translated Page:', IS_TRANSLATED_PAGE);
initializeDragAndDrop();
initializeSortable();
initializeStyleControls();
initializeAdvancedControls();
// Load existing components
loadExistingComponents();
loadTranslations();
// Adjust canvas scale after page load
setTimeout(() => {
adjustCanvasScale();
}, 100);
// Adjust canvas on window resize
window.addEventListener('resize', () => {
adjustCanvasScale();
});
// Add scroll event for layer sync
const canvasWrapper = document.querySelector('.canvas-wrapper');
if (canvasWrapper) {
canvasWrapper.addEventListener('scroll', syncLayerScroll);
}
// Close dropdowns when clicking outside
document.addEventListener('click', function(e) {
// Close page dropdown
if (!e.target.closest('.page-dropdown')) {
const dropdown = document.getElementById('page-dropdown-menu');
if (dropdown) dropdown.classList.remove('show');
}
// Close language dropdown
// Close translation panel when clicking outside
if (!e.target.closest('.translation-panel') && !e.target.closest('.translate-btn')) {
const panel = document.getElementById('translation-panel');
if (panel && panel.classList.contains('open')) {
closeTranslationPanel();
}
}
// Close modals when clicking backdrop
if (e.target.classList.contains('ai-modal-backdrop')) {
closeAIModal();
}
if (e.target.classList.contains('new-page-backdrop')) {
closeNewPageModal();
}
if (e.target.classList.contains('custom-component-backdrop')) {
closeCustomComponentModal();
}
if (e.target.classList.contains('component-edit-backdrop')) {
closeComponentEditModal();
}
if (e.target.classList.contains('link-editor-backdrop')) {
closeLinkEditor();
}
if (e.target.classList.contains('grid-manager-backdrop')) {
closeGridManager();
}
if (e.target.classList.contains('minimal-picker-backdrop')) {
closeComponentPicker();
}
if (e.target.classList.contains('component-editor-backdrop')) {
closeComponentEditor();
}
});
// Close modals with Escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
if (document.getElementById('ai-modal').classList.contains('open')) {
closeAIModal();
}
if (document.getElementById('ai-modify-modal').classList.contains('open')) {
closeAIModifyModal();
}
if (document.getElementById('new-page-modal').classList.contains('open')) {
closeNewPageModal();
}
if (document.getElementById('custom-component-modal').classList.contains('open')) {
closeCustomComponentModal();
}
if (document.getElementById('component-edit-modal').classList.contains('open')) {
closeComponentEditModal();
}
if (document.getElementById('link-editor-modal').classList.contains('open')) {
closeLinkEditor();
}
if (document.getElementById('grid-manager-modal').classList.contains('open')) {
closeGridManager();
}
if (document.getElementById('page-dropdown-menu').classList.contains('show')) {
togglePageDropdown();
}
if (document.getElementById('translation-panel').classList.contains('open')) {
closeTranslationPanel();
}
if (document.getElementById('component-editor-modal').classList.contains('open')) {
closeComponentEditor();
}
if (document.getElementById('minimal-component-picker').classList.contains('open')) {
closeComponentPicker();
}
}
});
updateDebugInfo();
});
// Debug functions
function toggleDebug() {
debugMode = !debugMode;
const debugDiv = document.getElementById('debug-info');
debugDiv.style.display = debugMode ? 'block' : 'none';
if (debugMode) {
updateDebugInfo();
}
}
function updateDebugInfo() {
if (!debugMode) return;
const debugDiv = document.getElementById('debug-info');
const componentCount = document.querySelectorAll('.canvas-component').length;
const existingCount = existingComponents.length;
const editableCount = document.querySelectorAll('.editable-element').length;
const imageCount = document.querySelectorAll('.editable-image').length;
debugDiv.innerHTML = `
Debug Info:
Page ID: ${PAGE_ID}
Language: ${CURRENT_LANGUAGE}
Is Translation: ${IS_TRANSLATED_PAGE}
Existing Components: ${existingCount}
Current Components: ${componentCount}
Editable Elements: ${editableCount}
Editable Images: ${imageCount}
Selected Element: ${selectedElement ? selectedElement.tagName : 'None'}
Canvas Scale: ${Math.round(canvasScale * 100)}%
Sidebar: ${sidebarOpen ? 'Open' : 'Closed'}
Last Update: ${new Date().toLocaleTimeString()}
`;
}
// Load existing components into the builder
function loadExistingComponents() {
console.log('Loading existing components...');
const canvasContent = document.getElementById('canvas-content');
const dropZone = document.getElementById('drop-zone');
if (existingComponents.length > 0) {
console.log('Found', existingComponents.length, 'existing components to load');
if (dropZone) {
dropZone.style.display = 'none';
}
const existingComps = canvasContent.querySelectorAll('.canvas-component');
existingComps.forEach(comp => comp.remove());
existingComponents.forEach((compData, index) => {
console.log(`Loading component ${index}:`, compData);
const componentElement = createComponentElement(compData, compData.settings, index);
canvasContent.appendChild(componentElement);
if (compData.saved_content) {
const contentDiv = componentElement.querySelector('.component-content, [data-component-content]');
if (contentDiv) {
contentDiv.innerHTML = compData.saved_content;
}
}
});
setTimeout(() => {
initializeAllEditing();
console.log('Finished loading existing components');
}, 100);
} else {
console.log('No existing components found');
if (dropZone) {
dropZone.style.display = 'flex';
}
}
updateDebugInfo();
}
// Create component element with proper settings
function createComponentElement(componentData, settings, index) {
console.log('Creating component element:', componentData.name, 'with settings:', settings);
const componentElement = document.createElement('div');
componentElement.className = 'canvas-component';
if (componentData.style_scope) {
componentElement.classList.add(componentData.style_scope);
}
componentElement.dataset.componentId = componentData.id;
componentElement.dataset.componentType = componentData.type;
componentElement.id = 'component-' + (index !== undefined ? index : Date.now());
// Render component HTML with current settings
const renderedHTML = renderComponentHTML(componentData.html_template, settings || {});
// Create a unique scope class for this component
const scopeClass = 'component-scope-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
componentElement.innerHTML = `
${renderedHTML}
`;
// Add scoped styles if component has custom CSS
if (componentData.css_styles && componentData.css_styles.trim()) {
const styleElement = document.createElement('style');
styleElement.textContent = `.${scopeClass} { ${componentData.css_styles} }`;
componentElement.appendChild(styleElement);
}
return componentElement;
}
// Show notification
function showNotification(message, isError = false) {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.className = 'notification' + (isError ? ' error' : '');
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
}, 3000);
}
// Initialize drag and drop
function initializeDragAndDrop() {
const componentItems = document.querySelectorAll('.component-card[draggable="true"]');
const canvasContent = document.getElementById('canvas-content');
const dropZone = document.getElementById('drop-zone');
// Component drag events
componentItems.forEach(item => {
item.addEventListener('dragstart', function(e) {
e.dataTransfer.setData('component-id', this.dataset.componentId);
e.dataTransfer.setData('component-name', this.dataset.componentName);
e.dataTransfer.setData('component-type', this.dataset.componentType);
e.dataTransfer.setData('item-type', 'component');
});
});
// Canvas drop events
[canvasContent, dropZone].forEach(element => {
if (element) {
element.addEventListener('dragover', function(e) {
e.preventDefault();
if (this === dropZone) {
this.classList.add('drag-over');
}
});
element.addEventListener('dragleave', function(e) {
if (this === dropZone) {
this.classList.remove('drag-over');
}
});
element.addEventListener('drop', function(e) {
e.preventDefault();
if (this === dropZone) {
this.classList.remove('drag-over');
}
const itemType = e.dataTransfer.getData('item-type');
if (itemType === 'component') {
const componentId = e.dataTransfer.getData('component-id');
const componentName = e.dataTransfer.getData('component-name');
const componentType = e.dataTransfer.getData('component-type');
addComponent(componentId, componentName, componentType);
}
});
}
});
}
// Initialize sortable for components
function initializeSortable() {
const canvasContent = document.getElementById('canvas-content');
if (canvasContent) {
new Sortable(canvasContent, {
animation: 150,
ghostClass: 'component-ghost',
chosenClass: 'component-chosen',
dragClass: 'component-drag',
filter: '.drop-zone',
onStart: function(evt) {
evt.item.classList.add('dragging');
},
onEnd: function(evt) {
evt.item.classList.remove('dragging');
refreshLayers();
updateDebugInfo();
}
});
}
}
// Add component to canvas
function addComponent(componentId, componentName, componentType) {
const componentData = componentsById[componentId];
if (!componentData) {
console.error('Component data not found for ID:', componentId);
return;
}
console.log('Adding new component:', componentName);
const canvasContent = document.getElementById('canvas-content');
const dropZone = document.getElementById('drop-zone');
if (dropZone) {
dropZone.style.display = 'none';
}
// Generate default settings
const defaultSettings = {};
componentData.settings_fields.forEach(field => {
defaultSettings[field.name] = field.default || '';
});
// Create component element
const componentElement = createComponentElement(componentData, defaultSettings);
componentElement.classList.add('component-enter');
canvasContent.appendChild(componentElement);
// Initialize editing for new component
initializeEditingForElement(componentElement);
// Auto-scroll to new component
setTimeout(() => {
componentElement.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'nearest'
});
// Add highlight effect
componentElement.style.outline = '3px solid var(--success-color)';
componentElement.style.outlineOffset = '4px';
setTimeout(() => {
componentElement.style.outline = '';
componentElement.style.outlineOffset = '';
}, 2000);
}, 100);
// Remove animation class after animation
setTimeout(() => {
componentElement.classList.remove('component-enter');
}, 300);
refreshLayers();
updateDebugInfo();
}
function createIsolatedComponentElement(componentData, settings, index) {
const componentElement = document.createElement('div');
componentElement.className = 'canvas-component';
componentElement.dataset.componentId = componentData.id;
componentElement.dataset.componentType = componentData.type;
componentElement.id = 'component-' + (index !== undefined ? index : Date.now());
// Create shadow root for complete isolation
const shadowRoot = componentElement.attachShadow({ mode: 'open' });
// Add Tailwind CSS to shadow DOM
const tailwindLink = document.createElement('link');
tailwindLink.rel = 'stylesheet';
tailwindLink.href = 'https://cdn.tailwindcss.com/3.3.0/tailwind.min.css';
shadowRoot.appendChild(tailwindLink);
// Add component styles
if (componentData.css_styles) {
const styleElement = document.createElement('style');
styleElement.textContent = componentData.css_styles;
shadowRoot.appendChild(styleElement);
}
// Add component HTML
const contentDiv = document.createElement('div');
contentDiv.className = 'component-content';
contentDiv.innerHTML = renderComponentHTML(componentData.html_template, settings || {});
shadowRoot.appendChild(contentDiv);
return componentElement;
}
// Enhanced initialization for all editing features
function initializeAllEditing() {
console.log('Initializing all editing features...');
const components = document.querySelectorAll('.canvas-component');
console.log('Found', components.length, 'components for editing');
components.forEach(component => {
initializeEditingForElement(component);
});
updateDebugInfo();
}
// Enhanced element editing initialization
function initializeEditingForElement(component) {
console.log('Initializing editing for component:', component.id);
// Initialize all elements that can be styled or edited
const allElements = component.querySelectorAll('*');
allElements.forEach(element => {
// Skip toolbar, already processed elements, and script/style tags
if (element.closest('.component-toolbar') ||
element.classList.contains('editable-element') ||
['script', 'style', 'noscript'].includes(element.tagName.toLowerCase())) {
return;
}
const tagName = element.tagName.toLowerCase();
// Make images editable and uploadable
if (tagName === 'img') {
makeImageEditable(element);
}
// Make text elements editable
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'span', 'a', 'button', 'li', 'td', 'th'].includes(tagName)) {
makeTextEditable(element);
}
// Make all elements styleable (including divs, containers, etc.)
makeElementStyleable(element);
});
}
// Make image elements editable and uploadable
// Make image elements editable and uploadable
function makeImageEditable(img) {
if (img.closest('.editable-image')) return; // Already processed
console.log('Making image editable:', img.src);
const wrapper = document.createElement('div');
wrapper.className = 'editable-image';
wrapper.style.position = 'relative';
wrapper.style.display = 'inline-block';
// Insert wrapper
img.parentNode.insertBefore(wrapper, img);
wrapper.appendChild(img);
const overlay = document.createElement('div');
overlay.className = 'image-upload-overlay';
overlay.innerHTML = `
სურათის განახლება
დააჭირეთ სურათის ასარჩევად
`;
wrapper.appendChild(overlay);
// Add click handler to overlay
overlay.onclick = function(e) {
e.preventDefault();
e.stopPropagation();
console.log('Overlay clicked, triggering file input');
selectImageForUpload(img);
return false;
};
// Add click handler to image for selection
img.onclick = function(e) {
e.stopPropagation();
selectElement(img);
selectComponent(wrapper.closest('.canvas-component'));
};
}
// Check image size and adjust overlay
function checkImageSize(wrapper, img) {
const minSize = 100; // Minimum size in pixels
if (img.width < minSize || img.height < minSize) {
wrapper.classList.add('small-image');
} else {
wrapper.classList.remove('small-image');
}
// Update wrapper dimensions
wrapper.style.width = img.width + 'px';
wrapper.style.height = img.height + 'px';
}
// Make text elements editable
function makeTextEditable(element) {
console.log('Making text element editable:', element.tagName, element.textContent?.substring(0, 20));
element.contentEditable = true;
element.classList.add('editable-element');
// Assign translation ID if doesn't exist
if (!element.dataset.translationId) {
element.dataset.translationId = 'trans_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
}
// Store original HTML structure and siblings
element.setAttribute('data-original-tag', element.tagName);
element.setAttribute('data-original-html', element.innerHTML);
// Prevent content from other elements being pulled in
element.addEventListener('input', function(e) {
// Check if this element now contains content from siblings
const parent = this.parentElement;
if (!parent) return;
// Get the next sibling before any modifications
const nextSibling = this.nextElementSibling;
// If Enter was pressed and created a div inside, handle it properly
const innerDivs = this.querySelectorAll('div');
if (innerDivs.length > 0 && (this.tagName === 'H1' || this.tagName === 'H2' || this.tagName === 'H3' || this.tagName === 'H4' || this.tagName === 'H5' || this.tagName === 'H6')) {
e.preventDefault();
// Extract the content from inner divs
let extractedContent = [];
innerDivs.forEach(div => {
extractedContent.push(div.innerHTML);
div.remove();
});
// If there's a next sibling paragraph, prepend the extracted content to it
if (nextSibling && nextSibling.tagName === 'P') {
nextSibling.innerHTML = extractedContent.join(' ') + ' ' + nextSibling.innerHTML;
} else if (extractedContent.length > 0) {
// Create a new paragraph for the extracted content
const newP = document.createElement('p');
newP.className = nextSibling ? nextSibling.className : 'text-base';
newP.innerHTML = extractedContent.join(' ');
newP.contentEditable = true;
newP.classList.add('editable-element');
// Insert after current element
if (nextSibling) {
parent.insertBefore(newP, nextSibling);
} else {
parent.appendChild(newP);
}
// Make the new paragraph editable
makeTextEditable(newP);
}
}
});
element.addEventListener('click', function(e) {
e.stopPropagation();
selectElement(this);
selectComponent(this.closest('.canvas-component'));
});
element.addEventListener('focus', function() {
this.classList.add('editing');
selectElement(this);
selectComponent(this.closest('.canvas-component'));
// Store the current state when focusing
this.setAttribute('data-focus-content', this.innerHTML);
});
element.addEventListener('blur', function() {
this.classList.remove('editing');
});
// Prevent selecting content across elements
element.addEventListener('selectstart', function(e) {
const selection = window.getSelection();
// Restrict selection to current element only
setTimeout(() => {
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const container = range.commonAncestorContainer;
const elementNode = container.nodeType === 3 ? container.parentNode : container;
// If selection extends beyond this element
if (!this.contains(elementNode) || elementNode === this.parentElement) {
// Collapse selection to this element only
const newRange = document.createRange();
newRange.selectNodeContents(this);
selection.removeAllRanges();
selection.addRange(newRange);
}
}
}, 0);
});
// Handle paste events to maintain structure
element.addEventListener('paste', function(e) {
e.preventDefault();
// Get plain text from clipboard
const text = (e.clipboardData || window.clipboardData).getData('text/plain');
// Insert plain text without formatting
const selection = window.getSelection();
if (!selection.rangeCount) return;
selection.deleteFromDocument();
// For headings, don't allow multi-line paste
if (this.tagName.match(/^H[1-6]$/)) {
const singleLine = text.replace(/[\r\n]+/g, ' ').trim();
selection.getRangeAt(0).insertNode(document.createTextNode(singleLine));
} else {
// For other elements, convert line breaks to
const lines = text.split(/[\r\n]+/);
const fragment = document.createDocumentFragment();
lines.forEach((line, index) => {
if (index > 0) {
fragment.appendChild(document.createElement('br'));
}
fragment.appendChild(document.createTextNode(line));
});
selection.getRangeAt(0).insertNode(fragment);
}
});
// Enhanced beforeinput handler
element.addEventListener('beforeinput', function(e) {
// If user is about to delete all content
if (e.inputType === 'deleteContentBackward' || e.inputType === 'deleteContentForward') {
const selection = window.getSelection();
const range = selection.getRangeAt(0);
const allSelected = range.toString() === this.innerText;
if (allSelected) {
e.preventDefault();
// Keep the element structure based on its type
if (this.tagName.match(/^H[1-6]$/)) {
this.innerHTML = ' '; // Non-breaking space for headings
} else {
this.innerHTML = ' ';
}
// Place cursor at the beginning
const newRange = document.createRange();
newRange.selectNodeContents(this);
newRange.collapse(true);
selection.removeAllRanges();
selection.addRange(newRange);
}
}
});
element.addEventListener('keydown', function(e) {
// Handle Enter key based on element type
if (e.key === 'Enter') {
const tagName = this.tagName.toLowerCase();
// For headings, Enter should create a new paragraph below
if (tagName.match(/^h[1-6]$/)) {
e.preventDefault();
// Create a new paragraph
const newP = document.createElement('p');
newP.className = 'text-base mb-4';
newP.innerHTML = ' ';
newP.contentEditable = true;
newP.classList.add('editable-element');
// Insert after current heading
if (this.nextSibling) {
this.parentNode.insertBefore(newP, this.nextSibling);
} else {
this.parentNode.appendChild(newP);
}
// Make it editable and focus it
makeTextEditable(newP);
newP.focus();
// Place cursor at the beginning
const range = document.createRange();
const selection = window.getSelection();
range.selectNodeContents(newP);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
return false;
}
// For paragraphs, divs, etc., allow Enter for line breaks
else if (tagName === 'p' || tagName === 'div' || tagName === 'td' || tagName === 'li') {
// Allow default behavior (creates or
)
return;
}
}
// Prevent Backspace/Delete from removing the element when empty
if ((e.key === 'Backspace' || e.key === 'Delete') && this.innerText.trim() === '') {
e.preventDefault();
if (this.tagName.match(/^H[1-6]$/)) {
this.innerHTML = ' ';
} else {
this.innerHTML = ' ';
}
}
// Prevent backspace at beginning from merging with previous element
if (e.key === 'Backspace') {
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
if (range.collapsed && range.startOffset === 0) {
const container = range.startContainer;
const element = container.nodeType === 3 ? container.parentNode : container;
// If we're at the very beginning of the element
if (element === this || (element.parentNode === this && !element.previousSibling)) {
e.preventDefault();
return false;
}
}
}
}
});
// Prevent link navigation in builder
if (element.tagName.toLowerCase() === 'a' ||
element.hasAttribute('data-editable-link') ||
element.hasAttribute('onclick')) {
element.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
return false;
}, true);
element.addEventListener('mousedown', function(e) {
if (e.button === 1 || e.button === 2) {
e.preventDefault();
e.stopPropagation();
}
}, true);
}
}
// Make any element styleable (divs, containers, etc.)
function makeElementStyleable(element) {
if (element.classList.contains('editable-element')) return; // Already processed
element.classList.add('editable-element');
element.addEventListener('click', function(e) {
e.stopPropagation();
selectElement(this);
selectComponent(this.closest('.canvas-component'));
});
}
// Select individual element
function selectElement(element) {
console.log('Selecting element:', element.tagName, element.className);
// Remove previous selections
document.querySelectorAll('.editable-element.selected-element').forEach(el => {
el.classList.remove('selected-element');
});
// Select new element
element.classList.add('selected-element');
selectedElement = element;
// Update style panel with current values
updateStylePanelValues(element);
updateElementInfo(element);
// Show/hide button styling group
const buttonStylingGroup = document.getElementById('button-styling-group');
const isButton = element.tagName.toLowerCase() === 'button' ||
element.tagName.toLowerCase() === 'a' ||
element.classList.contains('btn') ||
element.hasAttribute('data-editable-link');
if (buttonStylingGroup) {
buttonStylingGroup.style.display = isButton ? 'block' : 'none';
if (isButton) {
updateButtonStyleValues(element);
}
}
// Open style panel if not already open
openStylePanel();
}
function updateElementInfo(element) {
const infoDiv = document.getElementById('element-info');
const tagName = element.tagName.toLowerCase();
let elementType = tagName.toUpperCase();
if (tagName === 'img') elementType += ' (სურათი)';
if (tagName === 'div') elementType += ' (კონტეინერი)';
if (tagName === 'a') elementType += ' (ლინკი)';
if (tagName === 'button') elementType += ' (ღილაკი)';
infoDiv.innerHTML = `