// Global variables const availableComponents = ; const existingComponents = ; const allPages = ; const PAGE_ID = ; const CURRENT_LANGUAGE = ''; const IS_TRANSLATED_PAGE = ; const componentsById = {}; const SAVE_URL = window.location.origin + window.location.pathname.replace(/\?.*$/, '') + '?page_id=' + PAGE_ID + '&lang=' + CURRENT_LANGUAGE; let selectedComponent = null; let selectedElement = null; let currentEditingLink = null; let currentGridComponent = null; let currentGridItemIndex = null; let debugMode = false; let canvasScale = 1; // Create lookup object availableComponents.forEach(comp => { componentsById[comp.id] = comp; }); // Style Panel Tab Functions // Style Panel Tab Functions function switchStyleTab(tabName) { // Update tab buttons document.querySelectorAll('.style-panel-tab').forEach(tab => { tab.classList.remove('active'); }); // Update tab content document.querySelectorAll('.style-panel-tab-content').forEach(content => { content.classList.remove('active'); }); // Activate selected tab if (tabName === 'style') { document.querySelector('.style-panel-tab:first-child').classList.add('active'); document.getElementById('style-tab-content').classList.add('active'); } else if (tabName === 'layers') { document.querySelector('.style-panel-tab:last-child').classList.add('active'); document.getElementById('layers-tab-content').classList.add('active'); refreshLayers(); // Refresh layers when tab is opened syncLayerScroll(); // Sync scroll position } } // Canvas Layers Panel Functions (removed) function toggleCanvasLayers() { // This function is no longer needed const layersPanel = document.getElementById('canvas-layers-panel'); const floatingToggle = document.getElementById('floating-layers-toggle'); const toggleBtn = layersPanel.querySelector('.layers-toggle-btn i'); const floatingToggleIcon = floatingToggle.querySelector('i'); canvasLayersOpen = !canvasLayersOpen; if (canvasLayersOpen) { layersPanel.classList.add('open'); layersPanel.classList.remove('collapsed'); floatingToggle.classList.remove('show'); } else { layersPanel.classList.remove('open'); layersPanel.classList.add('collapsed'); floatingToggle.classList.add('show'); } // Update toggle button icons if (toggleBtn) { toggleBtn.className = canvasLayersOpen ? 'fas fa-chevron-right' : 'fas fa-chevron-left'; } if (floatingToggleIcon) { floatingToggleIcon.className = 'fas fa-chevron-left'; } // Readjust canvas scale when layers panel toggles setTimeout(() => { adjustCanvasScale(); }, 300); } // Toggle CMS dropdown function toggleCMSDropdown() { const dropdown = document.getElementById('cms-dropdown-menu'); dropdown.classList.toggle('show'); // Ensure it's on top if (dropdown.classList.contains('show')) { dropdown.style.zIndex = '2000'; } } // Close dropdowns when clicking outside document.addEventListener('click', function(e) { // Close CMS dropdown if (!e.target.closest('.cms-dropdown')) { const cmsDropdown = document.getElementById('cms-dropdown-menu'); if (cmsDropdown) cmsDropdown.classList.remove('show'); } // Close page dropdown (existing code) if (!e.target.closest('.page-dropdown')) { const dropdown = document.getElementById('page-dropdown-menu'); if (dropdown) dropdown.classList.remove('show'); } }); // Fullscreen Popup Functions function openComponentsPopup() { showAllComponentsInPicker(); } function closeComponentsPopup() { const popup = document.getElementById('components-fullscreen-popup'); popup.classList.remove('open'); document.body.style.overflow = ''; } // Hidden file input for SEO images document.body.insertAdjacentHTML('beforeend', ''); // SEO Image selection let currentSEOImageType = null; function selectSEOImage(type) { currentSEOImageType = type; document.getElementById('seoImageUpload').click(); } // Toggle scale dropdown // Toggle scale dropdown function toggleScaleDropdown() { const dropdown = document.getElementById('scale-dropdown-menu'); dropdown.classList.toggle('show'); // Ensure it's on top if (dropdown.classList.contains('show')) { dropdown.style.zIndex = '2000'; } } // Updated setCanvasScale function with proper dropdown update function setCanvasScale(scale, isAuto = false) { canvasScale = scale; const canvasLayout = document.getElementById('canvas-layout'); if (scale === 1) { canvasLayout.style.transform = ''; canvasLayout.classList.remove('scaled'); } else { canvasLayout.style.transform = `scale(${scale})`; canvasLayout.classList.add('scaled'); } // Update the dropdown button text const currentScaleSpan = document.getElementById('current-scale'); if (currentScaleSpan) { currentScaleSpan.textContent = Math.round(scale * 100) + '%'; } // Update active state in dropdown menu const scaleValue = Math.round(scale * 100); document.querySelectorAll('.scale-option').forEach(btn => { const btnScale = parseInt(btn.getAttribute('data-scale')); if (btnScale === scaleValue) { btn.classList.add('active'); } else { btn.classList.remove('active'); } }); // Close dropdown after selection (only if not auto-scaling) if (!isAuto) { const dropdown = document.getElementById('scale-dropdown-menu'); if (dropdown) { dropdown.classList.remove('show'); } } } // Close dropdowns when clicking outside document.addEventListener('click', function(e) { // Close scale dropdown if (!e.target.closest('.canvas-scale-dropdown')) { const scaleDropdown = document.getElementById('scale-dropdown-menu'); if (scaleDropdown) scaleDropdown.classList.remove('show'); } // Close CMS dropdown if (!e.target.closest('.cms-dropdown')) { const cmsDropdown = document.getElementById('cms-dropdown-menu'); if (cmsDropdown) cmsDropdown.classList.remove('show'); } // Close page dropdown if (!e.target.closest('.page-dropdown')) { const pageDropdown = document.getElementById('page-dropdown-menu'); if (pageDropdown) pageDropdown.classList.remove('show'); } }); // Initialize with correct scale on page load document.addEventListener('DOMContentLoaded', function() { // Set initial scale display const currentScaleSpan = document.getElementById('current-scale'); if (currentScaleSpan) { currentScaleSpan.textContent = '100%'; } }); // Function to check page limit and disable button if needed function checkPageLimit() { fetch(SAVE_URL + '&action=check_limits') .then(response => response.json()) .then(data => { if (data.success) { const newPageBtn = document.querySelector('.new-page-btn'); const pageDropdownFooter = document.querySelector('.page-dropdown-footer'); if (data.pages_remaining === 0) { // Disable the button if (newPageBtn) { newPageBtn.disabled = true; newPageBtn.style.opacity = '0.5'; newPageBtn.style.cursor = 'not-allowed'; newPageBtn.innerHTML = ' გვერდების ლიმიტი ამოწურულია'; } // Replace button with limit message if (pageDropdownFooter) { pageDropdownFooter.innerHTML = `

გვერდების ლიმიტი ამოწურულია
მაქსიმუმი: ${data.pages_limit} გვერდი

`; } } } }) .catch(error => console.error('Error checking page limit:', error)); } // Also update showNewPageModal to check limit function showNewPageModal() { // Check limit first fetch(SAVE_URL + '&action=check_limits') .then(response => response.json()) .then(data => { if (data.success && data.pages_remaining === 0) { showNotification('გვერდების ლიმიტი ამოწურულია! მაქსიმუმი: ' + data.pages_limit + ' გვერდი', true); return; } // If limit not exceeded, show modal togglePageDropdown(); document.getElementById('new-page-modal').classList.add('open'); document.getElementById('new-page-title').focus(); }); } // Call on page load document.addEventListener('DOMContentLoaded', function() { checkPageLimit(); }); // Handle SEO image upload // Handle SEO image upload document.getElementById('seoImageUpload').addEventListener('change', function(e) { const file = e.target.files[0]; if (file && currentSEOImageType) { // Show preview immediately with local file const reader = new FileReader(); reader.onload = function(e) { const previewId = currentSEOImageType + '-image-preview'; const previewElement = document.getElementById(previewId); if (previewElement) { // Show loading state previewElement.classList.add('loading'); previewElement.innerHTML = ` Uploading...
იტვირთება...
`; } }; reader.readAsDataURL(file); // Upload the file 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) { console.log('Upload successful, URL:', data.url); // Update the hidden input const inputId = currentSEOImageType + '-image-setting'; const inputElement = document.getElementById(inputId); if (inputElement) { inputElement.value = data.url; } // Update the preview with the uploaded image const previewId = currentSEOImageType + '-image-preview'; const previewElement = document.getElementById(previewId); if (previewElement) { previewElement.classList.remove('loading'); // Create new image element const img = new Image(); img.onload = function() { previewElement.innerHTML = ` ${currentSEOImageType.toUpperCase()} Image `; console.log('Image displayed successfully'); }; img.onerror = function() { console.error('Failed to load image:', data.url); // Try with a different path format const alternativeUrl = data.url.startsWith('http') ? data.url : window.location.origin + data.url; const imgRetry = new Image(); imgRetry.onload = function() { previewElement.innerHTML = ` ${currentSEOImageType.toUpperCase()} Image `; // Update the input with the working URL if (inputElement) { inputElement.value = alternativeUrl; } }; imgRetry.onerror = function() { previewElement.innerHTML = `
სურათი ვერ ჩაიტვირთა ${data.url}
`; }; imgRetry.src = alternativeUrl; }; // Start loading the image img.src = data.url; } showNotification('სურათი წარმატებით აიტვირთა!'); } else { // Remove loading state on error const previewId = currentSEOImageType + '-image-preview'; const previewElement = document.getElementById(previewId); if (previewElement) { previewElement.classList.remove('loading'); previewElement.innerHTML = `
ატვირთვა ვერ მოხერხდა
`; } showNotification('სურათის ატვირთვა ვერ მოხერხდა', true); } }) .catch(error => { console.error('Error uploading SEO image:', error); // Remove loading state on error const previewId = currentSEOImageType + '-image-preview'; const previewElement = document.getElementById(previewId); if (previewElement) { previewElement.classList.remove('loading'); previewElement.innerHTML = `
შეცდომა ატვირთვისას
`; } showNotification('შეცდომა სურათის ატვირთვისას', true); }) .finally(() => { this.value = ''; currentSEOImageType = null; }); } }); // Character counter for meta fields function updateCharCount(inputId, countId, maxLength) { const input = document.getElementById(inputId); const counter = document.getElementById(countId); const length = input.value.length; counter.textContent = `${length}/${maxLength}`; counter.style.color = length > maxLength * 0.9 ? '#ef4444' : '#6b7280'; } // Initialize character counters document.addEventListener('DOMContentLoaded', function() { // Meta title counter const metaTitleInput = document.getElementById('meta-title-setting'); if (metaTitleInput) { metaTitleInput.addEventListener('input', () => updateCharCount('meta-title-setting', 'meta-title-count', 70)); updateCharCount('meta-title-setting', 'meta-title-count', 70); } // Meta description counter const metaDescInput = document.getElementById('meta-description-setting'); if (metaDescInput) { metaDescInput.addEventListener('input', () => updateCharCount('meta-description-setting', 'meta-description-count', 160)); updateCharCount('meta-description-setting', 'meta-description-count', 160); } }); // Save page settings including SEO function savePageSettings() { const settings = { action: 'save_page_settings', page_id: PAGE_ID, title: document.getElementById('page-title-setting').value, slug: document.getElementById('page-slug-setting').value, is_published: document.getElementById('page-published-setting').checked ? 1 : 0, // SEO fields meta_title: document.getElementById('meta-title-setting').value, meta_description: document.getElementById('meta-description-setting').value, meta_keywords: document.getElementById('meta-keywords-setting').value, canonical_url: document.getElementById('canonical-url-setting').value, robots: document.getElementById('robots-setting').value, // Open Graph og_title: document.getElementById('og-title-setting').value, og_description: document.getElementById('og-description-setting').value, og_image: document.getElementById('og-image-setting').value, og_type: document.getElementById('og-type-setting').value, // Twitter twitter_card: document.getElementById('twitter-card-setting').value, twitter_title: document.getElementById('twitter-title-setting').value, twitter_description: document.getElementById('twitter-description-setting').value, twitter_image: document.getElementById('twitter-image-setting').value }; const formData = new FormData(); Object.keys(settings).forEach(key => { formData.append(key, settings[key]); }); // Show saving state const saveBtn = document.querySelector('.settings-save-btn'); const originalText = saveBtn.innerHTML; saveBtn.innerHTML = ' ინახება...'; saveBtn.disabled = true; fetch(SAVE_URL, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { showNotification('პარამეტრები წარმატებით შეინახა!'); // Update page title if changed if (settings.title !== '') { document.querySelector('.current-page-info span').textContent = 'რედაქტირდება: ' + settings.title; } } else { showNotification('შეცდომა პარამეტრების შენახვისას: ' + data.message, true); } }) .catch(error => { console.error('Error:', error); showNotification('შეცდომა პარამეტრების შენახვისას', true); }) .finally(() => { saveBtn.innerHTML = originalText; saveBtn.disabled = false; }); } function openSettingsPopup() { const popup = document.getElementById('settings-fullscreen-popup'); const content = document.getElementById('settings-fullscreen-content'); const originalContent = document.getElementById('settings-tab'); // Move content to fullscreen popup content.innerHTML = originalContent.innerHTML; popup.classList.add('open'); document.body.style.overflow = 'hidden'; } function closeSettingsPopup() { const popup = document.getElementById('settings-fullscreen-popup'); popup.classList.remove('open'); document.body.style.overflow = ''; } // Sidebar toggle with canvas adjustment // Adjust canvas scale based on available space // Adjust canvas scale based on available space function adjustCanvasScale() { const canvasContainer = document.getElementById('canvas-container'); const canvasLayout = document.getElementById('canvas-layout'); const layersPanel = document.getElementById('canvas-layers-panel'); const layersPanelWidth = layersPanel && layersPanel.classList.contains('open') ? 280 : 0; const containerWidth = canvasContainer.offsetWidth - 64 - layersPanelWidth; // Padding + layers width const canvasWidth = 1400; // Default canvas width if (containerWidth < canvasWidth && containerWidth > 0) { const autoScale = Math.max(0.3, containerWidth / canvasWidth); // Minimum 30% scale setCanvasScale(autoScale, true); } else if (containerWidth > 0) { setCanvasScale(1, true); } } // Canvas Scale Functions // Updated setCanvasScale function with proper active state management function setCanvasScale(scale, isAuto = false) { canvasScale = scale; const canvasLayout = document.getElementById('canvas-layout'); if (scale === 1) { canvasLayout.style.transform = ''; canvasLayout.classList.remove('scaled'); } else { canvasLayout.style.transform = `scale(${scale})`; canvasLayout.classList.add('scaled'); } // Update the dropdown button text const currentScaleSpan = document.getElementById('current-scale'); if (currentScaleSpan) { currentScaleSpan.textContent = Math.round(scale * 100) + '%'; } // Remove active class from all options first document.querySelectorAll('.scale-option').forEach(btn => { btn.classList.remove('active'); }); // Add active class to the selected option const scalePercentage = Math.round(scale * 100) + '%'; document.querySelectorAll('.scale-option').forEach(btn => { if (btn.textContent.trim() === scalePercentage) { btn.classList.add('active'); } }); // Close dropdown after selection (only if not auto-scaling) if (!isAuto) { const dropdown = document.getElementById('scale-dropdown-menu'); if (dropdown) { dropdown.classList.remove('show'); } } } // Language Functions // Component Editor Management // Component Editor Management let currentEditingComponent = null; let componentTextElements = []; let componentImageElements = []; function openComponentEditor() { if (!selectedComponent) { showNotification('გთხოვთ აირჩიოთ კომპონენტი რედაქტირებისთვის', true); return; } currentEditingComponent = selectedComponent; // Collect all text elements componentTextElements = []; const textElements = currentEditingComponent.querySelectorAll('h1, h2, h3, h4, h5, h6, p, span, a, button, li, td, th'); textElements.forEach((el, index) => { if (el.textContent.trim() && !el.closest('.component-toolbar')) { componentTextElements.push({ element: el, originalText: el.textContent.trim(), tagName: el.tagName.toLowerCase(), index: index }); } }); // Collect all images componentImageElements = []; const images = currentEditingComponent.querySelectorAll('img'); images.forEach((img, index) => { componentImageElements.push({ element: img, originalSrc: img.src, alt: img.alt || '', index: index }); }); // Populate the editor modal populateComponentEditor(); // Open modal document.getElementById('component-editor-modal').classList.add('open'); } function closeComponentEditor() { document.getElementById('component-editor-modal').classList.remove('open'); currentEditingComponent = null; componentTextElements = []; componentImageElements = []; } function populateComponentEditor() { // Populate text fields const textFieldsContainer = document.getElementById('component-text-fields'); textFieldsContainer.innerHTML = ''; if (componentTextElements.length === 0) { textFieldsContainer.innerHTML = '

ტექსტური კონტენტი ვერ მოიძებნა

'; } else { componentTextElements.forEach((textItem, index) => { // Skip "სურათის განახლება" text if (textItem.originalText === 'სურათის განახლება') { return; } const field = document.createElement('div'); field.className = 'editor-field'; const tagLabel = getGeorgianTagName(textItem.tagName); const isMultiline = textItem.originalText.length > 50 || textItem.tagName === 'p'; field.innerHTML = ` ${isMultiline ? `` : `` } `; textFieldsContainer.appendChild(field); }); } // Populate image fields const imageFieldsContainer = document.getElementById('component-image-fields'); imageFieldsContainer.innerHTML = ''; if (componentImageElements.length === 0) { imageFieldsContainer.innerHTML = '

სურათები ვერ მოიძებნა

'; } else { componentImageElements.forEach((imgItem, index) => { const field = document.createElement('div'); field.className = 'editor-image-field'; field.innerHTML = `
${imgItem.alt}
`; imageFieldsContainer.appendChild(field); }); } } function getGeorgianTagName(tag) { const tagNames = { 'h1': 'სათაური 1', 'h2': 'სათაური 2', 'h3': 'სათაური 3', 'h4': 'სათაური 4', 'h5': 'სათაური 5', 'h6': 'სათაური 6', 'p': 'პარაგრაფი', 'span': 'ტექსტი', 'a': 'ლინკი', 'button': 'ღილაკი', 'li': 'სიის ელემენტი', 'td': 'ცხრილის უჯრა', 'th': 'ცხრილის სათაური' }; return tagNames[tag] || tag.toUpperCase(); } let currentEditorImageIndex = null; function selectEditorImage(index) { currentEditorImageIndex = index; const fileInput = document.getElementById('imageUpload'); fileInput.isEditorUpload = true; fileInput.click(); } function saveComponentEdits() { // Update text content componentTextElements.forEach((textItem, index) => { const input = document.querySelector(`[data-text-index="${index}"]`); if (input) { const newText = input.value.trim(); if (newText !== textItem.originalText) { textItem.element.textContent = newText; } } }); // Update images componentImageElements.forEach((imgItem, index) => { const hiddenInput = document.getElementById(`editor-image-${index}`); if (hiddenInput) { const newSrc = hiddenInput.value; if (newSrc !== imgItem.originalSrc) { imgItem.element.src = newSrc; } } }); // Mark component as modified if (currentEditingComponent) { currentEditingComponent.setAttribute('data-modified', 'true'); } closeComponentEditor(); showNotification('კომპონენტი წარმატებით განახლდა!'); } // Translation Management let translationMode = false; let currentTranslations = {}; function toggleTranslationMode() { translationMode = !translationMode; const btn = document.querySelector('.translate-btn'); const btnText = document.getElementById('translate-mode-text'); if (translationMode) { btn.classList.add('active'); btnText.textContent = 'ინგლისური ვერსია'; openTranslationPanel(); } else { btn.classList.remove('active'); btnText.textContent = 'ინგლისური ვერსია'; closeTranslationPanel(); } } function openTranslationPanel() { const panel = document.getElementById('translation-panel'); panel.classList.add('open'); populateTranslationPanel(); } function closeTranslationPanel() { const panel = document.getElementById('translation-panel'); panel.classList.remove('open'); translationMode = false; const btn = document.querySelector('.translate-btn'); const btnText = document.getElementById('translate-mode-text'); btn.classList.remove('active'); btnText.textContent = 'ინგლისური ვერსია'; } function populateTranslationPanel() { const content = document.getElementById('translation-content'); content.innerHTML = ''; // Clear any existing translation IDs to start fresh document.querySelectorAll('[data-translation-id]').forEach(el => { el.removeAttribute('data-translation-id'); }); // Get all components const components = document.querySelectorAll('.canvas-component'); components.forEach((component, compIndex) => { const componentId = component.dataset.componentId; const componentData = componentsById[componentId]; const componentName = componentData ? componentData.name : 'Component'; // Add component header const componentHeader = document.createElement('div'); componentHeader.className = 'translation-component-header'; componentHeader.style.cssText = 'margin-top: 1.5rem; margin-bottom: 0.5rem; padding: 0.5rem; background: #f3f4f6; border-radius: 4px;'; componentHeader.innerHTML = `

${componentName}

`; 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 = `
${component.thumbnail ? `${component.name}` : `` }

${component.name}

${component.description || getComponentDescription(component.type)}

`; grid.appendChild(card); }); body.appendChild(grid); modal.classList.add('open'); document.body.style.overflow = 'hidden'; } function showAllComponentsInPicker() { const modal = document.getElementById('minimal-component-picker'); const body = document.getElementById('minimal-picker-body'); const title = document.getElementById('minimal-picker-heading'); title.textContent = 'აირჩიეთ კომპონენტის ტიპი'; body.innerHTML = ''; // Create main menu grid const mainGrid = document.createElement('div'); mainGrid.className = 'minimal-main-menu'; // AI Component option const aiOption = document.createElement('div'); aiOption.className = 'minimal-menu-card ai-card'; aiOption.onclick = () => { closeComponentPicker(); openAIModal(); }; aiOption.innerHTML = `

AI კომპონენტი

შექმენით კომპონენტი ხელოვნური ინტელექტით

`; mainGrid.appendChild(aiOption); // Custom Component option const customOption = document.createElement('div'); customOption.className = 'minimal-menu-card custom-card'; customOption.onclick = () => { closeComponentPicker(); openCustomComponentModal(); }; customOption.innerHTML = `

Custom კომპონენტი

შექმენით თქვენი HTML/CSS კომპონენტი

`; mainGrid.appendChild(customOption); // Category options const categories = {}; availableComponents.forEach(comp => { if (!categories[comp.type]) { categories[comp.type] = []; } categories[comp.type].push(comp); }); Object.entries(categories).forEach(([category, components]) => { const categoryCard = document.createElement('div'); categoryCard.className = 'minimal-menu-card category-card'; categoryCard.onclick = () => showCategoryComponents(category); categoryCard.innerHTML = `

${category.charAt(0).toUpperCase() + category.slice(1)} კომპონენტები

${components.length} კომპონენტი

`; mainGrid.appendChild(categoryCard); }); body.appendChild(mainGrid); modal.classList.add('open'); document.body.style.overflow = 'hidden'; } function showCategoryComponents(category) { const modal = document.getElementById('minimal-component-picker'); const body = document.getElementById('minimal-picker-body'); const title = document.getElementById('minimal-picker-heading'); title.innerHTML = ` ${category.charAt(0).toUpperCase() + category.slice(1)} კომპონენტები `; body.innerHTML = ''; const categoryComponents = availableComponents.filter(comp => comp.type.toLowerCase() === category.toLowerCase()); // Create large preview grid const grid = document.createElement('div'); grid.className = 'minimal-preview-grid'; categoryComponents.forEach(component => { const card = document.createElement('div'); card.className = 'minimal-preview-card'; // Check if it's a custom component const isCustom = component.type === 'custom'; card.innerHTML = `
${component.thumbnail ? `${component.name}` : `
Preview
` } ${isCustom || component.type === 'ai_generated' ? `
${component.type === 'ai_generated' ? 'AI' : 'Custom'}
` : ''}

${component.name}

${component.description || getComponentDescription(component.type)}

${isCustom ? `
` : ''}
`; 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 = `
ელემენტი: ${elementType}
ტექსტი: ${element.textContent?.substring(0, 30) || 'არ არის ტექსტი'}${element.textContent?.length > 30 ? '...' : ''}
`; } // Update style panel with current element values function updateStylePanelValues(element) { const styles = getComputedStyle(element); // Update color inputs const textColor = rgbToHex(styles.color); document.getElementById('text-color').value = textColor; document.getElementById('text-color-hex').value = textColor; const bgColor = styles.backgroundColor !== 'rgba(0, 0, 0, 0)' ? rgbToHex(styles.backgroundColor) : '#ffffff'; document.getElementById('bg-color').value = bgColor; document.getElementById('bg-color-hex').value = bgColor; // Update font size const fontSize = parseInt(styles.fontSize); document.getElementById('font-size').value = fontSize; document.getElementById('font-size-value').textContent = fontSize + 'px'; // Update size values document.getElementById('element-width').value = element.style.width || 'ავტომატურად'; document.getElementById('element-height').value = element.style.height || 'ავტომატურად'; } // Update button style values in panel function updateButtonStyleValues(element) { const styles = getComputedStyle(element); // Get current button colors const bgColor = styles.backgroundColor !== 'rgba(0, 0, 0, 0)' ? rgbToHex(styles.backgroundColor) : '#6366f1'; document.getElementById('btn-bg-color').value = bgColor; document.getElementById('btn-bg-color-hex').value = bgColor; const textColor = rgbToHex(styles.color); document.getElementById('btn-text-color').value = textColor; document.getElementById('btn-text-color-hex').value = textColor; // Border radius const borderRadius = parseInt(styles.borderRadius) || 6; document.getElementById('btn-border-radius').value = borderRadius; document.getElementById('btn-border-radius-value').textContent = borderRadius + 'px'; // Try to get hover colors from data attributes or use defaults const hoverBg = element.dataset.hoverBg || '#4f46e5'; const hoverText = element.dataset.hoverText || '#ffffff'; document.getElementById('btn-hover-bg').value = hoverBg; document.getElementById('btn-hover-bg-hex').value = hoverBg; document.getElementById('btn-hover-text').value = hoverText; document.getElementById('btn-hover-text-hex').value = hoverText; } // Convert RGB to Hex function rgbToHex(rgb) { if (rgb.includes('#')) return rgb; const values = rgb.match(/\d+/g); if (!values || values.length < 3) return '#000000'; return '#' + values.slice(0, 3).map(x => parseInt(x).toString(16).padStart(2, '0')).join(''); } // Initialize style controls function initializeStyleControls() { // Text color document.getElementById('text-color').addEventListener('change', function() { if (selectedElement) { selectedElement.style.setProperty('color', this.value, 'important'); document.getElementById('text-color-hex').value = this.value; } }); document.getElementById('text-color-hex').addEventListener('input', function() { if (selectedElement && this.value.match(/^#[0-9A-F]{6}$/i)) { selectedElement.style.setProperty('color', this.value, 'important'); document.getElementById('text-color').value = this.value; } }); // Background color document.getElementById('bg-color').addEventListener('change', function() { if (selectedElement) { selectedElement.style.setProperty('background-color', this.value, 'important'); document.getElementById('bg-color-hex').value = this.value; } }); // Font size document.getElementById('font-size').addEventListener('input', function() { if (selectedElement) { selectedElement.style.setProperty('font-size', this.value + 'px', 'important'); document.getElementById('font-size-value').textContent = this.value + 'px'; } }); // Advanced Layout Controls document.getElementById('element-width')?.addEventListener('input', function() { if (selectedElement) { selectedElement.style.setProperty('width', this.value, 'important'); } }); document.getElementById('element-height')?.addEventListener('input', function() { if (selectedElement) { selectedElement.style.setProperty('height', this.value, 'important'); } }); // Style buttons document.querySelectorAll('.style-btn[data-display]').forEach(btn => { btn.addEventListener('click', function() { if (selectedElement) { document.querySelectorAll('.style-btn[data-display]').forEach(b => b.classList.remove('active')); this.classList.add('active'); selectedElement.style.setProperty('display', this.dataset.display, 'important'); } }); }); document.querySelectorAll('.style-btn[data-position]').forEach(btn => { btn.addEventListener('click', function() { if (selectedElement) { document.querySelectorAll('.style-btn[data-position]').forEach(b => b.classList.remove('active')); this.classList.add('active'); selectedElement.style.setProperty('position', this.dataset.position, 'important'); } }); }); document.querySelectorAll('.style-btn[data-weight]').forEach(btn => { btn.addEventListener('click', function() { if (selectedElement) { document.querySelectorAll('.style-btn[data-weight]').forEach(b => b.classList.remove('active')); this.classList.add('active'); selectedElement.style.setProperty('font-weight', this.dataset.weight, 'important'); } }); }); document.querySelectorAll('.style-btn[data-padding]').forEach(btn => { btn.addEventListener('click', function() { if (selectedElement) { selectedElement.classList.forEach(cls => { if (cls.startsWith('p-')) { selectedElement.classList.remove(cls); } }); selectedElement.classList.add(this.dataset.padding); } }); }); document.querySelectorAll('.style-btn[data-margin]').forEach(btn => { btn.addEventListener('click', function() { if (selectedElement) { selectedElement.classList.forEach(cls => { if (cls.startsWith('m-')) { selectedElement.classList.remove(cls); } }); selectedElement.classList.add(this.dataset.margin); } }); // Button styling controls document.getElementById('btn-bg-color')?.addEventListener('change', function() { if (selectedElement) { selectedElement.style.setProperty('background-color', this.value, 'important'); document.getElementById('btn-bg-color-hex').value = this.value; selectedElement.dataset.bgColor = this.value; } }); document.getElementById('btn-text-color')?.addEventListener('change', function() { if (selectedElement) { selectedElement.style.setProperty('color', this.value, 'important'); document.getElementById('btn-text-color-hex').value = this.value; } }); document.getElementById('btn-hover-bg')?.addEventListener('change', function() { if (selectedElement) { selectedElement.dataset.hoverBg = this.value; document.getElementById('btn-hover-bg-hex').value = this.value; applyButtonHoverStyles(); } }); document.getElementById('btn-hover-text')?.addEventListener('change', function() { if (selectedElement) { selectedElement.dataset.hoverText = this.value; document.getElementById('btn-hover-text-hex').value = this.value; applyButtonHoverStyles(); } }); document.getElementById('btn-border-radius')?.addEventListener('input', function() { if (selectedElement) { selectedElement.style.setProperty('border-radius', this.value + 'px', 'important'); document.getElementById('btn-border-radius-value').textContent = this.value + 'px'; } }); // Button padding controls document.querySelectorAll('.style-btn[data-btn-padding]').forEach(btn => { btn.addEventListener('click', function() { if (selectedElement) { // Remove all padding classes selectedElement.className = selectedElement.className.replace(/\bp[xy]-[\d.]+\b/g, ''); // Add new padding const paddingClasses = this.dataset.btnPadding.split(' '); paddingClasses.forEach(cls => selectedElement.classList.add(cls)); // Update active state document.querySelectorAll('.style-btn[data-btn-padding]').forEach(b => b.classList.remove('active')); this.classList.add('active'); } }); }); // Button style controls document.querySelectorAll('.style-btn[data-btn-style]').forEach(btn => { btn.addEventListener('click', function() { if (selectedElement) { const style = this.dataset.btnStyle; if (style === 'outline') { selectedElement.style.setProperty('background-color', 'transparent', 'important'); selectedElement.style.setProperty('border', `2px solid ${selectedElement.dataset.bgColor || '#6366f1'}`, 'important'); selectedElement.style.setProperty('color', selectedElement.dataset.bgColor || '#6366f1', 'important'); } else if (style === 'ghost') { selectedElement.style.setProperty('background-color', 'transparent', 'important'); selectedElement.style.setProperty('border', 'none', 'important'); selectedElement.style.setProperty('color', selectedElement.dataset.bgColor || '#6366f1', 'important'); } else { // filled selectedElement.style.setProperty('background-color', selectedElement.dataset.bgColor || '#6366f1', 'important'); selectedElement.style.setProperty('border', 'none', 'important'); selectedElement.style.setProperty('color', '#ffffff', 'important'); } selectedElement.dataset.btnStyle = style; // Update active state document.querySelectorAll('.style-btn[data-btn-style]').forEach(b => b.classList.remove('active')); this.classList.add('active'); } }); }); }); } // Apply button hover styles using CSS function applyButtonHoverStyles() { if (!selectedElement) return; // Ensure element has an ID const elementId = selectedElement.id || 'btn-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9); if (!selectedElement.id) selectedElement.id = elementId; const hoverBg = selectedElement.dataset.hoverBg || '#4f46e5'; const hoverText = selectedElement.dataset.hoverText || '#ffffff'; // Store the hover colors selectedElement.dataset.hoverBg = hoverBg; selectedElement.dataset.hoverText = hoverText; // Remove any existing inline event handlers selectedElement.onmouseenter = null; selectedElement.onmouseleave = null; // Store original colors const originalBg = window.getComputedStyle(selectedElement).backgroundColor; const originalColor = window.getComputedStyle(selectedElement).color; selectedElement.dataset.originalBg = originalBg; selectedElement.dataset.originalColor = originalColor; // Add direct event handlers for hover selectedElement.addEventListener('mouseenter', function() { this.style.setProperty('background-color', hoverBg, 'important'); this.style.setProperty('color', hoverText, 'important'); this.style.setProperty('transform', 'translateY(-1px)', 'important'); this.style.setProperty('box-shadow', '0 4px 12px rgba(0,0,0,0.15)', 'important'); }); selectedElement.addEventListener('mouseleave', function() { this.style.setProperty('background-color', originalBg, 'important'); this.style.setProperty('color', originalColor, 'important'); this.style.setProperty('transform', '', 'important'); this.style.setProperty('box-shadow', '', 'important'); }); // Also inject a style tag as backup const existingStyle = document.getElementById('hover-js-' + elementId); if (existingStyle) existingStyle.remove(); const styleTag = document.createElement('style'); styleTag.id = 'hover-js-' + elementId; styleTag.innerHTML = ` #${elementId} { transition: all 0.2s ease !important; } `; document.head.appendChild(styleTag); console.log('Applied hover styles using JavaScript events'); console.log('Element:', elementId); console.log('Hover BG:', hoverBg); console.log('Hover Text:', hoverText); console.log('Original BG:', originalBg); console.log('Original Color:', originalColor); } // Initialize advanced controls function initializeAdvancedControls() { // Animation controls const animationControls = [ 'animation-type', 'animation-duration', 'animation-delay', 'transform-scale', 'transform-rotate', 'transform-skew-x', 'filter-blur', 'filter-brightness', 'filter-contrast', 'filter-saturation', 'border-width', 'border-color', 'border-style', 'box-shadow' ]; animationControls.forEach(controlId => { const control = document.getElementById(controlId); if (control) { control.addEventListener('input', applyAdvancedStyle); control.addEventListener('change', applyAdvancedStyle); } }); // Update value displays for range inputs const rangeInputs = [ 'animation-duration', 'animation-delay', 'transform-scale', 'transform-rotate', 'transform-skew-x', 'filter-blur', 'filter-brightness', 'filter-contrast', 'filter-saturation', 'border-width' ]; rangeInputs.forEach(inputId => { const input = document.getElementById(inputId); const valueSpan = document.getElementById(inputId + '-value'); if (input && valueSpan) { input.addEventListener('input', function() { let unit = ''; if (inputId.includes('duration') || inputId.includes('delay')) unit = 's'; if (inputId.includes('rotate') || inputId.includes('skew')) unit = '°'; if (inputId.includes('blur') || inputId.includes('border-width')) unit = 'px'; if (inputId.includes('brightness') || inputId.includes('contrast') || inputId.includes('saturation')) unit = '%'; valueSpan.textContent = this.value + unit; }); } }); } function applyAdvancedStyle() { if (!selectedElement) return; const animationType = document.getElementById('animation-type').value; const animationDuration = document.getElementById('animation-duration').value; const animationDelay = document.getElementById('animation-delay').value; const transformScale = document.getElementById('transform-scale').value; const transformRotate = document.getElementById('transform-rotate').value; const transformSkewX = document.getElementById('transform-skew-x').value; const filterBlur = document.getElementById('filter-blur').value; const filterBrightness = document.getElementById('filter-brightness').value; const filterContrast = document.getElementById('filter-contrast').value; const filterSaturation = document.getElementById('filter-saturation').value; const borderWidth = document.getElementById('border-width').value; const borderColor = document.getElementById('border-color').value; const borderStyle = document.getElementById('border-style').value; const boxShadow = document.getElementById('box-shadow').value; // Apply transforms let transform = ''; if (transformScale != 1) transform += `scale(${transformScale}) `; if (transformRotate != 0) transform += `rotate(${transformRotate}deg) `; if (transformSkewX != 0) transform += `skewX(${transformSkewX}deg) `; if (transform) { selectedElement.style.setProperty('transform', transform.trim(), 'important'); } // Apply filters let filter = ''; if (filterBlur > 0) filter += `blur(${filterBlur}px) `; if (filterBrightness != 100) filter += `brightness(${filterBrightness}%) `; if (filterContrast != 100) filter += `contrast(${filterContrast}%) `; if (filterSaturation != 100) filter += `saturate(${filterSaturation}%) `; if (filter) { selectedElement.style.setProperty('filter', filter.trim(), 'important'); } // Apply borders if (borderWidth > 0) { selectedElement.style.setProperty('border-width', borderWidth + 'px', 'important'); selectedElement.style.setProperty('border-color', borderColor, 'important'); selectedElement.style.setProperty('border-style', borderStyle, 'important'); } // Apply box shadow if (boxShadow) { selectedElement.style.setProperty('box-shadow', boxShadow, 'important'); } // Apply animations if (animationType) { // Remove existing animation classes selectedElement.classList.remove('fade-in', 'slide-up', 'slide-down', 'slide-left', 'slide-right', 'zoom-in', 'bounce'); selectedElement.style.setProperty('animation-duration', animationDuration + 's', 'important'); selectedElement.style.setProperty('animation-delay', animationDelay + 's', 'important'); selectedElement.style.setProperty('animation-fill-mode', 'both', 'important'); // Force reflow to restart animation void selectedElement.offsetWidth; // Add animation class selectedElement.classList.add(animationType); } } function selectImageForUpload(imgElement) { console.log('selectImageForUpload called for:', imgElement.src); const fileInput = document.getElementById('imageUpload'); if (!fileInput) { console.error('File input not found!'); return; } fileInput.targetImageElement = imgElement; fileInput.value = ''; // Reset file input fileInput.click(); } // Image upload handler document.getElementById('imageUpload').addEventListener('change', function(e) { const file = e.target.files[0]; // Handle editor image upload if (file && this.isEditorUpload && currentEditorImageIndex !== 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 preview in editor const preview = document.querySelectorAll('.editor-image-preview')[currentEditorImageIndex]; if (preview) { const img = preview.querySelector('img'); if (img) { img.src = data.url; } } // Update hidden input const hiddenInput = document.getElementById(`editor-image-${currentEditorImageIndex}`); if (hiddenInput) { hiddenInput.value = data.url; } showNotification('სურათი წარმატებით აიტვირთა!'); } else { showNotification('სურათის ატვირთვა ვერ მოხერხდა', true); } }) .catch(error => { console.error('Error:', error); showNotification('შეცდომა სურათის ატვირთვისას', true); }) .finally(() => { this.isEditorUpload = false; currentEditorImageIndex = null; }); this.value = ''; return; } // Original image upload code if (file && this.targetImageElement) { const formData = new FormData(); formData.append('action', 'upload_image'); formData.append('image', file); formData.append('page_id', PAGE_ID); // Show loading state const originalSrc = this.targetImageElement.src; const wrapper = this.targetImageElement.closest('.editable-image'); const loadingOverlay = wrapper.querySelector('.image-upload-overlay'); if (loadingOverlay) { loadingOverlay.innerHTML = '
Uploading...
'; loadingOverlay.style.display = 'flex'; } fetch(SAVE_URL, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { console.log('Image upload successful, new URL:', data.url); const timestamp = new Date().getTime(); const newUrl = data.url + '?v=' + timestamp; const newImg = new Image(); newImg.onload = () => { this.targetImageElement.src = newUrl; this.targetImageElement.style.display = 'block'; this.targetImageElement.style.opacity = '1'; this.targetImageElement.style.visibility = 'visible'; this.targetImageElement.offsetHeight; showNotification('Image uploaded successfully!'); console.log('Image display updated immediately'); if (loadingOverlay) { setTimeout(() => { loadingOverlay.style.display = 'none'; loadingOverlay.innerHTML = ` Click to upload new image `; }, 500); } }; newImg.onerror = () => { console.error('Error loading new image, reverting to original'); this.targetImageElement.src = originalSrc; showNotification('Error displaying new image', true); }; newImg.src = newUrl; } else { this.targetImageElement.src = originalSrc; showNotification('Failed to upload image: ' + (data.message || 'Unknown error'), true); } }) .catch(error => { console.error('Error uploading image:', error); this.targetImageElement.src = originalSrc; showNotification('Error uploading image: ' + error.message, true); }); } this.value = ''; }); // Enhanced Component actions function editComponent(button, event) { if (event) event.stopPropagation(); const component = button.closest('.canvas-component'); selectComponent(component); const firstEditable = component.querySelector('.editable-element[contenteditable="true"]'); if (firstEditable) { firstEditable.focus(); selectElement(firstEditable); } } function styleComponent(button, event) { if (event) event.stopPropagation(); const component = button.closest('.canvas-component'); selectComponent(component); openStylePanel(); } function duplicateComponent(button, event) { if (event) event.stopPropagation(); const component = button.closest('.canvas-component'); const componentId = component.dataset.componentId; const componentData = componentsById[componentId]; if (componentData) { const currentSettings = extractComponentSettings(component); const clone = createComponentElement(componentData, currentSettings); clone.id = 'component-' + Date.now(); component.parentNode.insertBefore(clone, component.nextSibling); initializeEditingForElement(clone); } refreshLayers(); updateDebugInfo(); } function deleteComponent(button, event) { if (event) event.stopPropagation(); if (confirm('Are you sure you want to delete this component?')) { const component = button.closest('.canvas-component'); component.remove(); 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'; } refreshLayers(); updateDebugInfo(); } } // Component selection function selectComponent(component) { document.querySelectorAll('.canvas-component.selected').forEach(el => { el.classList.remove('selected'); }); component.classList.add('selected'); selectedComponent = component; // Update active layer if layers tab is open if (document.getElementById('layers-tab-content').classList.contains('active')) { updateActiveLayer(); refreshLayers(); // Add this line to refresh and show AI button } } // Style panel functions function openStylePanel() { const stylePanel = document.getElementById('style-panel'); const canvasWrapper = document.querySelector('.canvas-wrapper'); stylePanel.classList.add('open'); canvasWrapper.classList.add('style-panel-open'); // Always start with style tab active switchStyleTab('style'); // Check screen width and adjust scale accordingly const screenWidth = window.innerWidth; if (canvasScale >= 1) { setTimeout(() => { if (screenWidth <= 1366) { // Small screens (1366px or less) setCanvasScale(0.60); } else { // Larger screens setCanvasScale(0.75); } }, 100); } } function closeStylePanel() { const stylePanel = document.getElementById('style-panel'); const canvasWrapper = document.querySelector('.canvas-wrapper'); stylePanel.classList.remove('open'); canvasWrapper.classList.remove('style-panel-open'); // Auto adjust canvas back to 100% when style panel closes setTimeout(() => { setCanvasScale(1); }, 100); } // Preview functions function openPreview() { const modal = document.getElementById('preview-modal'); const iframe = document.getElementById('preview-iframe'); const previewUrl = `../page.php?slug=&lang=${CURRENT_LANGUAGE}`; iframe.src = previewUrl; modal.classList.add('open'); } function closePreview() { document.getElementById('preview-modal').classList.remove('open'); document.getElementById('preview-iframe').src = 'about:blank'; } function setPreviewDevice(device) { const iframe = document.getElementById('preview-iframe'); const deviceBtns = document.querySelectorAll('.preview-modal .device-btn'); deviceBtns.forEach(btn => btn.classList.remove('active')); event.target.closest('.device-btn').classList.add('active'); iframe.className = `preview-iframe ${device}`; } // Enhanced Save components function saveComponents() { console.log('Starting save process...'); const components = []; const componentElements = document.querySelectorAll('.canvas-component'); console.log('Found components to save:', componentElements.length); componentElements.forEach((element, index) => { const componentId = element.dataset.componentId; const settings = extractComponentSettings(element); components.push({ component_id: componentId, settings: settings, html: stripToolbar(element) }); }); console.log('Final components array:', components); const saveBtn = document.querySelector('.save-button'); const originalHTML = saveBtn.innerHTML; saveBtn.innerHTML = ' ინახება...'; saveBtn.disabled = true; const postData = 'action=save_components&page_id=' + PAGE_ID + '&components=' + encodeURIComponent(JSON.stringify(components)); fetch(SAVE_URL, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: postData }) .then(response => { console.log('Response status:', response.status); return 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 format: ' + text); } }); }) .then(data => { console.log('Parsed response:', data); if (data.success) { saveBtn.innerHTML = ' განახლდა!'; showNotification(`დიზაინი წარმატებით განხლდა! (${data.count || 0} კომპონენტი)`); setTimeout(() => { saveBtn.innerHTML = originalHTML; saveBtn.disabled = false; }, 2000); } else { throw new Error(data.message || 'Save failed'); } }) .catch(error => { console.error('Save error:', error); saveBtn.innerHTML = ' Error'; showNotification('Error saving: ' + error.message, true); setTimeout(() => { saveBtn.innerHTML = originalHTML; saveBtn.disabled = false; }, 2000); }); updateDebugInfo(); } // Enhanced Extract component settings function extractComponentSettings(element) { const settings = {}; const componentId = element.dataset.componentId; const componentData = componentsById[componentId]; if (!componentData) { console.error('No component data found for ID:', componentId); return settings; } const expectedFields = componentData.settings_fields || []; const textElements = element.querySelectorAll('.editable-element[contenteditable="true"]'); if (expectedFields.length > 0) { expectedFields.forEach((field, index) => { let value = field.default || ''; const matchingElement = Array.from(textElements).find(el => { const tag = el.tagName.toLowerCase(); const classList = Array.from(el.classList); if (field.name === 'title') { return tag.match(/h[1-6]/) || classList.some(c => c.includes('title')) || index === 0; } if (field.name === 'subtitle') { return (tag === 'h2' || tag === 'p') && index === 1; } if (field.name === 'content') { return tag === 'p' && !classList.some(c => c.includes('title')); } if (field.name.includes('button_text') || field.name.includes('button')) { return tag === 'a' || tag === 'button'; } return false; }); if (matchingElement) { value = matchingElement.textContent || matchingElement.innerText || ''; if (field.name.includes('button') && matchingElement.tagName.toLowerCase() === 'a') { const linkField = field.name.replace('_text', '_link').replace('button_text', 'button_link'); settings[linkField] = matchingElement.href || '#'; } } else if (textElements[index]) { value = textElements[index].textContent || textElements[index].innerText || ''; } settings[field.name] = value; }); } // Extract image URLs const images = element.querySelectorAll('img'); if (images.length > 0) { if (images.length === 1) { settings['featured_image'] = images[0].src; } else { const imageUrls = Array.from(images).map(img => img.src).join('\n'); settings['images'] = imageUrls; } } // Extract links and buttons with data-editable-link const links = element.querySelectorAll('a, [data-editable-link]'); links.forEach((link, index) => { const url = link.href || link.getAttribute('data-editable-link') || '#'; const target = link.target || link.getAttribute('data-editable-target') || '_self'; if (index === 0 && !settings['button_link']) { settings['button_link'] = url; settings['button_target'] = target; } }); return settings; } // Enhanced Render component HTML function renderComponentHTML(template, settings) { let html = template; Object.keys(settings).forEach(key => { const placeholder = '{' + key + '}'; const value = settings[key] || ''; html = html.replace(new RegExp(placeholder, 'g'), escapeHtml(value)); }); if (html.includes('{images}')) { const images = settings.images || ''; const imageUrls = images.split('\n').filter(url => url.trim()); const imageHtml = imageUrls.map(url => `Gallery Image` ).join(''); html = html.replace('{images}', imageHtml); } return html; } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Keyboard shortcuts document.addEventListener('keydown', function(e) { // Save with Ctrl+S if (e.ctrlKey && e.key === 's') { e.preventDefault(); saveComponents(); } // Delete selected element with Delete key if (e.key === 'Delete' && selectedElement && !selectedElement.contentEditable) { e.preventDefault(); deleteElement(); } // Duplicate with Ctrl+D if (e.ctrlKey && e.key === 'd' && selectedElement) { e.preventDefault(); duplicateElement(); } // Open link editor with Ctrl+K if (e.ctrlKey && e.key === 'k' && selectedElement) { e.preventDefault(); editElementLink(); } }); // Helper function to get component icon function getComponentIcon(type) { const icons = { 'hero': 'rocket', 'content': 'file-text', 'gallery': 'images', 'features': 'th-large', 'contact': 'envelope', 'form': 'paper-plane', 'ai_generated': 'robot', 'custom': 'code' }; return icons[type] || 'puzzle-piece'; } function getComponentDescription(type) { const descriptions = { 'hero': 'Main banner with title and CTA', 'content': 'Rich text content section', 'gallery': 'Responsive image grid', 'features': 'Highlight key features', 'contact': 'Get in touch form', 'form': 'Custom contact form', 'ai_generated': 'Custom AI-generated component', 'custom': 'Custom HTML component' }; return descriptions[type] || 'Custom component'; }