diff --git a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.html b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.html index 7d73e68..8bd68c6 100644 --- a/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.html +++ b/force-app/main/default/lwc/propertyTemplateSelector/propertyTemplateSelector.html @@ -1211,12 +1211,12 @@ Image -
+
- + +
- - + +
-
- - +
+
- + `; + } + + // Hide selector options + hideSelectorOptions() { + const optionsPanel = this.template.querySelector(".selector-options-panel"); + if (optionsPanel) { + optionsPanel.remove(); + } + } + // Insert content at selected position + insertAtSelection(type) { + if (!this.selectedElement) return; + + let content; + switch (type) { + case "text": + content = document.createElement("p"); + content.textContent = "New Text"; + content.contentEditable = true; + break; + case "image": + content = document.createElement("img"); + content.src = "https://via.placeholder.com/200x150"; + content.style.maxWidth = "200px"; + content.style.height = "auto"; + content.draggable = true; + content.addEventListener( + "dragstart", + this.handleImageDragStart.bind(this) + ); + break; + case "table": + content = this.createTableElement(); + // Make table draggable + content.draggable = true; + content.addEventListener( + "dragstart", + this.handleTableDragStart.bind(this) + ); + break; } - // Hide selector options - hideSelectorOptions() { - const optionsPanel = this.template.querySelector('.selector-options-panel'); - if (optionsPanel) { - optionsPanel.remove(); - } + if (content) { + this.selectedElement.parentNode.insertBefore( + content, + this.selectedElement.nextSibling + ); + this.clearSelection(); } + } - // Insert content at selected position - insertAtSelection(type) { - if (!this.selectedElement) return; - - let content; - switch (type) { - case 'text': - content = document.createElement('p'); - content.textContent = 'New Text'; - content.contentEditable = true; - break; - case 'image': - content = document.createElement('img'); - content.src = 'https://via.placeholder.com/200x150'; - content.style.maxWidth = '200px'; - content.style.height = 'auto'; - content.draggable = true; - content.addEventListener('dragstart', this.handleImageDragStart.bind(this)); - break; - case 'table': - content = this.createTableElement(); - // Make table draggable - content.draggable = true; - content.addEventListener('dragstart', this.handleTableDragStart.bind(this)); - break; - } - - if (content) { - this.selectedElement.parentNode.insertBefore(content, this.selectedElement.nextSibling); - this.clearSelection(); - } + // Remove selected element + removeSelectedElement() { + if (this.selectedElement) { + this.selectedElement.remove(); + this.clearSelection(); } + } - // Remove selected element - removeSelectedElement() { - if (this.selectedElement) { - this.selectedElement.remove(); - this.clearSelection(); - } + // Move element up + moveElementUp() { + if (this.selectedElement && this.selectedElement.previousElementSibling) { + this.selectedElement.parentNode.insertBefore( + this.selectedElement, + this.selectedElement.previousElementSibling + ); } + } - // Move element up - moveElementUp() { - if (this.selectedElement && this.selectedElement.previousElementSibling) { - this.selectedElement.parentNode.insertBefore(this.selectedElement, this.selectedElement.previousElementSibling); - } + // Move element down + moveElementDown() { + if (this.selectedElement && this.selectedElement.nextElementSibling) { + this.selectedElement.parentNode.insertBefore( + this.selectedElement.nextElementSibling, + this.selectedElement + ); } + } - // Move element down - moveElementDown() { - if (this.selectedElement && this.selectedElement.nextElementSibling) { - this.selectedElement.parentNode.insertBefore(this.selectedElement.nextElementSibling, this.selectedElement); - } - } + // Insert property image + insertPropertyImage() { + if (!this.selectedElement) return; - // Insert property image - insertPropertyImage() { - if (!this.selectedElement) return; + // Show property image selection popup + this.showPropertyImagePopup(); + } - // Show property image selection popup - this.showPropertyImagePopup(); - } + // Insert local image + insertLocalImage() { + if (!this.selectedElement) return; - // Insert local image - insertLocalImage() { - if (!this.selectedElement) return; + const input = document.createElement("input"); + input.type = "file"; + input.accept = "image/*"; + input.onchange = (e) => { + const file = e.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + const img = document.createElement("img"); + img.src = e.target.result; + img.style.maxWidth = "200px"; + img.style.height = "auto"; + img.draggable = true; + img.addEventListener( + "dragstart", + this.handleImageDragStart.bind(this) + ); - const input = document.createElement('input'); - input.type = 'file'; - input.accept = 'image/*'; - input.onchange = (e) => { - const file = e.target.files[0]; - if (file) { - const reader = new FileReader(); - reader.onload = (e) => { - const img = document.createElement('img'); - img.src = e.target.result; - img.style.maxWidth = '200px'; - img.style.height = 'auto'; - img.draggable = true; - img.addEventListener('dragstart', this.handleImageDragStart.bind(this)); - - this.selectedElement.parentNode.insertBefore(img, this.selectedElement.nextSibling); - this.clearSelection(); - }; - reader.readAsDataURL(file); - } + this.selectedElement.parentNode.insertBefore( + img, + this.selectedElement.nextSibling + ); + this.clearSelection(); }; - input.click(); - } - - // Show property image popup - showPropertyImagePopup() { - // Create property image selection popup - let popup = this.template.querySelector('.property-image-popup'); - if (!popup) { - popup = document.createElement('div'); - popup.className = 'property-image-popup'; - popup.style.cssText = ` + reader.readAsDataURL(file); + } + }; + input.click(); + } + // Show property image popup + showPropertyImagePopup() { + // Create property image selection popup + let popup = this.template.querySelector(".property-image-popup"); + if (!popup) { + popup = document.createElement("div"); + popup.className = "property-image-popup"; + popup.style.cssText = ` position: fixed; top: 50%; left: 50%; @@ -4358,60 +11655,73 @@ export default class PropertyTemplateSelector extends LightningElement { max-height: 500px; overflow-y: auto; `; - document.body.appendChild(popup); - } + document.body.appendChild(popup); + } - // Get property images - const images = this.realPropertyImages || []; - const imageGrid = images.map(img => ` + // Get property images + const images = this.realPropertyImages || []; + const imageGrid = images + .map( + (img) => `
- -
${img.category || 'Uncategorized'}
+ +
${img.category || "Uncategorized" + }
- `).join(''); + ` + ) + .join(""); - popup.innerHTML = ` + popup.innerHTML = `
Select Property Image
${imageGrid}
- + `; - } + } + // Select property image + selectPropertyImage(imageUrl) { + if (this.selectedElement) { + const img = document.createElement("img"); + img.src = imageUrl; + img.style.maxWidth = "200px"; + img.style.height = "auto"; + img.draggable = true; + img.addEventListener("dragstart", this.handleImageDragStart.bind(this)); + img.addEventListener("dragend", this.handleImageDragEnd.bind(this)); + img.addEventListener("dragover", this.handleImageDragOver.bind(this)); + img.addEventListener("dragleave", this.handleImageDragLeave.bind(this)); + img.addEventListener("drop", this.handleImageDrop.bind(this)); - // Select property image - selectPropertyImage(imageUrl) { - if (this.selectedElement) { - const img = document.createElement('img'); - img.src = imageUrl; - img.style.maxWidth = '200px'; - img.style.height = 'auto'; - img.draggable = true; - img.addEventListener('dragstart', this.handleImageDragStart.bind(this)); - - this.selectedElement.parentNode.insertBefore(img, this.selectedElement.nextSibling); - this.clearSelection(); - } - this.closePropertyImagePopup(); + this.selectedElement.parentNode.insertBefore( + img, + this.selectedElement.nextSibling + ); + this.clearSelection(); } + this.closePropertyImagePopup(); + } - // Close property image popup - closePropertyImagePopup() { - const popup = this.template.querySelector('.property-image-popup'); - if (popup) { - popup.remove(); - } + // Close property image popup + closePropertyImagePopup() { + const popup = this.template.querySelector(".property-image-popup"); + if (popup) { + popup.remove(); } - - // Create table element with enhanced drag and resize functionality - createTableElement() { - // Create the main table container with absolute positioning for drag/resize - const tableContainer = document.createElement('div'); - tableContainer.className = 'draggable-table-container'; - tableContainer.style.cssText = ` + } + // Create table element with enhanced drag and resize functionality + createTableElement() { + // Create the main table container with absolute positioning for drag/resize + const tableContainer = document.createElement("div"); + tableContainer.className = "draggable-table-container"; + tableContainer.style.cssText = ` position: absolute; left: 50px; top: 50px; @@ -4427,88 +11737,87 @@ export default class PropertyTemplateSelector extends LightningElement { border-radius: 8px; overflow: hidden; `; - - // Create the actual table - const table = document.createElement('table'); - table.style.cssText = ` + + // Create the actual table + const table = document.createElement("table"); + table.style.cssText = ` width: 100%; height: 100%; border-collapse: collapse; margin: 0; background: white; `; - - // Create header row - const headerRow = document.createElement('tr'); - for (let i = 0; i < this.tableCols; i++) { - const th = document.createElement('th'); - th.textContent = `Header ${i + 1}`; - th.style.cssText = ` + + // Create header row + const headerRow = document.createElement("tr"); + for (let i = 0; i < this.tableCols; i++) { + const th = document.createElement("th"); + th.textContent = `Header ${i + 1}`; + th.style.cssText = ` border: 1px solid #ddd; padding: 8px; background: #f8f9fa; font-weight: 600; text-align: left; `; - headerRow.appendChild(th); - } - table.appendChild(headerRow); - - // Create data rows - const startRow = this.includeHeader ? 1 : 0; - for (let i = startRow; i < this.tableRows; i++) { - const row = document.createElement('tr'); - for (let j = 0; j < this.tableCols; j++) { - const td = document.createElement('td'); - td.textContent = `Cell ${i + 1},${j + 1}`; - td.style.cssText = ` + headerRow.appendChild(th); + } + table.appendChild(headerRow); + + // Create data rows + const startRow = this.includeHeader ? 1 : 0; + for (let i = startRow; i < this.tableRows; i++) { + const row = document.createElement("tr"); + for (let j = 0; j < this.tableCols; j++) { + const td = document.createElement("td"); + td.textContent = `Cell ${i + 1},${j + 1}`; + td.style.cssText = ` border: 1px solid #ddd; padding: 8px; background: white; `; - // Make cells editable - td.contentEditable = true; - td.addEventListener('blur', () => { - // Save changes when cell loses focus - }); - row.appendChild(td); - } - table.appendChild(row); - } - - tableContainer.appendChild(table); - - // Add resize handles (same as images) - this.addResizeHandles(tableContainer); - - // Add delete handle (same as images) - this.addDeleteHandle(tableContainer); - - // Add drag functionality (same as images) - this.makeDraggable(tableContainer); - - // Add click to select functionality - tableContainer.addEventListener('click', (e) => { - e.stopPropagation(); - this.selectDraggableElement(tableContainer); + // Make cells editable + td.contentEditable = true; + td.addEventListener("blur", () => { + // Save changes when cell loses focus }); - - // Add table controls overlay - this.addTableControls(tableContainer, table); - - // Select the table after a short delay - setTimeout(() => { - this.selectDraggableElement(tableContainer); - }, 100); - - return tableContainer; + row.appendChild(td); + } + table.appendChild(row); } - + + tableContainer.appendChild(table); + + // Add resize handles (same as images) + this.addResizeHandles(tableContainer); + + // Add delete handle (same as images) + this.addDeleteHandle(tableContainer); + + // Add drag functionality (same as images) + this.makeDraggable(tableContainer); + + // Add click to select functionality + tableContainer.addEventListener("click", (e) => { + e.stopPropagation(); + this.selectDraggableElement(tableContainer); + }); + // Add table controls overlay - addTableControls(container, table) { - const controls = document.createElement('div'); - controls.className = 'table-controls-overlay'; - controls.style.cssText = ` + this.addTableControls(tableContainer, table); + + // Select the table after a short delay + setTimeout(() => { + this.selectDraggableElement(tableContainer); + }, 100); + + return tableContainer; + } + // Add table controls overlay + addTableControls(container, table) { + const controls = document.createElement("div"); + controls.className = "table-controls-overlay"; + controls.style.cssText = ` position: absolute; top: -40px; left: 0; @@ -4522,11 +11831,11 @@ export default class PropertyTemplateSelector extends LightningElement { gap: 4px; z-index: 1002; `; - - // Add Row button - const addRowBtn = document.createElement('button'); - addRowBtn.innerHTML = '+ Row'; - addRowBtn.style.cssText = ` + + // Add Row button + const addRowBtn = document.createElement("button"); + addRowBtn.innerHTML = "+ Row"; + addRowBtn.style.cssText = ` padding: 4px 8px; font-size: 12px; background: #28a745; @@ -4535,15 +11844,15 @@ export default class PropertyTemplateSelector extends LightningElement { border-radius: 4px; cursor: pointer; `; - addRowBtn.onclick = (e) => { - e.stopPropagation(); - this.addTableRow(table); - }; - - // Add Column button - const addColBtn = document.createElement('button'); - addColBtn.innerHTML = '+ Col'; - addColBtn.style.cssText = ` + addRowBtn.onclick = (e) => { + e.stopPropagation(); + this.addTableRow(table); + }; + + // Add Column button + const addColBtn = document.createElement("button"); + addColBtn.innerHTML = "+ Col"; + addColBtn.style.cssText = ` padding: 4px 8px; font-size: 12px; background: #17a2b8; @@ -4552,15 +11861,15 @@ export default class PropertyTemplateSelector extends LightningElement { border-radius: 4px; cursor: pointer; `; - addColBtn.onclick = (e) => { - e.stopPropagation(); - this.addTableColumn(table); - }; - - // Delete Row button - const delRowBtn = document.createElement('button'); - delRowBtn.innerHTML = '- Row'; - delRowBtn.style.cssText = ` + addColBtn.onclick = (e) => { + e.stopPropagation(); + this.addTableColumn(table); + }; + + // Delete Row button + const delRowBtn = document.createElement("button"); + delRowBtn.innerHTML = "- Row"; + delRowBtn.style.cssText = ` padding: 4px 8px; font-size: 12px; background: #ffc107; @@ -4569,15 +11878,14 @@ export default class PropertyTemplateSelector extends LightningElement { border-radius: 4px; cursor: pointer; `; - delRowBtn.onclick = (e) => { - e.stopPropagation(); - this.deleteTableRow(table); - }; - - // Delete Column button - const delColBtn = document.createElement('button'); - delColBtn.innerHTML = '- Col'; - delColBtn.style.cssText = ` + delRowBtn.onclick = (e) => { + e.stopPropagation(); + this.deleteTableRow(table); + }; + // Delete Column button + const delColBtn = document.createElement("button"); + delColBtn.innerHTML = "- Col"; + delColBtn.style.cssText = ` padding: 4px 8px; font-size: 12px; background: #fd7e14; @@ -4586,175 +11894,184 @@ export default class PropertyTemplateSelector extends LightningElement { border-radius: 4px; cursor: pointer; `; - delColBtn.onclick = (e) => { - e.stopPropagation(); - this.deleteTableColumn(table); - }; - - controls.appendChild(addRowBtn); - controls.appendChild(addColBtn); - controls.appendChild(delRowBtn); - controls.appendChild(delColBtn); - - container.appendChild(controls); - - // Show/hide controls on hover - container.addEventListener('mouseenter', () => { - controls.style.opacity = '1'; - }); - - container.addEventListener('mouseleave', () => { - controls.style.opacity = '0'; - }); - } + delColBtn.onclick = (e) => { + e.stopPropagation(); + this.deleteTableColumn(table); + }; - // Table manipulation methods (updated for new structure) - addTableRow(table) { - const newRow = document.createElement('tr'); - const colCount = table.rows[0].cells.length; - - for (let i = 0; i < colCount; i++) { - const td = document.createElement('td'); - td.textContent = `New Cell`; - td.style.cssText = ` + controls.appendChild(addRowBtn); + controls.appendChild(addColBtn); + controls.appendChild(delRowBtn); + controls.appendChild(delColBtn); + + container.appendChild(controls); + + // Show/hide controls on hover + container.addEventListener("mouseenter", () => { + controls.style.opacity = "1"; + }); + + container.addEventListener("mouseleave", () => { + controls.style.opacity = "1"; + }); + } + // Table manipulation methods (updated for new structure) + addTableRow(table) { + const newRow = document.createElement("tr"); + const colCount = table.rows[0].cells.length; + + for (let i = 0; i < colCount; i++) { + const td = document.createElement("td"); + td.textContent = `New Cell`; + td.style.cssText = ` border: 1px solid #ddd; padding: 8px; background: white; `; - td.contentEditable = true; - newRow.appendChild(td); - } - - table.appendChild(newRow); + td.contentEditable = true; + newRow.appendChild(td); } - addTableColumn(table) { - const rows = table.rows; - - for (let i = 0; i < rows.length; i++) { - const cell = document.createElement(i === 0 ? 'th' : 'td'); - cell.textContent = i === 0 ? `Header ${rows[i].cells.length + 1}` : `New Cell`; - cell.style.cssText = ` + table.appendChild(newRow); + } + + addTableColumn(table) { + const rows = table.rows; + + for (let i = 0; i < rows.length; i++) { + const cell = document.createElement(i === 0 ? "th" : "td"); + cell.textContent = + i === 0 ? `Header ${rows[i].cells.length + 1}` : `New Cell`; + cell.style.cssText = ` border: 1px solid #ddd; padding: 8px; - background: ${i === 0 ? '#f8f9fa' : 'white'}; - font-weight: ${i === 0 ? '600' : 'normal'}; + background: ${i === 0 ? "#f8f9fa" : "white"}; + font-weight: ${i === 0 ? "600" : "normal"}; `; - if (i > 0) { - cell.contentEditable = true; - } - rows[i].appendChild(cell); - } + if (i > 0) { + cell.contentEditable = true; + } + rows[i].appendChild(cell); + } + } + + deleteTableRow(table) { + if (table.rows.length > 1) { + table.deleteRow(-1); + } + } + + deleteTableColumn(table) { + const rows = table.rows; + if (rows[0].cells.length > 1) { + for (let i = 0; i < rows.length; i++) { + rows[i].deleteCell(-1); + } + } + } + + deleteTable(event) { + const tableContainer = event.target.closest("div"); + tableContainer.remove(); + } + + // Make images draggable and resizable + makeImagesDraggableAndResizable() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) return; + + const images = editor.querySelectorAll("img"); + images.forEach((img) => { + // Prevent position changes on click + img.style.position = "relative"; + img.style.zIndex = "1000"; + img.style.transition = "none"; // Disable transitions during drag + + // Add resize handles + this.addResizeHandles(img); + + // Add smooth drag event listeners + img.addEventListener("mousedown", this.handleImageMouseDown.bind(this)); + img.addEventListener("mousemove", this.handleImageMouseMove.bind(this)); + img.addEventListener("mouseup", this.handleImageMouseUp.bind(this)); + img.addEventListener("mouseleave", this.handleImageMouseUp.bind(this)); + }); + } + // Smooth drag handlers for images + handleImageMouseDown(e) { + if (e.target.tagName !== "IMG") return; + + e.preventDefault(); + this.isDraggingImage = true; + this.dragStartX = e.clientX; + this.dragStartY = e.clientY; + this.dragElement = e.target; + this.dragInitiated = false; // will flip to true only after threshold is exceeded + + // Store initial position + const rect = this.dragElement.getBoundingClientRect(); + const editor = this.template.querySelector(".enhanced-editor-content"); + const editorRect = editor.getBoundingClientRect(); + + this.initialLeft = rect.left - editorRect.left; + this.initialTop = rect.top - editorRect.top; + + // Add dragging class for visual feedback + this.dragElement.style.cursor = "grabbing"; + + // Prevent text selection during drag + document.body.style.userSelect = "none"; + } + + handleImageMouseMove(e) { + if (!this.isDraggingImage || !this.dragElement) return; + + e.preventDefault(); + + const deltaX = e.clientX - this.dragStartX; + const deltaY = e.clientY - this.dragStartY; + const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + + // Only start moving the image if the cursor moved beyond a small threshold + if (!this.dragInitiated && distance > 5) { + this.dragInitiated = true; + this.dragElement.style.opacity = "0.85"; + this.dragElement.style.position = "absolute"; + } + if (!this.dragInitiated) return; + + // Update position smoothly after drag actually begins + this.dragElement.style.left = this.initialLeft + deltaX + "px"; + this.dragElement.style.top = this.initialTop + deltaY + "px"; + } + + handleImageMouseUp(e) { + if (!this.isDraggingImage || !this.dragElement) return; + + this.isDraggingImage = false; + + // Restore cursor and opacity + this.dragElement.style.cursor = "grab"; + this.dragElement.style.opacity = ""; + + // Re-enable text selection + document.body.style.userSelect = ""; + + // Save undo state after drag + if (this.dragInitiated) { + this.saveUndoState(); } - deleteTableRow(table) { - if (table.rows.length > 1) { - table.deleteRow(-1); - } - } - - deleteTableColumn(table) { - const rows = table.rows; - if (rows[0].cells.length > 1) { - for (let i = 0; i < rows.length; i++) { - rows[i].deleteCell(-1); - } - } - } - - deleteTable(event) { - const tableContainer = event.target.closest('div'); - tableContainer.remove(); - } - - // Make images draggable and resizable - makeImagesDraggableAndResizable() { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (!editor) return; - - const images = editor.querySelectorAll('img'); - images.forEach(img => { - // Prevent position changes on click - img.style.position = 'relative'; - img.style.zIndex = '1000'; - img.style.transition = 'none'; // Disable transitions during drag - - // Add resize handles - this.addResizeHandles(img); - - // Add smooth drag event listeners - img.addEventListener('mousedown', this.handleImageMouseDown.bind(this)); - img.addEventListener('mousemove', this.handleImageMouseMove.bind(this)); - img.addEventListener('mouseup', this.handleImageMouseUp.bind(this)); - img.addEventListener('mouseleave', this.handleImageMouseUp.bind(this)); - }); - } - - // Smooth drag handlers for images - handleImageMouseDown(e) { - if (e.target.tagName !== 'IMG') return; - - e.preventDefault(); - this.isDraggingImage = true; - this.dragStartX = e.clientX; - this.dragStartY = e.clientY; - this.dragElement = e.target; - - // Store initial position - const rect = this.dragElement.getBoundingClientRect(); - const editor = this.template.querySelector('.enhanced-editor-content'); - const editorRect = editor.getBoundingClientRect(); - - this.initialLeft = rect.left - editorRect.left; - this.initialTop = rect.top - editorRect.top; - - // Add dragging class for visual feedback - this.dragElement.style.cursor = 'grabbing'; - this.dragElement.style.opacity = '0.8'; - - // Prevent text selection during drag - document.body.style.userSelect = 'none'; - } - - handleImageMouseMove(e) { - if (!this.isDraggingImage || !this.dragElement) return; - - e.preventDefault(); - - const deltaX = e.clientX - this.dragStartX; - const deltaY = e.clientY - this.dragStartY; - - // Update position smoothly - this.dragElement.style.left = (this.initialLeft + deltaX) + 'px'; - this.dragElement.style.top = (this.initialTop + deltaY) + 'px'; - this.dragElement.style.position = 'absolute'; - } - - handleImageMouseUp(e) { - if (!this.isDraggingImage || !this.dragElement) return; - - this.isDraggingImage = false; - - // Restore cursor and opacity - this.dragElement.style.cursor = 'grab'; - this.dragElement.style.opacity = '1'; - - // Re-enable text selection - document.body.style.userSelect = ''; - - // Save undo state after drag - this.saveUndoState(); - - this.dragElement = null; - } - - // Add resize handles to image - addResizeHandles(img) { - const handles = ['nw', 'ne', 'sw', 'se']; - handles.forEach(handle => { - const resizeHandle = document.createElement('div'); - resizeHandle.className = `resize-handle resize-${handle}`; - resizeHandle.style.cssText = ` + this.dragElement = null; + this.dragInitiated = false; + } + // Add resize handles to image + addResizeHandles(img) { + const handles = ["nw", "ne", "sw", "se"]; + handles.forEach((handle) => { + const resizeHandle = document.createElement("div"); + resizeHandle.className = `resize-handle resize-${handle}`; + resizeHandle.style.cssText = ` position: absolute; width: 8px; height: 8px; @@ -4763,692 +12080,1757 @@ export default class PropertyTemplateSelector extends LightningElement { cursor: ${handle}-resize; z-index: 1001; `; - - // Position handles - switch (handle) { - case 'nw': - resizeHandle.style.top = '-4px'; - resizeHandle.style.left = '-4px'; - break; - case 'ne': - resizeHandle.style.top = '-4px'; - resizeHandle.style.right = '-4px'; - break; - case 'sw': - resizeHandle.style.bottom = '-4px'; - resizeHandle.style.left = '-4px'; - break; - case 'se': - resizeHandle.style.bottom = '-4px'; - resizeHandle.style.right = '-4px'; - break; - } - - img.appendChild(resizeHandle); - - // Add resize functionality - resizeHandle.addEventListener('mousedown', (e) => { - e.preventDefault(); - this.startResize(e, img, handle); - }); - }); - } - // Handle image drag start - handleImageDragStart(event) { - event.dataTransfer.setData('text/plain', 'image'); - event.dataTransfer.effectAllowed = 'move'; - } + // Position handles + switch (handle) { + case "nw": + resizeHandle.style.top = "-4px"; + resizeHandle.style.left = "-4px"; + break; + case "ne": + resizeHandle.style.top = "-4px"; + resizeHandle.style.right = "-4px"; + break; + case "sw": + resizeHandle.style.bottom = "-4px"; + resizeHandle.style.left = "-4px"; + break; + case "se": + resizeHandle.style.bottom = "-4px"; + resizeHandle.style.right = "-4px"; + break; + } - // Handle image drag end - handleImageDragEnd(event) { - // Remove any drag feedback - } + img.appendChild(resizeHandle); - // Start resize operation - startResize(event, target, handle) { - const container = target.classList.contains('draggable-image-container') ? target : target.parentElement; - const startX = event.clientX; - const startY = event.clientY; - const startWidth = container.offsetWidth; - const startHeight = container.offsetHeight; - const startLeft = container.offsetLeft; - const startTop = container.offsetTop; + // Add resize functionality + resizeHandle.addEventListener("mousedown", (e) => { + e.preventDefault(); + this.startResize(e, img, handle); + }); + }); + } - const handleMouseMove = (e) => { - const deltaX = e.clientX - startX; - const deltaY = e.clientY - startY; - - let newWidth = startWidth; - let newHeight = startHeight; - let newLeft = startLeft; - let newTop = startTop; - - switch (handle) { - case 'se': - newWidth = startWidth + deltaX; - newHeight = startHeight + deltaY; - break; - case 'sw': - newWidth = startWidth - deltaX; - newHeight = startHeight + deltaY; - newLeft = startLeft + deltaX; - break; - case 'ne': - newWidth = startWidth + deltaX; - newHeight = startHeight - deltaY; - newTop = startTop + deltaY; - break; - case 'nw': - newWidth = startWidth - deltaX; - newHeight = startHeight - deltaY; - newLeft = startLeft + deltaX; - newTop = startTop + deltaY; - break; - } - - container.style.width = Math.max(50, newWidth) + 'px'; - container.style.height = Math.max(50, newHeight) + 'px'; - container.style.left = newLeft + 'px'; - container.style.top = newTop + 'px'; - }; - - const handleMouseUp = () => { - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleMouseUp); - }; - - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('mouseup', handleMouseUp); - } - - handleAlignLeft() { - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - document.execCommand('justifyLeft', false, null); - this.showSuccess('Text aligned left'); - } else { - this.showError('Please select text first'); - } - } - - handleAlignCenter() { - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - document.execCommand('justifyCenter', false, null); - this.showSuccess('Text aligned center'); - } else { - this.showError('Please select text first'); - } - } - - handleAlignRight() { - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - document.execCommand('justifyRight', false, null); - this.showSuccess('Text aligned right'); - } else { - this.showError('Please select text first'); - } - } - - handleTextColorChange(event) { - const color = event.target.value; - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - document.execCommand('foreColor', false, color); - this.showSuccess(`Text color changed to ${color}`); - } else { - this.showError('Please select text first'); - } - } - - handleBackgroundColorChange(event) { - const color = event.target.value; - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - document.execCommand('hiliteColor', false, color); - this.showSuccess(`Background color changed to ${color}`); - } else { - this.showError('Please select text first'); - } - } - - - - handleIndent() { - const editorContent = this.template.querySelector('.enhanced-editor-content'); - if (!editorContent) { this.showError('Editor not found'); return; } - editorContent.focus(); - // For list contexts, execCommand handles nesting properly - document.execCommand('indent', false, null); - editorContent.dispatchEvent(new Event('input', { bubbles: true })); - } - - handleOutdent() { - const editorContent = this.template.querySelector('.enhanced-editor-content'); - if (!editorContent) { this.showError('Editor not found'); return; } - editorContent.focus(); - document.execCommand('outdent', false, null); - editorContent.dispatchEvent(new Event('input', { bubbles: true })); - } - - handleFontFamilyChange(event) { - const fontFamily = event.target.value; - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - document.execCommand('fontName', false, fontFamily); - this.showSuccess(`Font family changed to ${fontFamily}`); - } else { - this.showError('Please select text first'); - } - } - - handleContentChange() { - // Update the HTML content when user types in the editor - const editorContent = this.template.querySelector('.enhanced-editor-content'); - if (editorContent) { - this.htmlContent = editorContent.innerHTML; - } - } - - openPdfPreview() { - // Get current content from editor - const editorContent = this.template.querySelector('.enhanced-editor-content'); - if (editorContent) { - this.htmlContent = editorContent.innerHTML; - } - this.showPdfPreview = true; - } - - closePdfPreview() { - this.showPdfPreview = false; - } - - generatePdfFromPreview() { - // Close preview and generate PDF - this.showPdfPreview = false; - this.generatePdfSimple(); - } - - // Property insertion functions - insertPropertyName() { - const propertyName = this.propertyData.Name || 'Property Name'; - this.insertTextAtCursor(propertyName); - } - - insertPropertyPrice() { - const price = this.propertyData.Price__c || '$0'; - this.insertTextAtCursor(price); - } - - insertPropertyType() { - const type = this.propertyData.Property_Type__c || 'Property Type'; - this.insertTextAtCursor(type); - } - - insertPropertyBedrooms() { - const bedrooms = this.propertyData.Bedrooms__c || '0'; - this.insertTextAtCursor(bedrooms + ' Bedrooms'); - } - - insertPropertyBathrooms() { - const bathrooms = this.propertyData.Bathrooms__c || '0'; - this.insertTextAtCursor(bathrooms + ' Bathrooms'); - } - - insertPropertySqft() { - const sqft = this.propertyData.Square_Footage__c || '0'; - this.insertTextAtCursor(sqft + ' sq ft'); - } - - insertPropertyAddress() { - const address = this.propertyData.Location__c || 'Property Address'; - this.insertTextAtCursor(address); - } - - insertPropertyDescription() { - const description = this.propertyData.Description_English__c || this.propertyData.pcrm__Description_English__c || this.propertyData.Description__c || 'Property Description'; - // Wrap into paragraphs and basic formatting - const lines = String(description).split(/\n+/).map(l => l.trim()).filter(Boolean); - const html = lines.map(l => `

${l}

`).join(''); - this.insertHtmlAtCursor(html); - } - - // Helper function to insert text at cursor position - insertTextAtCursor(text) { - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - const range = selection.getRangeAt(0); - range.deleteContents(); - const textNode = document.createTextNode(text); - range.insertNode(textNode); - range.setStartAfter(textNode); - range.setEndAfter(textNode); - selection.removeAllRanges(); - selection.addRange(range); - this.showSuccess(`Inserted: ${text}`); - } else { - this.showError('Please place cursor in the editor first'); - } - } - - // Helper to insert HTML at cursor - insertHtmlAtCursor(html) { - const selection = window.getSelection(); - if (!selection.rangeCount) { this.showError('Please place cursor in the editor first'); return; } - const range = selection.getRangeAt(0); - range.deleteContents(); - const temp = document.createElement('div'); - temp.innerHTML = html; - const fragment = document.createDocumentFragment(); - while (temp.firstChild) { - fragment.appendChild(temp.firstChild); - } - range.insertNode(fragment); - // Move caret to end of inserted content - range.collapse(false); - selection.removeAllRanges(); - selection.addRange(range); - const editorContent = this.template.querySelector('.enhanced-editor-content'); - if (editorContent) editorContent.dispatchEvent(new Event('input', { bubbles: true })); - } - - // Setup editor click handler to deselect elements - setupEditorClickHandler() { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor && !editor.hasClickHandler) { - editor.addEventListener('click', (e) => { - // Enhanced image detection - check multiple ways to find images - let clickedImage = null; - - // Method 1: Direct image click - if (e.target.tagName === 'IMG' && e.target.src && e.target.src.trim() !== '') { - clickedImage = e.target; - } - - // Method 2: Click on element containing an image (children) - if (!clickedImage && e.target.querySelector) { - const containedImg = e.target.querySelector('img'); - if (containedImg && containedImg.src && containedImg.src.trim() !== '') { - clickedImage = containedImg; - } - } - - // Method 3: Click on element that is inside a container with an image (parent traversal) - if (!clickedImage) { - let currentElement = e.target; - while (currentElement && currentElement !== editor) { - // Check if current element is an IMG - if (currentElement.tagName === 'IMG' && currentElement.src && currentElement.src.trim() !== '') { - clickedImage = currentElement; - break; - } - // Check if current element contains an IMG - if (currentElement.querySelector && currentElement.querySelector('img')) { - const img = currentElement.querySelector('img'); - if (img && img.src && img.src.trim() !== '') { - clickedImage = img; - break; - } - } - // Check siblings for IMG elements only if current element is positioned - if (currentElement.parentElement && - (currentElement.style.position === 'absolute' || - currentElement.style.position === 'relative' || - currentElement.classList.contains('draggable-element'))) { - const siblingImg = currentElement.parentElement.querySelector('img'); - if (siblingImg && siblingImg.src && siblingImg.src.trim() !== '') { - clickedImage = siblingImg; - break; - } - } - currentElement = currentElement.parentElement; - } - } - - // Method 4: Check for background images in the element hierarchy (enhanced for property cards) - if (!clickedImage) { - let currentElement = e.target; - while (currentElement && currentElement !== editor) { - // Check for background images on any element (not just positioned ones) - const computedStyle = window.getComputedStyle(currentElement); - const backgroundImage = computedStyle.backgroundImage; - - if (backgroundImage && backgroundImage !== 'none' && backgroundImage !== 'initial') { - // Create a virtual IMG element for background images - const virtualImg = document.createElement('img'); - virtualImg.src = backgroundImage.replace(/url\(['"]?(.+?)['"]?\)/, '$1'); - virtualImg.isBackgroundImage = true; - virtualImg.style.backgroundImage = backgroundImage; - virtualImg.originalElement = currentElement; // Store reference to original element - clickedImage = virtualImg; - break; - } - - // Also check if this element has a background image set via CSS classes - if (currentElement.className) { - const classList = currentElement.className.split(' '); - for (let className of classList) { - // Look for common background image class patterns - if (className.includes('bg-') || className.includes('background') || - className.includes('hero') || className.includes('banner') || - className.includes('card') || className.includes('property')) { - - const classStyle = window.getComputedStyle(currentElement); - const classBgImage = classStyle.backgroundImage; - if (classBgImage && classBgImage !== 'none' && classBgImage !== 'initial') { - const virtualImg = document.createElement('img'); - virtualImg.src = classBgImage.replace(/url\(['"]?(.+?)['"]?\)/, '$1'); - virtualImg.isBackgroundImage = true; - virtualImg.style.backgroundImage = classBgImage; - virtualImg.originalElement = currentElement; - clickedImage = virtualImg; - break; - } - } - } - } - - currentElement = currentElement.parentElement; - } - } - - if (clickedImage) { - // Additional validation to ensure we have a valid image - if (clickedImage.tagName === 'IMG' || clickedImage.isBackgroundImage) { - this.handleImageClick(clickedImage, e); - return; - } else { - } - } - - // Reset image click tracking when clicking on non-image areas - this.resetImageClickTracking(); - - // Only deselect if clicking on the editor background or non-editable content - if (e.target === editor || (!e.target.classList.contains('draggable-element') && - !e.target.closest('.draggable-element'))) { - // Remove selection from all draggable elements - const allDraggable = editor.querySelectorAll('.draggable-element, .draggable-image-container, .draggable-table-container'); - allDraggable.forEach(el => { - el.classList.remove('selected'); - // Remove any resize handles - const resizeHandles = el.querySelectorAll('.resize-handle'); - resizeHandles.forEach(handle => handle.remove()); - // Remove any delete buttons - const deleteButtons = el.querySelectorAll('.delete-handle, .delete-image-btn'); - deleteButtons.forEach(btn => btn.remove()); - }); - - // Clear the selected element reference - this.clearSelection(); - } - }); - - // Ensure contenteditable is always enabled - editor.setAttribute('contenteditable', 'true'); - - // Prevent default scroll behavior when selecting draggable elements - editor.addEventListener('selectstart', (e) => { - if (e.target.classList.contains('draggable-element') && - !e.target.classList.contains('draggable-text')) { - e.preventDefault(); - } - }); - - // Prevent focus from jumping to top - editor.addEventListener('focus', (e) => { - e.preventDefault(); - }, true); - - // Add keyboard event handling for undo/redo - editor.addEventListener('keydown', (e) => { - if (e.ctrlKey || e.metaKey) { - if (e.key === 'z' && !e.shiftKey) { - e.preventDefault(); - this.undo(); - } else if (e.key === 'y' || (e.key === 'z' && e.shiftKey)) { - e.preventDefault(); - this.redo(); - } - } - }); - - editor.hasClickHandler = true; - } - } - - // Insert draggable text element - insertDraggableText() { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor) { - this.setupEditorClickHandler(); - const textElement = document.createElement('div'); - textElement.className = 'draggable-element draggable-text'; - textElement.contentEditable = true; - textElement.innerHTML = 'Click to edit text'; - textElement.style.left = '50px'; - textElement.style.top = '50px'; - textElement.style.width = '200px'; - textElement.style.height = '40px'; - textElement.style.zIndex = '1000'; - textElement.style.position = 'absolute'; - - // Add resize handles - this.addResizeHandles(textElement); - - // Add drag functionality - this.makeDraggable(textElement); - - // Focus on the text element after a short delay - setTimeout(() => { - textElement.focus(); - textElement.classList.add('selected'); - }, 100); - - editor.appendChild(textElement); - } - } - - // Show image insertion modal - showImageInsertModal() { - this.showImageModal = true; - this.selectedImageUrl = ''; - this.selectedImageName = ''; - this.uploadedImageData = ''; - this.selectedImageCategory = 'all'; - this.insertButtonDisabled = true; - - // Populate property images from the existing data - this.populatePropertyImages(); + // Handle image drag start + handleImageDragStart(event) { + console.log("=== IMAGE DRAG START ==="); + console.log("Dragged element:", event.target); + console.log("Element src:", event.target.src); + console.log("Element alt:", event.target.alt); + console.log("Element draggable:", event.target.draggable); + + // Store the dragged image element and its properties + this.draggedImageElement = event.target; + this.draggedImageSrc = event.target.src; + this.draggedImageAlt = event.target.alt; + + event.dataTransfer.setData("text/plain", "image"); + event.dataTransfer.effectAllowed = "move"; + + // Add visual feedback + event.target.style.opacity = "0.5"; + event.target.style.transform = "scale(0.95)"; + event.target.style.transition = "all 0.2s ease"; + + console.log("✅ Drag state stored:", { + element: this.draggedImageElement, + src: this.draggedImageSrc, + alt: this.draggedImageAlt + }); + + // Test: Add a simple alert to confirm drag is working + console.log("🚀 DRAG STARTED - Check if you see this message!"); + } + + // Handle image drag end + handleImageDragEnd(event) { + // Remove visual feedback + if (this.draggedImageElement) { + this.draggedImageElement.style.opacity = ""; + this.draggedImageElement.style.transform = ""; } - // Populate property images array - populatePropertyImages() { - this.propertyImages = []; - - // Add images from imagesByCategory - Object.keys(this.imagesByCategory).forEach(category => { - this.imagesByCategory[category].forEach(image => { - this.propertyImages.push({ - url: image.url, - name: image.title || image.name || `${category} Image`, - category: category.toLowerCase() - }); - }); + // Clear drag state + this.draggedImageElement = null; + this.draggedImageSrc = null; + this.draggedImageAlt = null; + } + + // Handle image drag over + handleImageDragOver(event) { + console.log("🔄 DRAG OVER EVENT!"); + event.preventDefault(); + event.dataTransfer.dropEffect = "move"; + + // Find the target image element (could be the target itself or a child) + let targetImage = event.target; + if (event.target.tagName !== 'IMG') { + targetImage = event.target.querySelector('img'); + } + + // Add visual feedback to drop target + if (targetImage && targetImage.tagName === 'IMG' && targetImage !== this.draggedImageElement) { + console.log("🎯 Valid drop target detected:", targetImage); + targetImage.style.border = "3px dashed #007bff"; + targetImage.style.borderRadius = "8px"; + targetImage.style.transition = "all 0.2s ease"; + } + } + + // Handle image drag leave + handleImageDragLeave(event) { + // Find the target image element (could be the target itself or a child) + let targetImage = event.target; + if (event.target.tagName !== 'IMG') { + targetImage = event.target.querySelector('img'); + } + + // Remove visual feedback + if (targetImage && targetImage.tagName === 'IMG') { + targetImage.style.border = ""; + targetImage.style.borderRadius = ""; + } + } + + // Handle image drop for swapping + handleImageDrop(event) { + console.log("🎯 DROP EVENT TRIGGERED!"); + event.preventDefault(); + event.stopPropagation(); + + console.log("=== IMAGE DROP EVENT ==="); + console.log("Event target:", event.target); + console.log("Event target tagName:", event.target.tagName); + console.log("Dragged image element:", this.draggedImageElement); + console.log("Dragged image src:", this.draggedImageSrc); + + // Find the target image element (could be the target itself or a child) + let targetImage = event.target; + if (event.target.tagName !== 'IMG') { + // Look for an img element within the target + targetImage = event.target.querySelector('img'); + console.log("Looking for img in container, found:", targetImage); + } + + // Remove visual feedback + if (targetImage && targetImage.tagName === 'IMG') { + targetImage.style.border = ""; + targetImage.style.borderRadius = ""; + } + + // Check if we're dropping on another image + if (targetImage && + targetImage.tagName === 'IMG' && + this.draggedImageElement && + targetImage !== this.draggedImageElement) { + + console.log("✅ Valid drop detected - performing swap"); + console.log("Target image:", targetImage); + + // Swap the image sources + const targetImageSrc = targetImage.src; + const targetImageAlt = targetImage.alt; + + console.log("Target image src:", targetImageSrc); + console.log("Target image alt:", targetImageAlt); + + // Perform the swap + targetImage.src = this.draggedImageSrc; + targetImage.alt = this.draggedImageAlt; + this.draggedImageElement.src = targetImageSrc; + this.draggedImageElement.alt = targetImageAlt; + + console.log("✅ Images swapped successfully!"); + console.log("New target src:", targetImage.src); + console.log("New dragged src:", this.draggedImageElement.src); + + // Show success message + this.showSuccess("Images swapped successfully!"); + + // Save undo state + this.saveUndoState(); + } else { + console.log("❌ Invalid drop - conditions not met"); + console.log("Target image found:", !!targetImage); + console.log("Is IMG:", targetImage && targetImage.tagName === 'IMG'); + console.log("Has dragged element:", !!this.draggedImageElement); + console.log("Different elements:", targetImage !== this.draggedImageElement); + } + } + + // Start resize operation + startResize(event, target, handle) { + const container = target.classList.contains("draggable-image-container") + ? target + : target.parentElement; + const startX = event.clientX; + const startY = event.clientY; + const startWidth = container.offsetWidth; + const startHeight = container.offsetHeight; + const startLeft = container.offsetLeft; + const startTop = container.offsetTop; + + const handleMouseMove = (e) => { + const deltaX = e.clientX - startX; + const deltaY = e.clientY - startY; + + let newWidth = startWidth; + let newHeight = startHeight; + let newLeft = startLeft; + let newTop = startTop; + + switch (handle) { + case "se": + newWidth = startWidth + deltaX; + newHeight = startHeight + deltaY; + break; + case "sw": + newWidth = startWidth - deltaX; + newHeight = startHeight + deltaY; + newLeft = startLeft + deltaX; + break; + case "ne": + newWidth = startWidth + deltaX; + newHeight = startHeight - deltaY; + newTop = startTop + deltaY; + break; + case "nw": + newWidth = startWidth - deltaX; + newHeight = startHeight - deltaY; + newLeft = startLeft + deltaX; + newTop = startTop + deltaY; + break; + } + + container.style.width = Math.max(50, newWidth) + "px"; + container.style.height = Math.max(50, newHeight) + "px"; + container.style.left = newLeft + "px"; + container.style.top = newTop + "px"; + }; + + const handleMouseUp = () => { + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + } + + handleAlignLeft() { + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + document.execCommand("justifyLeft", false, null); + this.showSuccess("Text aligned left"); + } else { + this.showError("Please select text first"); + } + } + handleAlignCenter() { + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + document.execCommand("justifyCenter", false, null); + this.showSuccess("Text aligned center"); + } else { + this.showError("Please select text first"); + } + } + + handleAlignRight() { + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + document.execCommand("justifyRight", false, null); + this.showSuccess("Text aligned right"); + } else { + this.showError("Please select text first"); + } + } + + handleTextColorChange(event) { + const color = event.target.value; + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + document.execCommand("foreColor", false, color); + this.showSuccess(`Text color changed to ${color}`); + } else { + this.showError("Please select text first"); + } + } + + handleBackgroundColorChange(event) { + const color = event.target.value; + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + document.execCommand("hiliteColor", false, color); + this.showSuccess(`Background color changed to ${color}`); + } else { + this.showError("Please select text first"); + } + } + + handleIndent() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) { + this.showError("Editor not found"); + return; + } + + const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + editor.focus(); + return; + } + + const range = selection.getRangeAt(0); + // If inside a list item, increase nesting by wrapping current LI into a new nested UL + let li = range.commonAncestorContainer; + while (li && li.nodeType === Node.ELEMENT_NODE && li.tagName !== "LI") { + li = li.parentElement; + } + if (li && li.tagName === "LI") { + // Move LI into a nested list if not already first-level child of a nested list + const parentList = li.parentElement; + let prev = li.previousElementSibling; + if (!prev) { + // If no previous sibling, create a new empty LI to hold the nested list + prev = document.createElement("li"); + prev.innerHTML = ""; + parentList.insertBefore(prev, li); + } + let nested = prev.querySelector("ul, ol"); + if (!nested) { + nested = document.createElement(parentList.tagName.toLowerCase()); + prev.appendChild(nested); + } + nested.appendChild(li); + return; + } + + // Otherwise add a visual tab (4 NBSP) at the start of the current block + const getBlock = (node) => { + let n = node.nodeType === Node.TEXT_NODE ? node.parentElement : node; + while (n && !/(P|DIV|LI|H1|H2|H3|H4|H5|H6)/i.test(n.tagName)) { + n = n.parentElement; + } + return n || editor; + }; + const block = getBlock(range.startContainer); + if (!block) return; + const TAB = "\u00A0\u00A0\u00A0\u00A0"; // 4 NBSP + const first = block.firstChild; + if (first && first.nodeType === Node.TEXT_NODE) { + first.textContent = TAB + first.textContent; + } else { + block.insertBefore(document.createTextNode(TAB), first || null); + } + editor.dispatchEvent(new Event("input", { bubbles: true })); + } + + handleOutdent() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) { + this.showError("Editor not found"); + return; + } + + const sel1 = window.getSelection(); + if (!sel1 || sel1.rangeCount === 0) { + editor.focus(); + return; + } + + const range1 = sel1.getRangeAt(0); + // If inside a nested list, move LI up one level + let li = range1.commonAncestorContainer; + while (li && li.nodeType === Node.ELEMENT_NODE && li.tagName !== "LI") { + li = li.parentElement; + } + if (li && li.tagName === "LI") { + const parentList = li.parentElement; // UL/OL + const listContainer = parentList.parentElement; // LI or block + if (listContainer && listContainer.tagName === "LI") { + // Move current LI to be after its parent LI + listContainer.parentElement.insertBefore(li, listContainer.nextSibling); + return; + } + } + + // Otherwise, remove one indentation level (up to 4 NBSP/spaces) at start of the current block + const getBlock = (node) => { + let n = node.nodeType === Node.TEXT_NODE ? node.parentElement : node; + while (n && !/(P|DIV|LI|H1|H2|H3|H4|H5|H6)/i.test(n.tagName)) { + n = n.parentElement; + } + return n || editor; + }; + const block = getBlock(range1.startContainer); + if (!block) return; + const first = block.firstChild; + if (first && first.nodeType === Node.TEXT_NODE) { + // Remove up to 4 leading NBSP/spaces + first.textContent = first.textContent.replace(/^(?:\u00A0|\s){1,4}/, ""); + } + editor.dispatchEvent(new Event("input", { bubbles: true })); + } + + handleFontFamilyChange(event) { + const fontFamily = event.target.value; + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + document.execCommand("fontName", false, fontFamily); + this.showSuccess(`Font family changed to ${fontFamily}`); + } else { + this.showError("Please select text first"); + } + } + handleContentChange() { + // Update the HTML content when user types in the editor + const editorContent = this.template.querySelector( + ".enhanced-editor-content" + ); + if (editorContent) { + this.htmlContent = editorContent.innerHTML; + } + } + + openPdfPreview() { + // Get current content from editor + const editorContent = this.template.querySelector( + ".enhanced-editor-content" + ); + if (editorContent) { + this.htmlContent = editorContent.innerHTML; + } + this.showPdfPreview = true; + } + + closePdfPreview() { + this.showPdfPreview = false; + } + + generatePdfFromPreview() { + // Close preview and generate PDF + this.showPdfPreview = false; + this.generatePdfSimple(); + } + + // Property insertion functions + insertPropertyName() { + this.ensureEditorFocus(); + const propertyName = this.propertyData.propertyName || this.propertyData.Name || "Property Name"; + this.insertTextAtCursor(propertyName + " | "); + } + + insertPropertyPrice() { + this.ensureEditorFocus(); + + // Get the first selected pricing field + let price = "Price on Request"; + + if (this.pricingSelection.includeSalePriceMin && this.propertyData.salePriceMin && this.propertyData.salePriceMin !== "N/A") { + price = this.propertyData.salePriceMin; + } else if (this.pricingSelection.includeRentPriceMin && this.propertyData.rentPriceMin && this.propertyData.rentPriceMin !== "N/A") { + price = this.propertyData.rentPriceMin; + } else if (this.propertyData.price && this.propertyData.price !== "N/A") { + price = this.propertyData.price; + } + + this.insertTextAtCursor(price + " | "); + } + + insertPropertyType() { + this.ensureEditorFocus(); + const type = this.propertyData.propertyType || this.propertyData.Property_Type__c || "Property Type"; + this.insertTextAtCursor(type + " | "); + } + + insertPropertyBathrooms() { + const bathrooms = this.propertyData.bathrooms || this.propertyData.Bathrooms__c || "0"; + this.insertTextAtCursor(bathrooms + " | "); + } + + insertPropertySqft() { + const sqft = this.propertyData.area || this.propertyData.size || this.propertyData.Square_Footage__c || "0"; + this.insertTextAtCursor(sqft + " | "); + } + + insertPropertyAddress() { + const address = this.propertyData.location || this.propertyData.Location__c || "Property Address"; + this.insertTextAtCursor(address + " | "); + } + + insertPropertyDescription() { + this.ensureEditorFocus(); + const description = this.propertyData.descriptionEnglish || this.propertyData.Description_English__c || this.propertyData.Description__c || "Property Description"; + // Wrap into paragraphs and basic formatting + const lines = String(description) + .split(/\n+/) + .map((l) => l.trim()) + .filter(Boolean); + const html = lines.map((l) => `

${l}

`).join(""); + this.insertHtmlAtCursor(html); + } + + // Additional property insertion methods + insertPropertyBedrooms() { + const bedrooms = this.propertyData.bedrooms || this.propertyData.Bedrooms__c || "0"; + this.insertTextAtCursor(bedrooms + " | "); + } + + insertPropertyStatus() { + const status = this.propertyData.status || this.propertyData.Status__c || "Available"; + this.insertTextAtCursor(status + " | "); + } + + insertPropertyCity() { + const city = this.propertyData.city || this.propertyData.City__c || "City"; + this.insertTextAtCursor(city + " | "); + } + + insertPropertyCommunity() { + const community = this.propertyData.community || this.propertyData.Community__c || "Community"; + this.insertTextAtCursor(community + " | "); + } + + insertPropertyFloor() { + const floor = this.propertyData.floor || this.propertyData.Floor__c || "N/A"; + this.insertTextAtCursor(floor + " | "); + } + + insertPropertyBuildYear() { + const buildYear = this.propertyData.buildYear || this.propertyData.yearBuilt || this.propertyData.Build_Year__c || "N/A"; + this.insertTextAtCursor(buildYear + " | "); + } + + insertPropertyParking() { + const parking = this.propertyData.parking || this.propertyData.parkingSpaces || this.propertyData.Parking_Spaces__c || "N/A"; + this.insertTextAtCursor(parking + " | "); + } + + insertPropertyFurnished() { + const furnished = this.propertyData.furnished || this.propertyData.furnishing || this.propertyData.Furnished__c || "N/A"; + this.insertTextAtCursor(furnished + " | "); + } + + insertPropertyOfferingType() { + const offeringType = this.propertyData.offeringType || this.propertyData.Offering_Type__c || "N/A"; + this.insertTextAtCursor(offeringType + " | "); + } + + insertPropertyRentPrice() { + const rentPrice = this.propertyData.rentPriceMin || this.propertyData.Rent_Price_min__c || "N/A"; + this.insertTextAtCursor(rentPrice + " | "); + } + + insertPropertySalePrice() { + const salePrice = this.propertyData.salePriceMin || this.propertyData.Sale_Price_min__c || "N/A"; + this.insertTextAtCursor(salePrice + " | "); + } + + insertPropertyContactName() { + const contactName = this.propertyData.contactName || this.propertyData.Contact_Name__c || "Contact Name"; + this.insertTextAtCursor(contactName + " | "); + } + + insertPropertyContactEmail() { + const contactEmail = this.propertyData.contactEmail || this.propertyData.Contact_Email__c || "contact@example.com"; + this.insertTextAtCursor(contactEmail + " | "); + } + + insertPropertyContactPhone() { + const contactPhone = this.propertyData.contactPhone || this.propertyData.Contact_Phone__c || "N/A"; + this.insertTextAtCursor(contactPhone + " | "); + } + + insertPropertyReferenceNumber() { + const referenceNumber = this.propertyData.referenceNumber || this.propertyData.Reference_Number__c || "REF-001"; + this.insertTextAtCursor(referenceNumber + " | "); + } + + insertPropertyTitle() { + const title = this.propertyData.titleEnglish || this.propertyData.Title_English__c || "Property Title"; + this.insertTextAtCursor(title + " | "); + } + + insertPropertyLocality() { + const locality = this.propertyData.locality || this.propertyData.Locality__c || "Locality"; + this.insertTextAtCursor(locality + " | "); + } + + insertPropertyTower() { + const tower = this.propertyData.tower || this.propertyData.Tower__c || "N/A"; + this.insertTextAtCursor(tower + " | "); + } + + insertPropertyUnitNumber() { + const unitNumber = this.propertyData.unitNumber || this.propertyData.Unit_Number__c || "N/A"; + this.insertTextAtCursor(unitNumber + " | "); + } + + insertPropertyRentAvailableFrom() { + const rentAvailableFrom = this.propertyData.rentAvailableFrom || this.propertyData.Rent_Available_From__c || "N/A"; + this.insertTextAtCursor(rentAvailableFrom + " | "); + } + + insertPropertyRentAvailableTo() { + const rentAvailableTo = this.propertyData.rentAvailableTo || this.propertyData.Rent_Available_To__c || "N/A"; + this.insertTextAtCursor(rentAvailableTo + " | "); + } + + // Helper function to ensure editor is focused + ensureEditorFocus() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor) { + editor.focus(); + } + } + + // Dynamic font sizing based on content length and viewport + applyDynamicFontSizing() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) return; + + // Get all text elements in the editor + const textElements = editor.querySelectorAll('h1, h2, h3, h4, h5, h6, p, span, div'); + + textElements.forEach(element => { + const text = element.textContent || element.innerText || ''; + const textLength = text.length; + const viewportWidth = window.innerWidth; + + // Remove existing content classes + element.classList.remove('content-short', 'content-medium', 'content-long'); + + // Determine content scale class based on text length + if (textLength < 50) { + element.classList.add('content-short'); + } else if (textLength < 200) { + element.classList.add('content-medium'); + } else { + element.classList.add('content-long'); + } + + // Add viewport-based classes + element.classList.remove('viewport-small', 'viewport-large', 'viewport-xl'); + if (viewportWidth < 480) { + element.classList.add('viewport-small'); + } else if (viewportWidth > 1600) { + element.classList.add('viewport-xl'); + } else if (viewportWidth > 1200) { + element.classList.add('viewport-large'); + } + }); + } + + // Enhanced content change handler with dynamic font sizing + handleContentChangeWithDynamicSizing() { + this.handleContentChange(); + // Apply dynamic font sizing after a short delay to ensure DOM is updated + setTimeout(() => { + this.applyDynamicFontSizing(); + }, 100); + } + + // Helper function to get center position of the screen for element insertion + getCenterPosition() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) { + return { x: 50, y: 50 }; // Default position if editor not found + } + + const editorRect = editor.getBoundingClientRect(); + + // Get screen center position + const screenCenterX = window.innerWidth / 2; + const screenCenterY = window.innerHeight / 2 - (window.innerHeight * 0.5); // Bring up by 60vh + + // Calculate position relative to editor + const x = screenCenterX - editorRect.left; + const y = screenCenterY - editorRect.top; + + // Offset by half the element size to center it properly + // Default element sizes: images (300x200), text (150x40), tables (400x150) + const elementWidth = 300; // Default width for most elements + const elementHeight = 200; // Default height for most elements + + const finalX = x - (elementWidth / 2); + const finalY = y - (elementHeight / 2); + + // Ensure position is within editor bounds + const maxX = editorRect.width - elementWidth; + const maxY = editorRect.height - elementHeight; + + return { + x: Math.max(10, Math.min(finalX, maxX)), + y: Math.max(10, Math.min(finalY, maxY)) + }; + } + + // Helper function to get center position for specific element types + getCenterPositionForElement(elementType = 'default') { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) { + return { x: 50, y: 50 }; + } + + const editorRect = editor.getBoundingClientRect(); + + // Get screen center position + const screenCenterX = window.innerWidth / 2; + const screenCenterY = window.innerHeight / 2 - (window.innerHeight * 0.6); // Bring up by 60vh + + // Calculate position relative to editor + const x = screenCenterX - editorRect.left; + const y = screenCenterY - editorRect.top; + + // Define element-specific dimensions + let elementWidth, elementHeight; + switch(elementType) { + case 'image': + elementWidth = 300; + elementHeight = 200; + break; + case 'text': + elementWidth = 150; + elementHeight = 40; + break; + case 'table': + elementWidth = 400; + elementHeight = 150; + break; + default: + elementWidth = 300; + elementHeight = 200; + } + + const finalX = x - (elementWidth / 2); + const finalY = y - (elementHeight / 2); + + // Ensure position is within editor bounds + const maxX = editorRect.width - elementWidth; + const maxY = editorRect.height - elementHeight; + + return { + x: Math.max(10, Math.min(finalX, maxX)), + y: Math.max(10, Math.min(finalY, maxY)) + }; + } + + // Helper function to insert text at cursor position + insertTextAtCursor(text) { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) { + this.showError("Editor not found"); + return; + } + + const selection = window.getSelection(); + let range; + + if (selection.rangeCount > 0) { + // Use existing cursor position + range = selection.getRangeAt(0); + } else { + // No cursor position, place at end of editor content + range = document.createRange(); + range.selectNodeContents(editor); + range.collapse(false); // Move to end + } + + range.deleteContents(); + const textNode = document.createTextNode(text); + range.insertNode(textNode); + range.setStartAfter(textNode); + range.setEndAfter(textNode); + selection.removeAllRanges(); + selection.addRange(range); + + // Focus the editor to ensure cursor is visible + editor.focus(); + + this.showSuccess(`Inserted: ${text}`); + } + // Helper to insert HTML at cursor + insertHtmlAtCursor(html) { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) { + this.showError("Editor not found"); + return; + } + + const selection = window.getSelection(); + let range; + + if (selection.rangeCount > 0) { + // Use existing cursor position + range = selection.getRangeAt(0); + } else { + // No cursor position, place at end of editor content + range = document.createRange(); + range.selectNodeContents(editor); + range.collapse(false); // Move to end + } + + range.deleteContents(); + const temp = document.createElement("div"); + temp.innerHTML = html; + const fragment = document.createDocumentFragment(); + while (temp.firstChild) { + fragment.appendChild(temp.firstChild); + } + range.insertNode(fragment); + // Move caret to end of inserted content + range.collapse(false); + selection.removeAllRanges(); + selection.addRange(range); + + // Focus the editor to ensure cursor is visible + editor.focus(); + + const editorContent = this.template.querySelector( + ".enhanced-editor-content" + ); + if (editorContent) + editorContent.dispatchEvent(new Event("input", { bubbles: true })); + } + + // NEW: Coordinate-based image detection with visual feedback + detectImageAtCoordinates(clickX, clickY, editor) { + console.log("=== NEW COORDINATE-BASED IMAGE DETECTION ==="); + console.log("Click coordinates:", { x: clickX, y: clickY }); + + let clickedImage = null; + let bestMatch = null; + let bestScore = -1; + + // Clear any existing visual feedback + this.clearImageSelectionFeedback(); + + // Method 1: Find all images and background images in the editor + const allImages = []; + + // Get all IMG elements + const imgElements = editor.querySelectorAll("img"); + imgElements.forEach(img => { + if (img.src && img.src.trim() !== "") { + allImages.push({ + element: img, + type: 'img', + rect: img.getBoundingClientRect(), + src: img.src }); + } + }); + + // Get all elements with background images + const allElements = editor.querySelectorAll("*"); + allElements.forEach(el => { + const computedStyle = window.getComputedStyle(el); + const backgroundImage = computedStyle.backgroundImage; + + if (backgroundImage && backgroundImage !== "none" && backgroundImage !== "initial") { + const rect = el.getBoundingClientRect(); + if (rect.width > 0 && rect.height > 0) { // Only consider visible elements + const virtualImg = document.createElement("img"); + virtualImg.src = backgroundImage.replace(/url\(['"]?(.+?)['"]?\)/, "$1"); + virtualImg.isBackgroundImage = true; + virtualImg.originalElement = el; + + allImages.push({ + element: virtualImg, + type: 'background', + rect: rect, + src: virtualImg.src, + originalElement: el + }); + } + } + }); + + console.log(`Found ${allImages.length} images to check`); + + // Method 2: Score each image based on click proximity and element importance + allImages.forEach((imageData, index) => { + const rect = imageData.rect; + const centerX = rect.left + rect.width / 2; + const centerY = rect.top + rect.height / 2; + + // Calculate distance from click point to image center + const distance = Math.sqrt( + Math.pow(clickX - centerX, 2) + Math.pow(clickY - centerY, 2) + ); + + // Calculate if click is within image bounds + const isWithinBounds = ( + clickX >= rect.left && + clickX <= rect.right && + clickY >= rect.top && + clickY <= rect.bottom + ); + + // Calculate image area (larger images get higher priority) + const area = rect.width * rect.height; + + // Calculate importance score based on element classes and position + let importanceScore = 0; + const originalElement = imageData.originalElement || imageData.element; + + if (originalElement) { + const className = originalElement.className || ''; - // Add real property images if available - if (this.realPropertyImages && this.realPropertyImages.length > 0) { - this.realPropertyImages.forEach(image => { - this.propertyImages.push({ - url: image.url || image.Url__c, - name: image.name || image.Name || 'Property Image', - category: (image.category || image.Category__c || 'none').toLowerCase() - }); - }); + // Hero section images get highest priority + if (className.includes('hero') || className.includes('p1-image-side') || + className.includes('p2-image') || className.includes('cover-page') || + className.includes('banner')) { + importanceScore += 1000; } + // Content images get medium priority + if (className.includes('content') || className.includes('main') || + className.includes('section') || className.includes('card') || + className.includes('property') || className.includes('image')) { + importanceScore += 500; + } + + // Footer images get negative priority (should be avoided) + if (className.includes('footer') || className.includes('page-footer') || + className.includes('p1-footer') || className.includes('agent-footer') || + className.includes('company-logo') || className.includes('footer-logo')) { + importanceScore -= 1000; + } + } + + // Calculate final score + let score = 0; + + if (isWithinBounds) { + // Click is within image bounds - high priority + score = 1000 + importanceScore + (area / 1000); // Larger images get slight bonus + } else { + // Click is outside image bounds - lower priority based on distance + score = Math.max(0, 1000 - distance + importanceScore + (area / 1000)); + } + + console.log(`Image ${index}:`, { + type: imageData.type, + src: imageData.src.substring(0, 50) + '...', + className: originalElement?.className || 'N/A', + isWithinBounds, + distance: Math.round(distance), + area: Math.round(area), + importanceScore, + score: Math.round(score) + }); + + if (score > bestScore) { + bestScore = score; + bestMatch = imageData; + } + }); + + // Method 3: If we have a good match, use it + if (bestMatch && bestScore > 0) { + clickedImage = bestMatch.element; + console.log("✅ BEST MATCH FOUND:", { + type: bestMatch.type, + src: bestMatch.src.substring(0, 50) + '...', + score: Math.round(bestScore), + isWithinBounds: ( + clickX >= bestMatch.rect.left && + clickX <= bestMatch.rect.right && + clickY >= bestMatch.rect.top && + clickY <= bestMatch.rect.bottom + ) + }); + + // Add visual feedback to show which image is selected + this.showImageSelectionFeedback(bestMatch.originalElement || bestMatch.element); + } else { + console.log("❌ NO SUITABLE IMAGE FOUND"); } + + return clickedImage; + } + + // Add visual feedback to show which image is selected + showImageSelectionFeedback(element) { + if (!element) return; - // Close image insertion modal - closeImageModal() { - this.showImageModal = false; - this.selectedImageUrl = ''; - this.selectedImageName = ''; - this.uploadedImageData = ''; - this.insertButtonDisabled = true; - - // Clear any selections - document.querySelectorAll('.property-image-item').forEach(item => { - item.classList.remove('selected'); - }); - - // Reset upload area - this.resetUploadArea(); + // Remove any existing feedback + this.clearImageSelectionFeedback(); + + // Add highlight to the selected element + element.style.outline = "3px solid #007bff"; + element.style.outlineOffset = "2px"; + element.style.boxShadow = "0 0 10px rgba(0, 123, 255, 0.5)"; + + // Store reference for cleanup + this.selectedElementForFeedback = element; + + console.log("Visual feedback added to selected image"); + } + + // Clear visual feedback + clearImageSelectionFeedback() { + if (this.selectedElementForFeedback) { + this.selectedElementForFeedback.style.outline = ""; + this.selectedElementForFeedback.style.outlineOffset = ""; + this.selectedElementForFeedback.style.boxShadow = ""; + this.selectedElementForFeedback = null; } - - // Set image source (property or local) - setImageSource(event) { - const source = event.target.dataset.source; - this.imageSource = source; - this.selectedImageUrl = ''; - this.selectedImageName = ''; - this.uploadedImageData = ''; - this.insertButtonDisabled = true; - - // Clear any selections - document.querySelectorAll('.property-image-item').forEach(item => { - item.classList.remove('selected'); - }); - - // Reset upload area - this.resetUploadArea(); - } - - // Select image category - selectImageCategory(event) { - const category = event.target.dataset.category; - this.selectedImageCategory = category; - - // Update button states - document.querySelectorAll('.category-btn').forEach(btn => { - btn.classList.remove('active'); - if (btn.dataset.category === category) { - btn.classList.add('active'); - } - }); - } - - // Select property image - selectPropertyImage(event) { - // Get the image URL from the closest element with data-image-url - const imageItem = event.target.closest('[data-image-url]'); - const imageUrl = imageItem ? imageItem.dataset.imageUrl : null; - const imageName = event.target.alt || event.target.textContent || 'Property Image'; - - - if (!imageUrl) { + } + + // Setup editor click handler to deselect elements + setupEditorClickHandler() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor && !editor.hasClickHandler) { + editor.addEventListener("click", (e) => { + console.log("=== NEW COORDINATE-BASED CLICK HANDLER ==="); + console.log("Click coordinates:", { x: e.clientX, y: e.clientY }); + console.log("Target element:", e.target); + + // Prevent default behavior + e.preventDefault(); + e.stopPropagation(); + + try { + // Use the new coordinate-based image detection + const clickedImage = this.detectImageAtCoordinates(e.clientX, e.clientY, editor); + + if (!clickedImage) { + console.log("❌ NO IMAGE FOUND AT CLICK LOCATION"); + this.showError("No image found at the clicked location. Please click directly on an image."); return; + } + + console.log("✅ IMAGE FOUND:", clickedImage); + console.log("Image details:", { + tagName: clickedImage.tagName, + src: clickedImage.src, + className: clickedImage.className, + isBackgroundImage: clickedImage.isBackgroundImage, + originalElement: clickedImage.originalElement + }); + + // Store the selected image element + this.selectedImageElement = clickedImage; + + // Check if this is the same image as the last click + const isSameImage = this.isSameImageAsLast(clickedImage); + + if (isSameImage) { + this.imageClickCount++; + console.log("Same image clicked, count:", this.imageClickCount); + } else { + this.imageClickCount = 1; + this.lastClickedImage = clickedImage; + console.log("Different image clicked, reset count to 1"); + } + + // Set timeout to reset counter + this.clickTimeout = setTimeout(() => { + this.imageClickCount = 0; + this.lastClickedImage = null; + this.clearImageSelectionFeedback(); + console.log("Click counter reset"); + }, 1000); + + // Check if image can be replaced + if (this.canReplaceImage(clickedImage)) { + console.log("Image can be replaced, opening replacement dialog"); + this.openImageReplacement(clickedImage); + } else { + console.log("Image cannot be replaced, showing error"); + this.showError("This image cannot be replaced. Please try clicking on a different image."); + } + } catch (error) { + console.error("Error in image detection:", error); + this.showError("An error occurred while processing the image click."); } - - // Remove previous selection - document.querySelectorAll('.property-image-item').forEach(item => { - item.classList.remove('selected'); + }); + + editor.hasClickHandler = true; + } + } + + addDeselectFunctionality() { + for (let child of currentElement.children) { + elementsToCheck.push(child); + } + } + + for (let element of elementsToCheck) { + const computedStyle = window.getComputedStyle(element); + const backgroundImage = computedStyle.backgroundImage; + + if ( + backgroundImage && + backgroundImage !== "none" && + backgroundImage !== "initial" + ) { + // Check if this is a hero section + const isHeroSection = element.classList.contains('hero') || + element.classList.contains('p1-image-side') || + element.classList.contains('p2-image') || + element.classList.contains('cover-page') || + element.classList.contains('banner'); + + if (isHeroSection) { + // Create a virtual IMG element for hero background images + const virtualImg = document.createElement("img"); + virtualImg.src = backgroundImage.replace( + /url\(['"]?(.+?)['"]?\)/, + "$1" + ); + virtualImg.isBackgroundImage = true; + virtualImg.style.backgroundImage = backgroundImage; + virtualImg.originalElement = element; + heroBackgroundImage = virtualImg; + console.log("✅ Method 4 SUCCESS: Found HERO background image in clicked area", virtualImg); + break; // Prioritize hero sections - break immediately + } else if (element === currentElement) { + // Store the clicked element's background image as priority + const virtualImg = document.createElement("img"); + virtualImg.src = backgroundImage.replace( + /url\(['"]?(.+?)['"]?\)/, + "$1" + ); + virtualImg.isBackgroundImage = true; + virtualImg.style.backgroundImage = backgroundImage; + virtualImg.originalElement = element; + clickedElementBackgroundImage = virtualImg; + console.log("✅ Method 4: Found clicked element background image", virtualImg); + } else { + // Store other background images for fallback + if (!otherBackgroundImage) { + const virtualImg = document.createElement("img"); + virtualImg.src = backgroundImage.replace( + /url\(['"]?(.+?)['"]?\)/, + "$1" + ); + virtualImg.isBackgroundImage = true; + virtualImg.style.backgroundImage = backgroundImage; + virtualImg.originalElement = element; + otherBackgroundImage = virtualImg; + console.log("✅ Method 4: Found other background image", virtualImg); + } + } + } + } + + // If no hero image found in clicked area, traverse up the DOM tree + if (!heroBackgroundImage) { + console.log("No hero image found in clicked area, traversing up DOM tree..."); + currentElement = e.target.parentElement; + + while (currentElement && currentElement !== editor) { + // Check for background images on any element (not just positioned ones) + const computedStyle = window.getComputedStyle(currentElement); + const backgroundImage = computedStyle.backgroundImage; + + if ( + backgroundImage && + backgroundImage !== "none" && + backgroundImage !== "initial" + ) { + // Check if this is a hero section + const isHeroSection = currentElement.classList.contains('hero') || + currentElement.classList.contains('p1-image-side') || + currentElement.classList.contains('p2-image') || + currentElement.classList.contains('cover-page') || + currentElement.classList.contains('banner'); + + if (isHeroSection) { + // Create a virtual IMG element for hero background images + const virtualImg = document.createElement("img"); + virtualImg.src = backgroundImage.replace( + /url\(['"]?(.+?)['"]?\)/, + "$1" + ); + virtualImg.isBackgroundImage = true; + virtualImg.style.backgroundImage = backgroundImage; + virtualImg.originalElement = currentElement; + heroBackgroundImage = virtualImg; + console.log("✅ Method 4 SUCCESS: Found HERO background image in parent", virtualImg); + break; // Prioritize hero sections - break immediately + } else { + // Store other background images for fallback + if (!otherBackgroundImage) { + const virtualImg = document.createElement("img"); + virtualImg.src = backgroundImage.replace( + /url\(['"]?(.+?)['"]?\)/, + "$1" + ); + virtualImg.isBackgroundImage = true; + virtualImg.style.backgroundImage = backgroundImage; + virtualImg.originalElement = currentElement; + otherBackgroundImage = virtualImg; + console.log("✅ Method 4: Found other background image in parent", virtualImg); + } + } + } + + // Also check if this element has a background image set via CSS classes + if (currentElement.className) { + const classList = currentElement.className.split(" "); + for (let className of classList) { + // Look for common background image class patterns + if ( + className.includes("bg-") || + className.includes("background") || + className.includes("hero") || + className.includes("banner") || + className.includes("card") || + className.includes("property") || + className.includes("p1-image-side") || + className.includes("p2-image") + ) { + const classStyle = window.getComputedStyle(currentElement); + const classBgImage = classStyle.backgroundImage; + if ( + classBgImage && + classBgImage !== "none" && + classBgImage !== "initial" + ) { + // Check if this is a hero section + const isHeroSection = currentElement.classList.contains('hero') || + currentElement.classList.contains('p1-image-side') || + currentElement.classList.contains('p2-image'); + + if (isHeroSection) { + const virtualImg = document.createElement("img"); + virtualImg.src = classBgImage.replace( + /url\(['"]?(.+?)['"]?\)/, + "$1" + ); + virtualImg.isBackgroundImage = true; + virtualImg.style.backgroundImage = classBgImage; + virtualImg.originalElement = currentElement; + heroBackgroundImage = virtualImg; + console.log("✅ Method 4 SUCCESS: Found HERO CSS class background image", virtualImg); + break; // Prioritize hero sections - break immediately + } else if (!otherBackgroundImage) { + const virtualImg = document.createElement("img"); + virtualImg.src = classBgImage.replace( + /url\(['"]?(.+?)['"]?\)/, + "$1" + ); + virtualImg.isBackgroundImage = true; + virtualImg.style.backgroundImage = classBgImage; + virtualImg.originalElement = currentElement; + otherBackgroundImage = virtualImg; + console.log("✅ Method 4: Found other CSS class background image", virtualImg); + } + } + } + } + } + + currentElement = currentElement.parentElement; + } + } + + // Use hero background image if found, otherwise fall back to clicked element's background image, then other background image + if (heroBackgroundImage) { + clickedImage = heroBackgroundImage; + console.log("✅ Method 4 SUCCESS: Using HERO background image", clickedImage); + } else if (clickedElementBackgroundImage) { + clickedImage = clickedElementBackgroundImage; + console.log("✅ Method 4 SUCCESS: Using clicked element background image", clickedImage); + } else if (otherBackgroundImage) { + clickedImage = otherBackgroundImage; + console.log("✅ Method 4 SUCCESS: Using other background image", clickedImage); + } else { + console.log("❌ Method 4 FAILED: No background image found"); + } + } else { + console.log("❌ Method 4 SKIPPED: Already found image"); + } + + // Method 5: Enhanced detection for layered images with z-index and overlapping elements + console.log("=== METHOD 5: Layered image detection ==="); + if (!clickedImage) { + const clickPoint = { x: e.clientX, y: e.clientY }; + const elementsAtPoint = document.elementsFromPoint(clickPoint.x, clickPoint.y); + + console.log("Elements at click point:", elementsAtPoint.map(el => ({ + tagName: el.tagName, + className: el.className, + zIndex: window.getComputedStyle(el).zIndex, + position: window.getComputedStyle(el).position + }))); + + // Look for images in the elements at the click point + for (let element of elementsAtPoint) { + // Skip if element is the editor itself + if (element === editor) continue; + + // Check if this element is an image + if (element.tagName === "IMG" && element.src && element.src.trim() !== "") { + clickedImage = element; + console.log("Found layered IMG element:", element); + break; + } + + // Check if this element contains an image + const imgInElement = element.querySelector && element.querySelector("img"); + if (imgInElement && imgInElement.src && imgInElement.src.trim() !== "") { + clickedImage = imgInElement; + console.log("Found layered container with IMG:", element, imgInElement); + break; + } + + // Check for background images in layered elements + const computedStyle = window.getComputedStyle(element); + const bgImage = computedStyle.backgroundImage; + if (bgImage && bgImage !== "none" && bgImage !== "initial" && bgImage.includes("url(")) { + // Check if this is a hero section + const isHeroSection = element.classList.contains('hero') || + element.classList.contains('p1-image-side') || + element.classList.contains('p2-image'); + + if (isHeroSection) { + const virtualImg = document.createElement("img"); + virtualImg.src = bgImage.replace(/url\(['"]?([^'"]*)['"]?\)/, "$1"); + virtualImg.isBackgroundImage = true; + virtualImg.style.backgroundImage = bgImage; + virtualImg.originalElement = element; + clickedImage = virtualImg; + console.log("Found layered HERO background image:", element, bgImage); + break; // Prioritize hero sections - break immediately + } else if (!clickedImage) { + const virtualImg = document.createElement("img"); + virtualImg.src = bgImage.replace(/url\(['"]?([^'"]*)['"]?\)/, "$1"); + virtualImg.isBackgroundImage = true; + virtualImg.style.backgroundImage = bgImage; + virtualImg.originalElement = element; + clickedImage = virtualImg; + console.log("Found layered background image:", element, bgImage); + } + } + + // Check for pseudo-elements with background images (::before, ::after) + try { + const beforeBg = window.getComputedStyle(element, '::before').backgroundImage; + const afterBg = window.getComputedStyle(element, '::after').backgroundImage; + + if (beforeBg && beforeBg !== "none" && beforeBg !== "initial" && beforeBg.includes("url(")) { + const virtualImg = document.createElement("img"); + virtualImg.src = beforeBg.replace(/url\(['"]?([^'"]*)['"]?\)/, "$1"); + virtualImg.isBackgroundImage = true; + virtualImg.style.backgroundImage = beforeBg; + virtualImg.originalElement = element; + virtualImg.isPseudoElement = 'before'; + clickedImage = virtualImg; + console.log("Found layered pseudo-element background (::before):", element, beforeBg); + break; + } + + if (afterBg && afterBg !== "none" && afterBg !== "initial" && afterBg.includes("url(")) { + const virtualImg = document.createElement("img"); + virtualImg.src = afterBg.replace(/url\(['"]?([^'"]*)['"]?\)/, "$1"); + virtualImg.isBackgroundImage = true; + virtualImg.style.backgroundImage = afterBg; + virtualImg.originalElement = element; + virtualImg.isPseudoElement = 'after'; + clickedImage = virtualImg; + console.log("Found layered pseudo-element background (::after):", element, afterBg); + break; + } + } catch (error) { + // Pseudo-element access might fail in some browsers, continue + console.log("Could not access pseudo-elements for:", element); + } + } + } + + // Final result + console.log("=== FINAL DETECTION RESULT ==="); + if (clickedImage) { + console.log("🎯 IMAGE DETECTED:", { + tagName: clickedImage.tagName, + alt: clickedImage.alt, + src: clickedImage.src, + isBackgroundImage: clickedImage.isBackgroundImage, + originalElement: clickedImage.originalElement + }); + + // Additional validation to ensure we have a valid image + if ( + clickedImage.tagName === "IMG" || + clickedImage.isBackgroundImage + ) { + console.log("✅ VALID IMAGE - Calling handleImageClick"); + this.handleImageClick(clickedImage, e); + return; + } else { + console.log("❌ INVALID IMAGE TYPE - Not calling handleImageClick"); + } + } else { + console.log("❌ NO IMAGE DETECTED - All methods failed"); + } + + // Reset image click tracking when clicking on non-image areas + this.resetImageClickTracking(); + // Only deselect if clicking on the editor background or non-editable content + if ( + e.target === editor || + (!e.target.classList.contains("draggable-element") && + !e.target.closest(".draggable-element")) + ) { + // Remove selection from all draggable elements + const allDraggable = editor.querySelectorAll( + ".draggable-element, .draggable-image-container, .draggable-table-container" + ); + allDraggable.forEach((el) => { + el.classList.remove("selected"); + // Remove any resize handles + const resizeHandles = el.querySelectorAll(".resize-handle"); + resizeHandles.forEach((handle) => handle.remove()); + // Remove any delete buttons + const deleteButtons = el.querySelectorAll( + ".delete-handle, .delete-image-btn" + ); + deleteButtons.forEach((btn) => btn.remove()); + }); + + // Clear the selected element reference + this.clearSelection(); + } + }); + + // Ensure contenteditable is always enabled + editor.setAttribute("contenteditable", "true"); + + // Prevent default scroll behavior when selecting draggable elements + editor.addEventListener("selectstart", (e) => { + if ( + e.target.classList.contains("draggable-element") && + !e.target.classList.contains("draggable-text") + ) { + e.preventDefault(); + } + }); + + // Prevent focus from jumping to top + editor.addEventListener( + "focus", + (e) => { + e.preventDefault(); + }, + true + ); + + // Add keyboard event handling for undo/redo + editor.addEventListener("keydown", (e) => { + if (e.ctrlKey || e.metaKey) { + if (e.key === "z" && !e.shiftKey) { + e.preventDefault(); + this.undo(); + } else if (e.key === "y" || (e.key === "z" && e.shiftKey)) { + e.preventDefault(); + this.redo(); + } + } + }); + + editor.hasClickHandler = true; + } + } + + addDeselectFunctionality() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor || editor.hasDeselectHandler) return; + + editor.addEventListener( + "click", + (e) => { + // Only deselect if we're NOT clicking on: + // 1. Images or image containers + // 2. Resize handles + // 3. Delete buttons + // 4. Any draggable elements + + const isImageClick = + e.target.tagName === "IMG" || + e.target.closest(".draggable-image-container") || + e.target.closest(".draggable-table-container") || + e.target.classList.contains("resize-handle") || + e.target.classList.contains("delete-handle") || + e.target.closest(".resize-handle") || + e.target.closest(".delete-handle"); + + if (!isImageClick) { + this.deselectAllElements(); + } + }, + true + ); // Use capture phase to run before your existing handlers + + editor.hasDeselectHandler = true; + } + // Keep the deselectAllElements method as I suggested + deselectAllElements() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) return; + + const allDraggable = editor.querySelectorAll( + ".draggable-element, .draggable-image-container, .draggable-table-container" + ); + + allDraggable.forEach((el) => { + el.classList.remove("selected"); + el.style.border = ""; + el.style.boxShadow = ""; + + const resizeHandles = el.querySelectorAll(".resize-handle"); + resizeHandles.forEach((handle) => handle.remove()); + + const deleteButtons = el.querySelectorAll( + ".delete-handle, .delete-image-btn" + ); + deleteButtons.forEach((btn) => btn.remove()); + }); + + this.selectedElement = null; + } + + // Insert draggable text element + insertDraggableText() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor) { + this.setupEditorClickHandler(); + + // Get center position for insertion + const centerPos = this.getCenterPositionForElement('text'); + + const textElement = document.createElement("div"); + textElement.className = "draggable-element draggable-text"; + textElement.contentEditable = true; + textElement.innerHTML = "Click to edit text"; + textElement.style.left = `${centerPos.x}px`; + textElement.style.top = `${centerPos.y}px`; + textElement.style.width = "200px"; + textElement.style.height = "40px"; + textElement.style.zIndex = "1000"; + textElement.style.position = "absolute"; + + // Add resize handles + this.addResizeHandles(textElement); + + // Add drag functionality + this.makeDraggable(textElement); + + // Focus on the text element after a short delay + setTimeout(() => { + textElement.focus(); + textElement.classList.add("selected"); + }, 100); + + editor.appendChild(textElement); + } + } + + // Show image insertion modal + showImageInsertModal() { + this.showImageModal = true; + this.selectedImageUrl = ""; + this.selectedImageName = ""; + this.uploadedImageData = ""; + this.selectedImageCategory = "all"; + this.insertButtonDisabled = true; + + // Populate property images from the existing data + this.populatePropertyImages(); + } + + // Populate property images array + populatePropertyImages() { + this.propertyImages = []; + + // Add images from imagesByCategory + Object.keys(this.imagesByCategory).forEach((category) => { + this.imagesByCategory[category].forEach((image) => { + this.propertyImages.push({ + url: image.url, + name: image.title || image.name || `${category} Image`, + category: category.toLowerCase(), }); - - // Add selection to clicked item - const targetItem = event.target.closest('.property-image-item'); - if (targetItem) { - targetItem.classList.add('selected'); - } - - // Force reactivity by creating new objects - this.selectedImageUrl = imageUrl; - this.selectedImageName = imageName; - this.uploadedImageData = ''; + }); + }); + + // Add real property images if available + if (this.realPropertyImages && this.realPropertyImages.length > 0) { + this.realPropertyImages.forEach((image) => { + this.propertyImages.push({ + url: image.url || image.Url__c, + name: image.name || image.Name || "Property Image", + category: ( + image.category || + image.Category__c || + "none" + ).toLowerCase(), + }); + }); + } + } + // Close image insertion modal + closeImageModal() { + this.showImageModal = false; + this.selectedImageUrl = ""; + this.selectedImageName = ""; + this.uploadedImageData = ""; + this.insertButtonDisabled = true; + + // Clear any selections + document.querySelectorAll(".property-image-item").forEach((item) => { + item.classList.remove("selected"); + }); + + // Reset upload area + this.resetUploadArea(); + } + // Set image source (property or local) + setImageSource(event) { + const source = event.target.dataset.source; + this.imageSource = source; + this.selectedImageUrl = ""; + this.selectedImageName = ""; + this.uploadedImageData = ""; + this.insertButtonDisabled = true; + + // Clear any selections + document.querySelectorAll(".property-image-item").forEach((item) => { + item.classList.remove("selected"); + }); + + // Reset upload area + this.resetUploadArea(); + } + + // Select image category + selectImageCategory(event) { + const category = event.target.dataset.category; + this.selectedImageCategory = category; + + // Update button states + document.querySelectorAll(".category-btn").forEach((btn) => { + btn.classList.remove("active"); + if (btn.dataset.category === category) { + btn.classList.add("active"); + } + }); + } + + // Select property image + selectPropertyImage(event) { + // Get the image URL from the closest element with data-image-url + const imageItem = event.target.closest("[data-image-url]"); + const imageUrl = imageItem ? imageItem.dataset.imageUrl : null; + const imageName = + event.target.alt || event.target.textContent || "Property Image"; + + if (!imageUrl) { + return; + } + + // Remove previous selection + document.querySelectorAll(".property-image-item").forEach((item) => { + item.classList.remove("selected"); + }); + + // Add selection to clicked item + const targetItem = event.target.closest(".property-image-item"); + if (targetItem) { + targetItem.classList.add("selected"); + } + + // Force reactivity by creating new objects + this.selectedImageUrl = imageUrl; + this.selectedImageName = imageName; + this.uploadedImageData = ""; + this.insertButtonDisabled = false; + + // Log current state for debugging + this.logCurrentState(); + + // Reset upload area if we're on local tab + if (this.imageSource === "local") { + this.resetUploadArea(); + } + + // Force a re-render by updating a tracked property + this.forceRerender(); + } + // Reset upload area to default state + resetUploadArea() { + const uploadArea = this.template.querySelector(".upload-area"); + if (uploadArea) { + // Remove existing preview if any + const existingPreview = uploadArea.querySelector( + ".uploaded-image-preview" + ); + if (existingPreview) { + existingPreview.remove(); + } + + // Show upload content again + const uploadContent = uploadArea.querySelector(".upload-content"); + if (uploadContent) { + uploadContent.style.display = "flex"; + } + } + } + + // Trigger file upload for main image modal + triggerFileUpload() { + const fileInput = this.template.querySelector(".file-input"); + if (fileInput) { + fileInput.click(); + } else { + } + } + + // Handle file upload + handleFileUpload(event) { + const file = event.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + this.uploadedImageData = e.target.result; + this.selectedImageUrl = e.target.result; + this.selectedImageName = file.name; this.insertButtonDisabled = false; - - + // Log current state for debugging this.logCurrentState(); - - // Reset upload area if we're on local tab - if (this.imageSource === 'local') { - this.resetUploadArea(); - } - + + // Update the upload area to show selected image + this.updateUploadAreaWithSelectedImage(e.target.result, file.name); + // Force a re-render by updating a tracked property this.forceRerender(); + }; + reader.readAsDataURL(file); } - - // Reset upload area to default state - resetUploadArea() { - const uploadArea = this.template.querySelector('.upload-area'); - if (uploadArea) { - // Remove existing preview if any - const existingPreview = uploadArea.querySelector('.uploaded-image-preview'); - if (existingPreview) { - existingPreview.remove(); - } - - // Show upload content again - const uploadContent = uploadArea.querySelector('.upload-content'); - if (uploadContent) { - uploadContent.style.display = 'flex'; - } - } - } - - // Trigger file upload for main image modal - triggerFileUpload() { - const fileInput = this.template.querySelector('.file-input'); - if (fileInput) { - fileInput.click(); - } else { - } - } - - // Handle file upload - handleFileUpload(event) { - const file = event.target.files[0]; - if (file) { - const reader = new FileReader(); - reader.onload = (e) => { - this.uploadedImageData = e.target.result; - this.selectedImageUrl = e.target.result; - this.selectedImageName = file.name; - this.insertButtonDisabled = false; - - - // Log current state for debugging - this.logCurrentState(); - - // Update the upload area to show selected image - this.updateUploadAreaWithSelectedImage(e.target.result, file.name); - - // Force a re-render by updating a tracked property - this.forceRerender(); - }; - reader.readAsDataURL(file); - } - } - - // Update upload area to show selected image - updateUploadAreaWithSelectedImage(imageUrl, fileName) { - const uploadArea = this.template.querySelector('.upload-area'); - if (uploadArea) { - // Remove existing preview if any - const existingPreview = uploadArea.querySelector('.uploaded-image-preview'); - if (existingPreview) { - existingPreview.remove(); - } - - // Create preview container - const previewContainer = document.createElement('div'); - previewContainer.className = 'uploaded-image-preview'; - previewContainer.style.cssText = ` + } + + // Update upload area to show selected image + updateUploadAreaWithSelectedImage(imageUrl, fileName) { + const uploadArea = this.template.querySelector(".upload-area"); + if (uploadArea) { + // Remove existing preview if any + const existingPreview = uploadArea.querySelector( + ".uploaded-image-preview" + ); + if (existingPreview) { + existingPreview.remove(); + } + + // Create preview container + const previewContainer = document.createElement("div"); + previewContainer.className = "uploaded-image-preview"; + previewContainer.style.cssText = ` position: relative; width: 100%; max-width: 200px; @@ -5458,22 +13840,30 @@ export default class PropertyTemplateSelector extends LightningElement { border: 2px solid #4f46e5; box-shadow: 0 4px 12px rgba(79, 70, 229, 0.2); `; - - // Create image element - const img = document.createElement('img'); - img.src = imageUrl; - img.alt = fileName; - img.style.cssText = ` + + // Create image element + const img = document.createElement("img"); + img.src = imageUrl; + img.alt = fileName; + img.draggable = true; // Enable dragging + img.style.cssText = ` width: 100%; height: auto; display: block; max-height: 150px; object-fit: cover; `; - - // Create file name overlay - const fileNameOverlay = document.createElement('div'); - fileNameOverlay.style.cssText = ` + + // Add drag and drop listeners for image swapping + img.addEventListener("dragstart", this.handleImageDragStart.bind(this)); + img.addEventListener("dragend", this.handleImageDragEnd.bind(this)); + img.addEventListener("dragover", this.handleImageDragOver.bind(this)); + img.addEventListener("dragleave", this.handleImageDragLeave.bind(this)); + img.addEventListener("drop", this.handleImageDrop.bind(this)); + + // Create file name overlay + const fileNameOverlay = document.createElement("div"); + fileNameOverlay.style.cssText = ` position: absolute; bottom: 0; left: 0; @@ -5484,836 +13874,1091 @@ export default class PropertyTemplateSelector extends LightningElement { font-size: 12px; font-weight: 500; `; - fileNameOverlay.textContent = fileName; - - previewContainer.appendChild(img); - previewContainer.appendChild(fileNameOverlay); - - // Replace upload content with preview - const uploadContent = uploadArea.querySelector('.upload-content'); - if (uploadContent) { - uploadContent.style.display = 'none'; - } - - uploadArea.appendChild(previewContainer); - - // Add click handler to change image - uploadArea.onclick = () => { - this.triggerFileUpload(); - }; - } + fileNameOverlay.textContent = fileName; + + previewContainer.appendChild(img); + previewContainer.appendChild(fileNameOverlay); + // Replace upload content with preview + const uploadContent = uploadArea.querySelector(".upload-content"); + if (uploadContent) { + uploadContent.style.display = "none"; + } + + uploadArea.appendChild(previewContainer); + + // Add click handler to change image + uploadArea.onclick = () => { + this.triggerFileUpload(); + }; } - - // Handle insert button click with debugging - handleInsertButtonClick() { - this.logCurrentState(); - this.insertSelectedImage(); + } + + // Handle insert button click with debugging + handleInsertButtonClick() { + this.logCurrentState(); + this.insertSelectedImage(); + } + // Insert selected image + insertSelectedImage() { + // Check if we have a valid image URL + const imageUrl = this.selectedImageUrl || this.uploadedImageData; + const imageName = this.selectedImageName || "Uploaded Image"; + + if (this.insertButtonDisabled || !imageUrl) { + alert("Please select an image first"); + return; } - - // Insert selected image - insertSelectedImage() { - - // Check if we have a valid image URL - const imageUrl = this.selectedImageUrl || this.uploadedImageData; - const imageName = this.selectedImageName || 'Uploaded Image'; - - if (this.insertButtonDisabled || !imageUrl) { - alert('Please select an image first'); - return; - } - - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor) { + + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor) { + // Save undo state before making changes + this.saveUndoState(); + this.setupEditorClickHandler(); + + // Get center position for insertion + const centerPos = this.getCenterPositionForElement('image'); + + // Create draggable image container + const imageContainer = document.createElement("div"); + imageContainer.className = "draggable-image-container"; + imageContainer.style.left = `${centerPos.x}px`; + imageContainer.style.top = `${centerPos.y}px`; + imageContainer.style.width = "200px"; + imageContainer.style.height = "150px"; + imageContainer.style.zIndex = "1000"; + imageContainer.style.position = "absolute"; + imageContainer.style.overflow = "hidden"; + imageContainer.style.border = "none"; + imageContainer.style.cursor = "move"; + imageContainer.style.userSelect = "none"; + imageContainer.style.boxSizing = "border-box"; + imageContainer.style.borderRadius = "4px"; + + // Create image element + const img = document.createElement("img"); + img.src = imageUrl; + img.alt = imageName; + img.draggable = true; // Enable dragging + img.style.width = "100%"; + img.style.height = "100%"; + img.style.objectFit = "cover"; + + // Add drag and drop listeners for image swapping + img.addEventListener("dragstart", this.handleImageDragStart.bind(this)); + img.addEventListener("dragend", this.handleImageDragEnd.bind(this)); + img.addEventListener("dragover", this.handleImageDragOver.bind(this)); + img.addEventListener("dragleave", this.handleImageDragLeave.bind(this)); + img.addEventListener("drop", this.handleImageDrop.bind(this)); + img.style.display = "block"; + img.style.border = "none"; + img.style.outline = "none"; + img.style.borderRadius = "4px"; + + imageContainer.appendChild(img); + + // Add resize handles + this.addResizeHandles(imageContainer); + + // Add delete handle + this.addDeleteHandle(imageContainer); + + // Add drag functionality + this.makeDraggable(imageContainer); + + // Add click to select functionality + imageContainer.addEventListener("click", (e) => { + e.stopPropagation(); + this.selectDraggableElement(imageContainer); + }); + + // Select the image after a short delay + setTimeout(() => { + this.selectDraggableElement(imageContainer); + }, 100); + + editor.appendChild(imageContainer); + + // Close modal + this.closeImageModal(); + } + } + // Insert draggable image element + insertDraggableImage() { + const input = document.createElement("input"); + input.type = "file"; + input.accept = "image/*"; + input.onchange = (e) => { + const file = e.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (event) => { + const editor = this.template.querySelector( + ".enhanced-editor-content" + ); + if (editor) { // Save undo state before making changes this.saveUndoState(); this.setupEditorClickHandler(); - // Create draggable image container - const imageContainer = document.createElement('div'); - imageContainer.className = 'draggable-image-container'; - imageContainer.style.left = '50px'; - imageContainer.style.top = '50px'; - imageContainer.style.width = '200px'; - imageContainer.style.height = '150px'; - imageContainer.style.zIndex = '1000'; - imageContainer.style.position = 'absolute'; - imageContainer.style.overflow = 'hidden'; - imageContainer.style.border = '2px solid transparent'; - imageContainer.style.cursor = 'move'; - imageContainer.style.userSelect = 'none'; - - // Create image element - const img = document.createElement('img'); - img.src = imageUrl; - img.alt = imageName; - img.style.width = '100%'; - img.style.height = '100%'; - img.style.objectFit = 'cover'; - img.style.display = 'block'; + // Get center position for insertion + const centerPos = this.getCenterPositionForElement('image'); + const imageContainer = document.createElement("div"); + imageContainer.className = "draggable-image-container"; + imageContainer.style.left = `${centerPos.x}px`; + imageContainer.style.top = `${centerPos.y}px`; + imageContainer.style.width = "200px"; + imageContainer.style.height = "150px"; + imageContainer.style.zIndex = "1000"; + imageContainer.style.position = "absolute"; + imageContainer.style.overflow = "hidden"; + imageContainer.style.border = "none"; + imageContainer.style.boxSizing = "border-box"; + imageContainer.style.borderRadius = "4px"; + + const img = document.createElement("img"); + img.src = event.target.result; + img.className = "draggable-image"; + img.alt = "Inserted Image"; + img.style.width = "100%"; + img.style.height = "100%"; + img.style.objectFit = "cover"; + img.style.border = "none"; + img.style.outline = "none"; + img.style.borderRadius = "4px"; + imageContainer.appendChild(img); - + // Add resize handles this.addResizeHandles(imageContainer); - - // Add delete handle - this.addDeleteHandle(imageContainer); - + // Add drag functionality this.makeDraggable(imageContainer); - - // Add click to select functionality - imageContainer.addEventListener('click', (e) => { - e.stopPropagation(); - this.selectDraggableElement(imageContainer); - }); - + // Select the image after a short delay setTimeout(() => { - this.selectDraggableElement(imageContainer); + imageContainer.classList.add("selected"); }, 100); - + editor.appendChild(imageContainer); - - // Close modal - this.closeImageModal(); - } - } - - // Insert draggable image element - insertDraggableImage() { - const input = document.createElement('input'); - input.type = 'file'; - input.accept = 'image/*'; - input.onchange = (e) => { - const file = e.target.files[0]; - if (file) { - const reader = new FileReader(); - reader.onload = (event) => { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor) { - // Save undo state before making changes - this.saveUndoState(); - this.setupEditorClickHandler(); - const imageContainer = document.createElement('div'); - imageContainer.className = 'draggable-element'; - imageContainer.style.left = '50px'; - imageContainer.style.top = '50px'; - imageContainer.style.width = '200px'; - imageContainer.style.height = '150px'; - imageContainer.style.zIndex = '1000'; - imageContainer.style.position = 'absolute'; - imageContainer.style.overflow = 'hidden'; - - const img = document.createElement('img'); - img.src = event.target.result; - img.className = 'draggable-image'; - img.alt = 'Inserted Image'; - img.style.width = '100%'; - img.style.height = '100%'; - img.style.objectFit = 'cover'; - - imageContainer.appendChild(img); - - // Add resize handles - this.addResizeHandles(imageContainer); - - // Add drag functionality - this.makeDraggable(imageContainer); - - // Select the image after a short delay - setTimeout(() => { - imageContainer.classList.add('selected'); - }, 100); - - editor.appendChild(imageContainer); - } - }; - reader.readAsDataURL(file); - } + } }; - input.click(); - } + reader.readAsDataURL(file); + } + }; + input.click(); + } - // Add resize handles to element - addResizeHandles(element) { - // Avoid duplicate handles - const existing = element.querySelectorAll('.resize-handle'); - if (existing && existing.length > 0) return; + // Add resize handles to element + addResizeHandles(element) { + // Avoid duplicate handles + const existing = element.querySelectorAll(".resize-handle"); + if (existing && existing.length > 0) return; - const handles = ['nw', 'ne', 'sw', 'se', 'n', 's', 'w', 'e']; - handles.forEach(direction => { - const handle = document.createElement('div'); - handle.className = `resize-handle ${direction}`; - handle.style.position = 'absolute'; - handle.style.width = '8px'; - handle.style.height = '8px'; - handle.style.background = '#6c63ff'; - handle.style.border = '2px solid white'; - handle.style.borderRadius = '50%'; - handle.style.zIndex = '1001'; - handle.addEventListener('mousedown', (e) => this.startResize(e, element, direction)); - element.appendChild(handle); + const handles = ["nw", "ne", "sw", "se", "n", "s", "w", "e"]; + handles.forEach((direction) => { + const handle = document.createElement("div"); + handle.className = `resize-handle ${direction}`; + handle.style.position = "absolute"; + handle.style.width = "8px"; + handle.style.height = "8px"; + handle.style.background = "#6c63ff"; + handle.style.border = "2px solid white"; + handle.style.borderRadius = "50%"; + handle.style.zIndex = "1001"; + handle.addEventListener("mousedown", (e) => + this.startResize(e, element, direction) + ); + element.appendChild(handle); + }); + } + + // Make element draggable + makeDraggable(element) { + let isDragging = false; + let startX, startY, startLeft, startTop; + let dragStarted = false; + + // Handle mousedown on the element (not on resize handles) + const handleMouseDown = (e) => { + if (e.target.classList.contains("resize-handle")) return; + + isDragging = true; + dragStarted = false; + element.classList.add("selected"); + + // Remove selection from other elements + const editor = element.closest(".enhanced-editor-content"); + if (editor) { + const allDraggable = editor.querySelectorAll(".draggable-element"); + allDraggable.forEach((el) => { + if (el !== element) el.classList.remove("selected"); }); - } + } - // Make element draggable - makeDraggable(element) { - let isDragging = false; - let startX, startY, startLeft, startTop; - let dragStarted = false; + startX = e.clientX; + startY = e.clientY; + startLeft = parseInt(element.style.left) || 0; + startTop = parseInt(element.style.top) || 0; - // Handle mousedown on the element (not on resize handles) - const handleMouseDown = (e) => { - if (e.target.classList.contains('resize-handle')) return; - - isDragging = true; - dragStarted = false; - element.classList.add('selected'); - - // Remove selection from other elements - const editor = element.closest('.enhanced-editor-content'); - if (editor) { - const allDraggable = editor.querySelectorAll('.draggable-element'); - allDraggable.forEach(el => { - if (el !== element) el.classList.remove('selected'); - }); - } - - startX = e.clientX; - startY = e.clientY; - startLeft = parseInt(element.style.left) || 0; - startTop = parseInt(element.style.top) || 0; - - e.preventDefault(); - e.stopPropagation(); - - // Prevent scrolling while dragging - document.body.style.overflow = 'hidden'; - }; + if (editor) { + editor.initialScrollLeft = editor.scrollLeft; + editor.initialScrollTop = editor.scrollTop; + } - const handleMouseMove = (e) => { - if (!isDragging) return; - - const deltaX = e.clientX - startX; - const deltaY = e.clientY - startY; - - // Only start dragging if mouse moved more than 5px - if (!dragStarted && (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5)) { - dragStarted = true; - element.classList.add('dragging'); - } - - if (dragStarted) { - const editor = element.closest('.enhanced-editor-content'); - const editorRect = editor ? editor.getBoundingClientRect() : { left: 0, top: 0, width: window.innerWidth, height: window.innerHeight }; - - // Calculate new position relative to editor - let newLeft = startLeft + deltaX; - let newTop = startTop + deltaY; - - // Keep element within editor bounds - use scrollHeight for full template height - const maxWidth = editor ? editor.clientWidth : editorRect.width; - const maxHeight = editor ? editor.scrollHeight : editorRect.height; - - newLeft = Math.max(0, Math.min(newLeft, maxWidth - element.offsetWidth)); - newTop = Math.max(0, Math.min(newTop, maxHeight - element.offsetHeight)); - - element.style.left = newLeft + 'px'; - element.style.top = newTop + 'px'; - element.style.position = 'absolute'; - } - - e.preventDefault(); - e.stopPropagation(); - }; + e.preventDefault(); + e.stopPropagation(); - const handleMouseUp = () => { - if (isDragging) { - isDragging = false; - dragStarted = false; - element.classList.remove('dragging'); - - // Restore scrolling - document.body.style.overflow = ''; - } - }; + // Prevent scrolling while dragging + document.body.style.overflow = "hidden"; + }; + const handleMouseMove = (e) => { + if (!isDragging) return; - element.addEventListener('mousedown', handleMouseDown); - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('mouseup', handleMouseUp); + const deltaX = e.clientX - startX; + const deltaY = e.clientY - startY; - // Handle click to select without dragging - element.addEventListener('click', (e) => { - if (!dragStarted) { - e.stopPropagation(); - element.classList.add('selected'); - - // Remove selection from other elements - const editor = element.closest('.enhanced-editor-content'); - if (editor) { - const allDraggable = editor.querySelectorAll('.draggable-element'); - allDraggable.forEach(el => { - if (el !== element) el.classList.remove('selected'); - }); - } - } - }); + // Only start dragging if mouse moved more than 5px + if (!dragStarted && (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5)) { + dragStarted = true; + element.classList.add("dragging"); + } - // Handle text editing for text elements - if (element.classList.contains('draggable-text')) { - element.addEventListener('dblclick', (e) => { - if (!dragStarted) { - e.stopPropagation(); - element.focus(); - element.style.cursor = 'text'; - } - }); + if (dragStarted) { + const editor = element.closest(".enhanced-editor-content"); + const editorRect = editor + ? editor.getBoundingClientRect() + : { + left: 0, + top: 0, + width: window.innerWidth, + height: window.innerHeight, + }; - element.addEventListener('input', (e) => { - e.stopPropagation(); - }); + // Calculate new position relative to editor + // let newLeft = startLeft + deltaX; + // let newTop = startTop + deltaY; - element.addEventListener('keydown', (e) => { - e.stopPropagation(); - }); + // Calculate new position relative to editor (FIXED for smooth scroll) + // Use existing 'editor' and 'editorRect' variables declared above to avoid redeclaration + let newLeft = startLeft + deltaX; + let newTop = startTop + deltaY; + + // Account for editor scroll position for smooth dragging + if (editor) { + const currentScrollLeft = editor.scrollLeft; + const currentScrollTop = editor.scrollTop; + + // Adjust position based on scroll changes since drag started + const scrollDeltaX = + currentScrollLeft - (editor.initialScrollLeft || 0); + const scrollDeltaY = + currentScrollTop - (editor.initialScrollTop || 0); + + newLeft -= scrollDeltaX; + newTop -= scrollDeltaY; } - } - // Start resize operation - startResize(e, element, direction) { - e.preventDefault(); + // Keep element within editor bounds - use scrollHeight for full template height + const maxWidth = editor ? editor.clientWidth : editorRect.width; + const maxHeight = editor ? editor.scrollHeight : editorRect.height; + + newLeft = Math.max( + 0, + Math.min(newLeft, maxWidth - element.offsetWidth) + ); + newTop = Math.max( + 0, + Math.min(newTop, maxHeight - element.offsetHeight) + ); + + element.style.left = newLeft + "px"; + element.style.top = newTop + "px"; + element.style.position = "absolute"; + } + + e.preventDefault(); + e.stopPropagation(); + }; + + const handleMouseUp = () => { + if (isDragging) { + isDragging = false; + dragStarted = false; + element.classList.remove("dragging"); + + // Restore scrolling + document.body.style.overflow = ""; + } + }; + element.addEventListener("mousedown", handleMouseDown); + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + + // Handle click to select without dragging + element.addEventListener("click", (e) => { + if (!dragStarted) { e.stopPropagation(); - - const startX = e.clientX; - const startY = e.clientY; - const startWidth = parseInt(element.style.width) || element.offsetWidth; - const startHeight = parseInt(element.style.height) || element.offsetHeight; - const startLeft = parseInt(element.style.left) || 0; - const startTop = parseInt(element.style.top) || 0; + element.classList.add("selected"); - // Add resizing class and prevent scrolling - element.classList.add('resizing'); - document.body.style.overflow = 'hidden'; + // Ensure controls (resize + delete) are visible on click + this.addResizeHandles(element); + this.addDeleteButton(element); - const handleMouseMove = (e) => { - e.preventDefault(); - e.stopPropagation(); - - const deltaX = e.clientX - startX; - const deltaY = e.clientY - startY; - - let newWidth = startWidth; - let newHeight = startHeight; - let newLeft = startLeft; - let newTop = startTop; + // Remove selection from other elements + const editor = element.closest(".enhanced-editor-content"); + if (editor) { + const allDraggable = editor.querySelectorAll( + ".draggable-element, .draggable-image-container, .draggable-table-container" + ); + allDraggable.forEach((el) => { + if (el !== element) el.classList.remove("selected"); + }); + } + } + }); - const editor = element.closest('.enhanced-editor-content'); - const editorRect = editor ? editor.getBoundingClientRect() : { width: window.innerWidth, height: window.innerHeight }; - - // Use scrollHeight for full template height - const maxWidth = editor ? editor.clientWidth : editorRect.width; - const maxHeight = editor ? editor.scrollHeight : editorRect.height; + // Handle text editing for text elements + if (element.classList.contains("draggable-text")) { + element.addEventListener("dblclick", (e) => { + if (!dragStarted) { + e.stopPropagation(); + element.focus(); + element.style.cursor = "text"; + } + }); - switch(direction) { - case 'se': - newWidth = Math.max(50, Math.min(startWidth + deltaX, maxWidth - startLeft)); - newHeight = Math.max(20, Math.min(startHeight + deltaY, maxHeight - startTop)); - break; - case 'sw': - newWidth = Math.max(50, startWidth - deltaX); - newHeight = Math.max(20, Math.min(startHeight + deltaY, maxHeight - startTop)); - if (newWidth >= 50) { - newLeft = Math.max(0, startLeft + deltaX); - } - break; - case 'ne': - newWidth = Math.max(50, Math.min(startWidth + deltaX, maxWidth - startLeft)); - newHeight = Math.max(20, startHeight - deltaY); - if (newHeight >= 20) { - newTop = Math.max(0, startTop + deltaY); - } - break; - case 'nw': - newWidth = Math.max(50, startWidth - deltaX); - newHeight = Math.max(20, startHeight - deltaY); - if (newWidth >= 50) { - newLeft = Math.max(0, startLeft + deltaX); - } - if (newHeight >= 20) { - newTop = Math.max(0, startTop + deltaY); - } - break; - case 'e': - newWidth = Math.max(50, Math.min(startWidth + deltaX, maxWidth - startLeft)); - break; - case 'w': - newWidth = Math.max(50, startWidth - deltaX); - if (newWidth >= 50) { - newLeft = Math.max(0, startLeft + deltaX); - } - break; - case 's': - newHeight = Math.max(20, Math.min(startHeight + deltaY, maxHeight - startTop)); - break; - case 'n': - newHeight = Math.max(20, startHeight - deltaY); - if (newHeight >= 20) { - newTop = Math.max(0, startTop + deltaY); - } - break; - } + element.addEventListener("input", (e) => { + e.stopPropagation(); + }); - // Apply the new dimensions and position - element.style.width = newWidth + 'px'; - element.style.height = newHeight + 'px'; - element.style.left = newLeft + 'px'; - element.style.top = newTop + 'px'; - element.style.position = 'absolute'; + element.addEventListener("keydown", (e) => { + e.stopPropagation(); + }); + } + } + + // Start resize operation + startResize(e, element, direction) { + e.preventDefault(); + e.stopPropagation(); + + const startX = e.clientX; + const startY = e.clientY; + const startWidth = parseInt(element.style.width) || element.offsetWidth; + const startHeight = parseInt(element.style.height) || element.offsetHeight; + const startLeft = parseInt(element.style.left) || 0; + const startTop = parseInt(element.style.top) || 0; + + // Add resizing class and prevent scrolling + element.classList.add("resizing"); + document.body.style.overflow = "hidden"; + + const handleMouseMove = (e) => { + e.preventDefault(); + e.stopPropagation(); + + const deltaX = e.clientX - startX; + const deltaY = e.clientY - startY; + + let newWidth = startWidth; + let newHeight = startHeight; + let newLeft = startLeft; + let newTop = startTop; + + const editor = element.closest(".enhanced-editor-content"); + const editorRect = editor + ? editor.getBoundingClientRect() + : { width: window.innerWidth, height: window.innerHeight }; + // Use scrollHeight for full template height + const maxWidth = editor ? editor.clientWidth : editorRect.width; + const maxHeight = editor ? editor.scrollHeight : editorRect.height; + + switch (direction) { + case "se": + newWidth = Math.max( + 50, + Math.min(startWidth + deltaX, maxWidth - startLeft) + ); + newHeight = Math.max( + 20, + Math.min(startHeight + deltaY, maxHeight - startTop) + ); + break; + case "sw": + newWidth = Math.max(50, startWidth - deltaX); + newHeight = Math.max( + 20, + Math.min(startHeight + deltaY, maxHeight - startTop) + ); + if (newWidth >= 50) { + newLeft = Math.max(0, startLeft + deltaX); + } + break; + case "ne": + newWidth = Math.max( + 50, + Math.min(startWidth + deltaX, maxWidth - startLeft) + ); + newHeight = Math.max(20, startHeight - deltaY); + if (newHeight >= 20) { + newTop = Math.max(0, startTop + deltaY); + } + break; + case "nw": + newWidth = Math.max(50, startWidth - deltaX); + newHeight = Math.max(20, startHeight - deltaY); + if (newWidth >= 50) { + newLeft = Math.max(0, startLeft + deltaX); + } + if (newHeight >= 20) { + newTop = Math.max(0, startTop + deltaY); + } + break; + case "e": + newWidth = Math.max( + 50, + Math.min(startWidth + deltaX, maxWidth - startLeft) + ); + break; + case "w": + newWidth = Math.max(50, startWidth - deltaX); + if (newWidth >= 50) { + newLeft = Math.max(0, startLeft + deltaX); + } + break; + case "s": + newHeight = Math.max( + 20, + Math.min(startHeight + deltaY, maxHeight - startTop) + ); + break; + case "n": + newHeight = Math.max(20, startHeight - deltaY); + if (newHeight >= 20) { + newTop = Math.max(0, startTop + deltaY); + } + break; + } + + // Apply the new dimensions and position + element.style.width = newWidth + "px"; + element.style.height = newHeight + "px"; + element.style.left = newLeft + "px"; + element.style.top = newTop + "px"; + element.style.position = "absolute"; + }; + + const handleMouseUp = () => { + element.classList.remove("resizing"); + document.body.style.overflow = ""; + document.removeEventListener("mousemove", handleMouseMove); + document.removeEventListener("mouseup", handleMouseUp); + }; + + document.addEventListener("mousemove", handleMouseMove); + document.addEventListener("mouseup", handleMouseUp); + } + handleBringForward() { + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + const range = selection.getRangeAt(0); + const element = + range.commonAncestorContainer.nodeType === Node.ELEMENT_NODE + ? range.commonAncestorContainer + : range.commonAncestorContainer.parentElement; + + if (element && element.style) { + const currentZIndex = parseInt(element.style.zIndex) || 0; + element.style.zIndex = currentZIndex + 1; + this.showSuccess(`Z-index increased to ${currentZIndex + 1}`); + } + } else { + this.showError("Please select an element first"); + } + } + + handleSendBackward() { + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + const range = selection.getRangeAt(0); + const element = + range.commonAncestorContainer.nodeType === Node.ELEMENT_NODE + ? range.commonAncestorContainer + : range.commonAncestorContainer.parentElement; + + if (element && element.style) { + const currentZIndex = parseInt(element.style.zIndex) || 0; + element.style.zIndex = Math.max(0, currentZIndex - 1); + this.showSuccess( + `Z-index decreased to ${Math.max(0, currentZIndex - 1)}` + ); + } + } else { + this.showError("Please select an element first"); + } + } + setZIndex() { + const zIndexInput = this.template.querySelector("#zIndexInput"); + const zIndex = parseInt(zIndexInput.value) || 0; + + const selection = window.getSelection(); + if (selection.rangeCount > 0) { + const range = selection.getRangeAt(0); + const element = + range.commonAncestorContainer.nodeType === Node.ELEMENT_NODE + ? range.commonAncestorContainer + : range.commonAncestorContainer.parentElement; + + if (element && element.style) { + element.style.zIndex = zIndex; + this.showSuccess(`Z-index set to ${zIndex}`); + } + } else { + this.showError("Please select an element first"); + } + } + + // Helper method to make elements draggable + makeDraggable(element) { + let isDragging = false; + let currentX; + let currentY; + let initialX; + let initialY; + let xOffset = 0; + let yOffset = 0; + + element.addEventListener("mousedown", (e) => { + // Only start dragging if clicking on the element itself (not on text inside) + if ( + e.target === element || + (element.classList.contains("draggable-text-box") && + e.target.parentNode === element) + ) { + initialX = e.clientX - xOffset; + initialY = e.clientY - yOffset; + isDragging = true; + element.style.cursor = "grabbing"; + } + }); + + document.addEventListener("mousemove", (e) => { + if (isDragging) { + e.preventDefault(); + currentX = e.clientX - initialX; + currentY = e.clientY - initialY; + xOffset = currentX; + yOffset = currentY; + + element.style.left = currentX + "px"; + element.style.top = currentY + "px"; + } + }); + + document.addEventListener("mouseup", () => { + if (isDragging) { + isDragging = false; + element.style.cursor = element.classList.contains("draggable-text-box") + ? "text" + : "move"; + } + }); + } + // Helper method to make elements resizable + makeResizable(element) { + const resizer = document.createElement("div"); + resizer.className = "resizer"; + resizer.style.position = "absolute"; + resizer.style.width = "10px"; + resizer.style.height = "10px"; + resizer.style.background = "#667eea"; + resizer.style.borderRadius = "50%"; + resizer.style.bottom = "-5px"; + resizer.style.right = "-5px"; + resizer.style.cursor = "se-resize"; + resizer.style.zIndex = "1001"; + + element.appendChild(resizer); + + let isResizing = false; + let startWidth, startHeight, startX, startY; + + resizer.addEventListener("mousedown", (e) => { + isResizing = true; + startX = e.clientX; + startY = e.clientY; + startWidth = parseInt(element.style.width) || element.offsetWidth; + startHeight = parseInt(element.style.height) || element.offsetHeight; + e.stopPropagation(); + }); + + document.addEventListener("mousemove", (e) => { + if (isResizing) { + const newWidth = startWidth + (e.clientX - startX); + const newHeight = startHeight + (e.clientY - startY); + + if (newWidth > 50) element.style.width = newWidth + "px"; + if (newHeight > 30) element.style.height = newHeight + "px"; + } + }); + + document.addEventListener("mouseup", () => { + isResizing = false; + }); + } + + insertText() { + const previewFrame = this.template.querySelector( + ".enhanced-editor-content" + ); + if (previewFrame) { + // Get center position for insertion + const centerPos = this.getCenterPositionForElement('text'); + + // Create draggable and resizable text box + const textBox = document.createElement("div"); + textBox.className = "draggable-text-box"; + textBox.contentEditable = true; + textBox.textContent = "Double-click to edit text"; + textBox.style.position = "absolute"; + textBox.style.left = `${centerPos.x}px`; + textBox.style.top = `${centerPos.y}px`; + textBox.style.width = "150px"; + textBox.style.height = "40px"; + textBox.style.minWidth = "100px"; + textBox.style.minHeight = "30px"; + textBox.style.padding = "8px"; + textBox.style.border = "2px solid #ddd"; + textBox.style.borderRadius = "4px"; + textBox.style.backgroundColor = "white"; + textBox.style.cursor = "text"; + textBox.style.zIndex = "1000"; + textBox.style.fontSize = "14px"; + textBox.style.fontFamily = "Arial, sans-serif"; + textBox.style.color = "#333"; + textBox.style.boxSizing = "border-box"; + textBox.style.outline = "none"; + + // Handle Enter key to keep text in place + textBox.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + e.preventDefault(); + // Insert a line break instead of creating new element + document.execCommand("insertLineBreak", false); + } + }); + + // Handle selection like Word/Google Docs + textBox.addEventListener("click", (e) => { + e.stopPropagation(); + this.selectElement(textBox); + }); + + // Make text box draggable + this.makeDraggable(textBox); + + // Make text box resizable + this.makeResizable(textBox); + + previewFrame.appendChild(textBox); + textBox.focus(); + + // Select the text for easy editing + const range = document.createRange(); + range.selectNodeContents(textBox); + const selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(range); + } + } + insertImage() { + // Prevent multiple simultaneous file dialogs (international standard) + if (this.isFileDialogOpen) { + console.warn('File dialog already open, ignoring duplicate request'); + return; + } + + try { + // Create file input for local image upload with proper validation + const fileInput = document.createElement("input"); + fileInput.type = "file"; + fileInput.accept = "image/jpeg,image/jpg,image/png,image/gif,image/webp"; + fileInput.style.display = "none"; + fileInput.setAttribute('aria-label', 'Select image file to upload'); + + // Set flag to prevent multiple dialogs + this.isFileDialogOpen = true; + + fileInput.onchange = (event) => { + this.isFileDialogOpen = false; + + const file = event.target?.files?.[0]; + if (!file) { + console.log('No file selected'); + return; + } + + // Validate file size (10MB limit - international standard) + const maxSize = 10 * 1024 * 1024; // 10MB + if (file.size > maxSize) { + this.showError('File size must be less than 10MB'); + return; + } + + // Validate file type + const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp']; + if (!allowedTypes.includes(file.type)) { + this.showError('Please select a valid image file (JPEG, PNG, GIF, or WebP)'); + return; + } + + // Show loading state + this.showSuccess('Processing image...'); + + const reader = new FileReader(); + reader.onload = (e) => { + try { + this.createImageElement(e.target.result, file.name); + this.showSuccess('Image inserted successfully!'); + } catch (error) { + console.error('Error creating image element:', error); + this.showError('Failed to insert image. Please try again.'); + } }; - const handleMouseUp = () => { - element.classList.remove('resizing'); - document.body.style.overflow = ''; - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleMouseUp); + reader.onerror = () => { + this.isFileDialogOpen = false; + this.showError('Failed to read image file'); }; - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('mouseup', handleMouseUp); - } + reader.readAsDataURL(file); + }; - handleBringForward() { - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - const range = selection.getRangeAt(0); - const element = range.commonAncestorContainer.nodeType === Node.ELEMENT_NODE - ? range.commonAncestorContainer - : range.commonAncestorContainer.parentElement; - - if (element && element.style) { - const currentZIndex = parseInt(element.style.zIndex) || 0; - element.style.zIndex = currentZIndex + 1; - this.showSuccess(`Z-index increased to ${currentZIndex + 1}`); - } - } else { - this.showError('Please select an element first'); + // Handle dialog cancellation + fileInput.oncancel = () => { + this.isFileDialogOpen = false; + }; + + // Add to DOM, trigger, and remove + document.body.appendChild(fileInput); + fileInput.click(); + + // Clean up after a short delay to ensure dialog has time to open + setTimeout(() => { + if (document.body.contains(fileInput)) { + document.body.removeChild(fileInput); } + }, 100); + + } catch (error) { + this.isFileDialogOpen = false; + console.error('Error in insertImage:', error); + this.showError('Failed to open file dialog'); + } + } + + // Separate method for creating image element (following single responsibility principle) + createImageElement(imageDataUrl, fileName = 'Inserted Image') { + const previewFrame = this.template.querySelector('.enhanced-editor-content'); + if (!previewFrame) { + throw new Error('Preview frame not found'); } - handleSendBackward() { - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - const range = selection.getRangeAt(0); - const element = range.commonAncestorContainer.nodeType === Node.ELEMENT_NODE - ? range.commonAncestorContainer - : range.commonAncestorContainer.parentElement; - - if (element && element.style) { - const currentZIndex = parseInt(element.style.zIndex) || 0; - element.style.zIndex = Math.max(0, currentZIndex - 1); - this.showSuccess(`Z-index decreased to ${Math.max(0, currentZIndex - 1)}`); - } - } else { - this.showError('Please select an element first'); - } - } + // Get center position for insertion + const centerPos = this.getCenterPositionForElement('image'); - setZIndex() { - const zIndexInput = this.template.querySelector('#zIndexInput'); - const zIndex = parseInt(zIndexInput.value) || 0; - - const selection = window.getSelection(); - if (selection.rangeCount > 0) { - const range = selection.getRangeAt(0); - const element = range.commonAncestorContainer.nodeType === Node.ELEMENT_NODE - ? range.commonAncestorContainer - : range.commonAncestorContainer.parentElement; - - if (element && element.style) { - element.style.zIndex = zIndex; - this.showSuccess(`Z-index set to ${zIndex}`); - } - } else { - this.showError('Please select an element first'); - } - } + // Generate unique ID for the image container + const uniqueId = `img_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - // Helper method to make elements draggable - makeDraggable(element) { - let isDragging = false; - let currentX; - let currentY; - let initialX; - let initialY; - let xOffset = 0; - let yOffset = 0; + // Create draggable and resizable image container + const imageContainer = document.createElement("div"); + imageContainer.className = "draggable-image-container"; + imageContainer.id = uniqueId; + imageContainer.setAttribute('data-image-type', 'inserted'); + imageContainer.setAttribute('data-original-filename', fileName); - element.addEventListener('mousedown', (e) => { - // Only start dragging if clicking on the element itself (not on text inside) - if (e.target === element || (element.classList.contains('draggable-text-box') && e.target.parentNode === element)) { - initialX = e.clientX - xOffset; - initialY = e.clientY - yOffset; - isDragging = true; - element.style.cursor = 'grabbing'; - } + // Apply consistent styling with center position + Object.assign(imageContainer.style, { + position: "absolute", + left: `${centerPos.x}px`, + top: `${centerPos.y}px`, + width: "300px", + height: "200px", + cursor: "move", + zIndex: "1000", + border: "2px solid transparent", + borderRadius: "4px", + overflow: "hidden", + transition: "border-color 0.2s ease" + }); + + const img = document.createElement("img"); + img.src = imageDataUrl; + img.alt = fileName; + img.setAttribute('loading', 'lazy'); // Performance optimization + img.draggable = true; // Enable dragging + + Object.assign(img.style, { + width: "100%", + height: "100%", + objectFit: "cover", + borderRadius: "4px", + boxShadow: "0 2px 8px rgba(0,0,0,0.1)", + display: "block" + }); + + // Add drag and drop listeners for image swapping + img.addEventListener("dragstart", this.handleImageDragStart.bind(this)); + img.addEventListener("dragend", this.handleImageDragEnd.bind(this)); + img.addEventListener("dragover", this.handleImageDragOver.bind(this)); + img.addEventListener("dragleave", this.handleImageDragLeave.bind(this)); + img.addEventListener("drop", this.handleImageDrop.bind(this)); + + // Add click handler for selection + imageContainer.addEventListener("click", (e) => { + e.stopPropagation(); + this.selectElement(imageContainer); + }); + + // Add triple-click handler for replacement + imageContainer.addEventListener("click", (e) => { + this.handleImageClick(img, e); + }); + + // Create delete button + const deleteBtn = this.createDeleteButton(imageContainer); + imageContainer.appendChild(deleteBtn); + + // Make container interactive + this.makeDraggable(imageContainer); + this.makeResizable(imageContainer); + + // Append image and container + imageContainer.appendChild(img); + previewFrame.appendChild(imageContainer); + + // Auto-select the newly inserted image + setTimeout(() => { + this.selectElement(imageContainer); + }, 100); + } + + // Create delete button with proper accessibility + createDeleteButton(parentContainer) { + const deleteBtn = document.createElement("button"); + deleteBtn.className = "delete-btn"; + deleteBtn.innerHTML = "×"; + deleteBtn.setAttribute('aria-label', 'Delete image'); + deleteBtn.setAttribute('title', 'Delete image'); + + Object.assign(deleteBtn.style, { + position: "absolute", + top: "-10px", + right: "-10px", + width: "20px", + height: "20px", + borderRadius: "50%", + background: "#ff4757", + color: "white", + border: "none", + cursor: "pointer", + fontSize: "16px", + fontWeight: "bold", + zIndex: "1002", + opacity: "1", + transition: "opacity 0.2s ease", + display: "flex", + alignItems: "center", + justifyContent: "center" + }); + + deleteBtn.onclick = (e) => { + e.stopPropagation(); + e.preventDefault(); + + // Confirm deletion for better UX + if (confirm('Are you sure you want to delete this image?')) { + parentContainer.remove(); + this.showSuccess('Image deleted successfully'); + } + }; + + return deleteBtn; + } + // Helper method to duplicate an image + duplicateImage(originalContainer) { + const previewFrame = this.template.querySelector( + ".enhanced-editor-content" + ); + if (previewFrame) { + const newContainer = originalContainer.cloneNode(true); + newContainer.style.left = + parseInt(originalContainer.style.left) + 20 + "px"; + newContainer.style.top = + parseInt(originalContainer.style.top) + 20 + "px"; + newContainer.style.zIndex = parseInt(originalContainer.style.zIndex) + 1; + + // Reattach event listeners + this.makeDraggable(newContainer); + this.makeResizable(newContainer); + + // Update control panel event listeners + const controlPanel = newContainer.querySelector(".image-control-panel"); + if (controlPanel) { + controlPanel.addEventListener("mouseenter", () => { + controlPanel.style.opacity = "1"; }); - document.addEventListener('mousemove', (e) => { - if (isDragging) { - e.preventDefault(); - currentX = e.clientX - initialX; - currentY = e.clientY - initialY; - xOffset = currentX; - yOffset = currentY; - - element.style.left = currentX + 'px'; - element.style.top = currentY + 'px'; - } + controlPanel.addEventListener("mouseleave", () => { + controlPanel.style.opacity = "1"; }); + } - document.addEventListener('mouseup', () => { - if (isDragging) { - isDragging = false; - element.style.cursor = element.classList.contains('draggable-text-box') ? 'text' : 'move'; - } - }); + previewFrame.appendChild(newContainer); + this.showSuccess("Image duplicated successfully!"); + } + } + + // Select element like Word/Google Docs + selectElement(element) { + // Remove selection from all other elements + const allElements = this.template.querySelectorAll( + ".draggable-text-box, .draggable-image-container" + ); + allElements.forEach((el) => { + el.classList.remove("selected"); + // Do not use border to avoid layout shifts; use outline which doesn't affect layout + el.style.outline = "none"; + el.style.outlineOffset = "0px"; + + // Hide delete buttons + const deleteBtn = el.querySelector(".delete-btn"); + if (deleteBtn) { + deleteBtn.style.opacity = "0"; + } + }); + + // Select current element + element.classList.add("selected"); + // Use outline to show selection without affecting layout/position + element.style.outline = "2px solid #667eea"; + element.style.outlineOffset = "0px"; + + // Show delete button (support both legacy and new class names) + const deleteBtn = + element.querySelector(".delete-btn") || + element.querySelector(".delete-handle"); + if (deleteBtn) { + deleteBtn.style.opacity = "1"; + deleteBtn.style.display = "flex"; } - // Helper method to make elements resizable - makeResizable(element) { - const resizer = document.createElement('div'); - resizer.className = 'resizer'; - resizer.style.position = 'absolute'; - resizer.style.width = '10px'; - resizer.style.height = '10px'; - resizer.style.background = '#667eea'; - resizer.style.borderRadius = '50%'; - resizer.style.bottom = '-5px'; - resizer.style.right = '-5px'; - resizer.style.cursor = 'se-resize'; - resizer.style.zIndex = '1001'; + // Show selection handles + this.showSelectionHandles(element); + } + // Show selection handles like Word/Google Docs + showSelectionHandles(element) { + // Remove existing handles + const existingHandles = element.querySelectorAll(".selection-handle"); + existingHandles.forEach((handle) => handle.remove()); - element.appendChild(resizer); + // Create selection handles + const handles = [ + { position: "top-left", cursor: "nw-resize" }, + { position: "top-right", cursor: "ne-resize" }, + { position: "bottom-left", cursor: "sw-resize" }, + { position: "bottom-right", cursor: "se-resize" }, + ]; - let isResizing = false; - let startWidth, startHeight, startX, startY; + handles.forEach((handle) => { + const handleElement = document.createElement("div"); + handleElement.className = "selection-handle"; + handleElement.style.position = "absolute"; + handleElement.style.width = "8px"; + handleElement.style.height = "8px"; + handleElement.style.background = "#667eea"; + handleElement.style.border = "1px solid white"; + handleElement.style.borderRadius = "50%"; + handleElement.style.cursor = handle.cursor; + handleElement.style.zIndex = "1003"; - resizer.addEventListener('mousedown', (e) => { - isResizing = true; - startX = e.clientX; - startY = e.clientY; - startWidth = parseInt(element.style.width) || element.offsetWidth; - startHeight = parseInt(element.style.height) || element.offsetHeight; - e.stopPropagation(); - }); + // Position handles + switch (handle.position) { + case "top-left": + handleElement.style.top = "-4px"; + handleElement.style.left = "-4px"; + break; + case "top-right": + handleElement.style.top = "-4px"; + handleElement.style.right = "-4px"; + break; + case "bottom-left": + handleElement.style.bottom = "-4px"; + handleElement.style.left = "-4px"; + break; + case "bottom-right": + handleElement.style.bottom = "-4px"; + handleElement.style.right = "-4px"; + break; + } - document.addEventListener('mousemove', (e) => { - if (isResizing) { - const newWidth = startWidth + (e.clientX - startX); - const newHeight = startHeight + (e.clientY - startY); - - if (newWidth > 50) element.style.width = newWidth + 'px'; - if (newHeight > 30) element.style.height = newHeight + 'px'; - } - }); + element.appendChild(handleElement); + }); + } + addShape() { } - document.addEventListener('mouseup', () => { - isResizing = false; - }); + // Helper method to build amenities list dynamically + buildAmenitiesList(data) { + let amenitiesList = ""; + + // First priority: Use amenities array if available + if ( + data.amenities && + Array.isArray(data.amenities) && + data.amenities.length > 0 + ) { + amenitiesList = data.amenities + .map( + (amenity) => `
  • ${amenity}
  • ` + ) + .join(""); } + // Second priority: Use individual amenity fields if available + else if ( + data.amenity1 || + data.amenity2 || + data.amenity3 || + data.amenity4 || + data.amenity5 || + data.amenity6 || + data.amenity7 || + data.amenity8 || + data.amenity9 || + data.amenity10 + ) { + const individualAmenities = [ + data.amenity1, + data.amenity2, + data.amenity3, + data.amenity4, + data.amenity5, + data.amenity6, + data.amenity7, + data.amenity8, + data.amenity9, + data.amenity10, + ].filter((amenity) => amenity && amenity.trim() !== ""); - insertText() { - const previewFrame = this.template.querySelector('.enhanced-editor-content'); - if (previewFrame) { - // Create draggable and resizable text box - const textBox = document.createElement('div'); - textBox.className = 'draggable-text-box'; - textBox.contentEditable = true; - textBox.textContent = 'Double-click to edit text'; - textBox.style.position = 'absolute'; - textBox.style.left = '50px'; - textBox.style.top = '50px'; - textBox.style.width = '150px'; - textBox.style.height = '40px'; - textBox.style.minWidth = '100px'; - textBox.style.minHeight = '30px'; - textBox.style.padding = '8px'; - textBox.style.border = '2px solid #ddd'; - textBox.style.borderRadius = '4px'; - textBox.style.backgroundColor = 'white'; - textBox.style.cursor = 'text'; - textBox.style.zIndex = '1000'; - textBox.style.fontSize = '14px'; - textBox.style.fontFamily = 'Arial, sans-serif'; - textBox.style.color = '#333'; - textBox.style.boxSizing = 'border-box'; - textBox.style.outline = 'none'; - - // Handle Enter key to keep text in place - textBox.addEventListener('keydown', (e) => { - if (e.key === 'Enter') { - e.preventDefault(); - // Insert a line break instead of creating new element - document.execCommand('insertLineBreak', false); - } - }); - - // Handle selection like Word/Google Docs - textBox.addEventListener('click', (e) => { - e.stopPropagation(); - this.selectElement(textBox); - }); - - // Make text box draggable - this.makeDraggable(textBox); - - // Make text box resizable - this.makeResizable(textBox); - - previewFrame.appendChild(textBox); - textBox.focus(); - - // Select the text for easy editing - const range = document.createRange(); - range.selectNodeContents(textBox); - const selection = window.getSelection(); - selection.removeAllRanges(); - selection.addRange(range); - } + amenitiesList = individualAmenities + .map( + (amenity) => `
  • ${amenity}
  • ` + ) + .join(""); } - - insertImage() { - // Create file input for local image upload - const fileInput = document.createElement('input'); - fileInput.type = 'file'; - fileInput.accept = 'image/*'; - fileInput.style.display = 'none'; - - fileInput.onchange = (event) => { - const file = event.target.files[0]; - if (file) { - const reader = new FileReader(); - reader.onload = (e) => { - const previewFrame = this.template.querySelector('.enhanced-editor-content'); - if (previewFrame) { - // Create draggable and resizable image container - const imageContainer = document.createElement('div'); - imageContainer.className = 'draggable-image-container'; - imageContainer.style.position = 'absolute'; - imageContainer.style.left = '50px'; - imageContainer.style.top = '50px'; - imageContainer.style.width = '300px'; - imageContainer.style.height = '200px'; - imageContainer.style.cursor = 'move'; - imageContainer.style.zIndex = '1000'; - imageContainer.style.border = '2px dashed #667eea'; - imageContainer.style.borderRadius = '4px'; - imageContainer.style.overflow = 'hidden'; - - const img = document.createElement('img'); - img.src = e.target.result; - img.alt = 'Inserted Image'; - img.style.width = '100%'; - img.style.height = '100%'; - img.style.objectFit = 'cover'; - img.style.borderRadius = '4px'; - img.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)'; - - // Handle selection like Word/Google Docs - imageContainer.addEventListener('click', (e) => { - e.stopPropagation(); - this.selectElement(imageContainer); - }); - - // Add delete button (only visible when selected) - const deleteBtn = document.createElement('button'); - deleteBtn.className = 'delete-btn'; - deleteBtn.innerHTML = '×'; - deleteBtn.style.position = 'absolute'; - deleteBtn.style.top = '-10px'; - deleteBtn.style.right = '-10px'; - deleteBtn.style.width = '20px'; - deleteBtn.style.height = '20px'; - deleteBtn.style.borderRadius = '50%'; - deleteBtn.style.background = '#ff4757'; - deleteBtn.style.color = 'white'; - deleteBtn.style.border = 'none'; - deleteBtn.style.cursor = 'pointer'; - deleteBtn.style.fontSize = '16px'; - deleteBtn.style.fontWeight = 'bold'; - deleteBtn.style.zIndex = '1002'; - deleteBtn.style.opacity = '0'; - deleteBtn.style.transition = 'opacity 0.2s ease'; - - deleteBtn.onclick = (e) => { - e.stopPropagation(); - imageContainer.remove(); - }; - - imageContainer.appendChild(deleteBtn); - - // Make image container draggable - this.makeDraggable(imageContainer); - - // Make image container resizable - this.makeResizable(imageContainer); - - imageContainer.appendChild(img); - previewFrame.appendChild(imageContainer); - } - }; - reader.readAsDataURL(file); - } - }; - - // Trigger file selection - document.body.appendChild(fileInput); - fileInput.click(); - document.body.removeChild(fileInput); - } - - // Helper method to duplicate an image - duplicateImage(originalContainer) { - const previewFrame = this.template.querySelector('.enhanced-editor-content'); - if (previewFrame) { - const newContainer = originalContainer.cloneNode(true); - newContainer.style.left = (parseInt(originalContainer.style.left) + 20) + 'px'; - newContainer.style.top = (parseInt(originalContainer.style.top) + 20) + 'px'; - newContainer.style.zIndex = parseInt(originalContainer.style.zIndex) + 1; - - // Reattach event listeners - this.makeDraggable(newContainer); - this.makeResizable(newContainer); - - // Update control panel event listeners - const controlPanel = newContainer.querySelector('.image-control-panel'); - if (controlPanel) { - controlPanel.addEventListener('mouseenter', () => { - controlPanel.style.opacity = '1'; - }); - - controlPanel.addEventListener('mouseleave', () => { - controlPanel.style.opacity = '0'; - }); - } - - previewFrame.appendChild(newContainer); - this.showSuccess('Image duplicated successfully!'); - } - } - - - - // Select element like Word/Google Docs - selectElement(element) { - // Remove selection from all other elements - const allElements = this.template.querySelectorAll('.draggable-text-box, .draggable-image-container'); - allElements.forEach(el => { - el.classList.remove('selected'); - el.style.border = el.classList.contains('draggable-text-box') ? '2px solid #ddd' : '2px solid #ddd'; - - // Hide delete buttons - const deleteBtn = el.querySelector('.delete-btn'); - if (deleteBtn) { - deleteBtn.style.opacity = '0'; - } - }); - - // Select current element - element.classList.add('selected'); - element.style.border = '2px solid #667eea'; - - // Show delete button - const deleteBtn = element.querySelector('.delete-btn'); - if (deleteBtn) { - deleteBtn.style.opacity = '1'; - } - - // Show selection handles - this.showSelectionHandles(element); - } - - // Show selection handles like Word/Google Docs - showSelectionHandles(element) { - // Remove existing handles - const existingHandles = element.querySelectorAll('.selection-handle'); - existingHandles.forEach(handle => handle.remove()); - - // Create selection handles - const handles = [ - { position: 'top-left', cursor: 'nw-resize' }, - { position: 'top-right', cursor: 'ne-resize' }, - { position: 'bottom-left', cursor: 'sw-resize' }, - { position: 'bottom-right', cursor: 'se-resize' } - ]; - - handles.forEach(handle => { - const handleElement = document.createElement('div'); - handleElement.className = 'selection-handle'; - handleElement.style.position = 'absolute'; - handleElement.style.width = '8px'; - handleElement.style.height = '8px'; - handleElement.style.background = '#667eea'; - handleElement.style.border = '1px solid white'; - handleElement.style.borderRadius = '50%'; - handleElement.style.cursor = handle.cursor; - handleElement.style.zIndex = '1003'; - - // Position handles - switch(handle.position) { - case 'top-left': - handleElement.style.top = '-4px'; - handleElement.style.left = '-4px'; - break; - case 'top-right': - handleElement.style.top = '-4px'; - handleElement.style.right = '-4px'; - break; - case 'bottom-left': - handleElement.style.bottom = '-4px'; - handleElement.style.left = '-4px'; - break; - case 'bottom-right': - handleElement.style.bottom = '-4px'; - handleElement.style.right = '-4px'; - break; - } - - element.appendChild(handleElement); - }); - } - - addShape() { - } - - - // Helper method to build amenities list dynamically - buildAmenitiesList(data) { - let amenitiesList = ''; - - // First priority: Use amenities array if available - if (data.amenities && Array.isArray(data.amenities) && data.amenities.length > 0) { - amenitiesList = data.amenities.map(amenity => - `
  • ${amenity}
  • ` - ).join(''); - } - // Second priority: Use individual amenity fields if available - else if (data.amenity1 || data.amenity2 || data.amenity3 || data.amenity4 || data.amenity5 || - data.amenity6 || data.amenity7 || data.amenity8 || data.amenity9 || data.amenity10) { - - const individualAmenities = [ - data.amenity1, data.amenity2, data.amenity3, data.amenity4, data.amenity5, - data.amenity6, data.amenity7, data.amenity8, data.amenity9, data.amenity10 - ].filter(amenity => amenity && amenity.trim() !== ''); - - amenitiesList = individualAmenities.map(amenity => - `
  • ${amenity}
  • ` - ).join(''); - } - // Fallback: Use default luxury amenities - else { - amenitiesList = ` + // Fallback: Use default luxury amenities + else { + amenitiesList = `
  • Primary Suite with Spa-Bath
  • Radiant Heated Flooring
  • Custom Walk-in Closets
  • @@ -6325,37 +14970,63 @@ export default class PropertyTemplateSelector extends LightningElement {
  • Gourmet Chef's Kitchen
  • Floor-to-Ceiling Glass Walls
  • `; - } - - return amenitiesList; } - // Helper method to build amenities list for THE VERTICE template - buildAmenitiesListForVertice(data) { - let amenitiesList = ''; - - // First priority: Use amenities array if available - if (data.amenities && Array.isArray(data.amenities) && data.amenities.length > 0) { - amenitiesList = data.amenities.map(amenity => - `
  • ${amenity}
  • ` - ).join(''); - } - // Second priority: Use individual amenity fields if available - else if (data.amenity1 || data.amenity2 || data.amenity3 || data.amenity4 || data.amenity5 || - data.amenity6 || data.amenity7 || data.amenity8 || data.amenity9 || data.amenity10) { - - const individualAmenities = [ - data.amenity1, data.amenity2, data.amenity3, data.amenity4, data.amenity5, - data.amenity6, data.amenity7, data.amenity8, data.amenity9, data.amenity10 - ].filter(amenity => amenity && amenity.trim() !== ''); - - amenitiesList = individualAmenities.map(amenity => - `
  • ${amenity}
  • ` - ).join(''); - } - // Fallback: Use default luxury amenities - else { - amenitiesList = ` + return amenitiesList; + } + // Helper method to build amenities list for THE VERTICE template + buildAmenitiesListForVertice(data) { + let amenitiesList = ""; + + // First priority: Use amenities array if available + if ( + data.amenities && + Array.isArray(data.amenities) && + data.amenities.length > 0 + ) { + amenitiesList = data.amenities + .map( + (amenity) => + `
  • ${amenity}
  • ` + ) + .join(""); + } + // Second priority: Use individual amenity fields if available + else if ( + data.amenity1 || + data.amenity2 || + data.amenity3 || + data.amenity4 || + data.amenity5 || + data.amenity6 || + data.amenity7 || + data.amenity8 || + data.amenity9 || + data.amenity10 + ) { + const individualAmenities = [ + data.amenity1, + data.amenity2, + data.amenity3, + data.amenity4, + data.amenity5, + data.amenity6, + data.amenity7, + data.amenity8, + data.amenity9, + data.amenity10, + ].filter((amenity) => amenity && amenity.trim() !== ""); + + amenitiesList = individualAmenities + .map( + (amenity) => + `
  • ${amenity}
  • ` + ) + .join(""); + } + // Fallback: Use default luxury amenities + else { + amenitiesList = `
  • Rooftop Infinity Pool
  • Fitness Center
  • Residents' Sky Lounge
  • @@ -6365,1030 +15036,1571 @@ export default class PropertyTemplateSelector extends LightningElement {
  • 24/7 Concierge
  • Secure Parking
  • `; - } - - return amenitiesList; } - // Image Review Methods - openImageReview() { - this.showImageReview = true; - // Auto-select category will be handled in loadPropertyImages // Default to Interior category + return amenitiesList; + } + + // Image Review Methods + openImageReview() { + this.showImageReview = true; + // Auto-select category will be handled in loadPropertyImages // Default to Interior category + } + + closeImageReview() { + this.showImageReview = false; + this.currentImageIndex = 0; + this.currentImage = null; + } + selectCategory(event) { + let category; + + // Handle both event and direct category parameter + if (typeof event === "string") { + category = event; + } else if (event && event.currentTarget && event.currentTarget.dataset) { + category = event.currentTarget.dataset.category; + + // Update active category button + this.template.querySelectorAll(".category-btn-step2").forEach((btn) => { + btn.classList.remove("active"); + }); + event.currentTarget.classList.add("active"); + } else { + return; } - closeImageReview() { - this.showImageReview = false; - this.currentImageIndex = 0; - this.currentImage = null; + this.selectedCategory = category; + + // Filter real property images by category + this.filterImagesByCategory(category); + } + + // Add new method to show all images (no filtering) + filterImagesByCategory(category) { + if (!this.realPropertyImages || this.realPropertyImages.length === 0) { + this.currentImage = null; + this.totalImages = 0; + this.currentImageIndex = 0; + return; } - selectCategory(event) { - let category; - - // Handle both event and direct category parameter - if (typeof event === 'string') { - category = event; - } else if (event && event.currentTarget && event.currentTarget.dataset) { - category = event.currentTarget.dataset.category; - - // Update active category button - this.template.querySelectorAll('.category-btn-step2').forEach(btn => { - btn.classList.remove('active'); - }); - event.currentTarget.classList.add('active'); - } else { - return; - } - - - this.selectedCategory = category; - - // Filter real property images by category - this.filterImagesByCategory(category); + // Show all images instead of filtering by category + this.propertyImages = this.realPropertyImages; + this.totalImages = this.realPropertyImages.length; + + if (this.realPropertyImages.length > 0) { + this.currentImage = this.realPropertyImages[0]; + this.currentImageIndex = 0; + } else { + this.currentImage = null; + this.totalImages = 0; + this.currentImageIndex = 0; + } + } + + getImagesForCategory(category) { + // First try to get real images from Salesforce + if (this.realPropertyImages && this.realPropertyImages.length > 0) { + // Filter images by category + const categoryImages = this.realPropertyImages + .filter((img) => { + // Handle case-insensitive matching and variations + const imgCategory = img.category ? img.category.toLowerCase() : ""; + const searchCategory = category.toLowerCase(); + + // Direct match + if (imgCategory === searchCategory) { + return true; + } + + // Category mapping for common variations + const categoryMappings = { + interior: ["interior", "inside", "indoor"], + exterior: ["exterior", "outside", "outdoor", "facade"], + kitchen: ["kitchen", "dining"], + bedroom: ["bedroom", "bed", "room"], + "living area": ["living", "lounge", "sitting"], + parking: ["parking", "garage"], + anchor: ["anchor", "main", "hero"], + maps: ["map", "location", "area"], + }; + + const mappings = categoryMappings[searchCategory] || [searchCategory]; + return mappings.some((mapping) => imgCategory.includes(mapping)); + }) + .map((img) => ({ + url: img.url || `/servlet/FileDownload?file=${img.id}`, + id: img.id, + title: img.name || `${category} Image`, + category: category, + })); + + if (categoryImages.length > 0) { + return categoryImages; + } } - // Add new method to filter images by category - filterImagesByCategory(category) { - - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - this.currentImage = null; - this.totalImages = 0; - this.currentImageIndex = 0; - return; - } - - // Filter images by category - const filteredImages = this.realPropertyImages.filter(img => { - const imgCategory = img.category || img.pcrm__Category__c; - - // Handle "None" category - show images with no category or empty category - if (category === 'None') { - return !imgCategory || imgCategory === '' || imgCategory === null || imgCategory === undefined || imgCategory === 'None'; - } - - return imgCategory === category; + // Get images based on the selected template and property + if (!this.selectedTemplateId || !this.propertyData) { + return []; + } + + // Template-specific image mapping + const templateImages = this.getTemplateSpecificImages(category); + if (templateImages && templateImages.length > 0) { + return templateImages; + } + // No images found + return []; + } + getTemplateSpecificImages(category) { + const templateId = this.selectedTemplateId; + const propertyData = this.propertyData; + + // Map category names to property fields + const categoryFieldMap = { + Interior: ["interiorImage1", "interiorImage2", "interiorImage3"], + Exterior: ["exteriorImage1", "exteriorImage2", "exteriorImage3"], + Kitchen: ["kitchenImage1", "kitchenImage2", "kitchenImage3"], + Bedroom: ["bedroomImage1", "bedroomImage2", "bedroomImage3"], + "Living Area": [ + "livingAreaImage1", + "livingAreaImage2", + "livingAreaImage3", + ], + Parking: ["parkingImage1", "parkingImage2"], + Anchor: ["anchorImage1", "anchorImage2"], + Maps: ["mapImage1", "mapImage2"], + }; + + const fields = categoryFieldMap[category] || []; + const images = []; + + // Check if property has images for this category + fields.forEach((field) => { + if (propertyData[field] && propertyData[field].trim() !== "") { + images.push({ + url: propertyData[field], + title: `${category} - ${field.replace("Image", " View ")}`, + category: category, }); - - - if (filteredImages.length > 0) { - this.currentImage = filteredImages[0]; - this.totalImages = filteredImages.length; - this.currentImageIndex = 0; - } else { - this.currentImage = null; - this.totalImages = 0; - this.currentImageIndex = 0; - } + } + }); + + return images; + } + + generateImagesFromPropertyData(category, propertyData) { + const images = []; + + // Generate placeholder images based on property type and category + const propertyType = propertyData.propertyType || "Property"; + const location = propertyData.city || propertyData.community || "Location"; + + // Create sample images based on category and property data + const sampleImages = { + Interior: [ + "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + "https://images.unsplash.com/photo-1618221195710-dd6b41faaea6?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=800", + ], + Exterior: [ + "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + ], + Kitchen: [ + "https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + ], + Bedroom: [ + "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + ], + "Living Area": [ + "https://images.unsplash.com/photo-1618221195710-dd6b41faaea6?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=800", + ], + Parking: [ + "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + ], + Anchor: [ + "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + ], + Maps: [ + "https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200", + ], + }; + + const urls = sampleImages[category] || []; + urls.forEach((url, index) => { + images.push({ + url: url, + title: `${propertyType} - ${category} View ${index + 1}`, + category: category, + }); + }); + + return images; + } + + nextImage() { + if (this.currentImageIndex < this.totalImages - 1) { + this.currentImageIndex++; + this.updateCurrentImage(); + // Auto-classify the new image + this.autoClassifyCurrentImage(); + } else { + } + } + previousImage() { + if (this.currentImageIndex > 0) { + this.currentImageIndex--; + this.updateCurrentImage(); + // Auto-classify the new image + this.autoClassifyCurrentImage(); + } else { + } + } + + // Add new method to update current image + updateCurrentImage() { + if (!this.realPropertyImages || this.realPropertyImages.length === 0) { + return; } - getImagesForCategory(category) { - // First try to get real images from Salesforce - if (this.realPropertyImages && this.realPropertyImages.length > 0) { - // Filter images by category - const categoryImages = this.realPropertyImages.filter(img => { - // Handle case-insensitive matching and variations - const imgCategory = img.category ? img.category.toLowerCase() : ''; - const searchCategory = category.toLowerCase(); - - // Direct match - if (imgCategory === searchCategory) { - return true; - } - - // Category mapping for common variations - const categoryMappings = { - 'interior': ['interior', 'inside', 'indoor'], - 'exterior': ['exterior', 'outside', 'outdoor', 'facade'], - 'kitchen': ['kitchen', 'dining'], - 'bedroom': ['bedroom', 'bed', 'room'], - 'living area': ['living', 'lounge', 'sitting'], - 'parking': ['parking', 'garage'], - 'anchor': ['anchor', 'main', 'hero'], - 'maps': ['map', 'location', 'area'] - }; - - const mappings = categoryMappings[searchCategory] || [searchCategory]; - return mappings.some(mapping => imgCategory.includes(mapping)); - }).map(img => ({ - url: img.url || `/servlet/FileDownload?file=${img.id}`, - id: img.id, - title: img.name || `${category} Image`, - category: category - })); - - if (categoryImages.length > 0) { - return categoryImages; - } - } - - // Get images based on the selected template and property - if (!this.selectedTemplateId || !this.propertyData) { - return []; - } - - // Template-specific image mapping - const templateImages = this.getTemplateSpecificImages(category); - if (templateImages && templateImages.length > 0) { - return templateImages; - } - - // No images found - return []; - } - - getTemplateSpecificImages(category) { - const templateId = this.selectedTemplateId; - const propertyData = this.propertyData; - - // Map category names to property fields - const categoryFieldMap = { - 'Interior': ['interiorImage1', 'interiorImage2', 'interiorImage3'], - 'Exterior': ['exteriorImage1', 'exteriorImage2', 'exteriorImage3'], - 'Kitchen': ['kitchenImage1', 'kitchenImage2', 'kitchenImage3'], - 'Bedroom': ['bedroomImage1', 'bedroomImage2', 'bedroomImage3'], - 'Living Area': ['livingAreaImage1', 'livingAreaImage2', 'livingAreaImage3'], - 'Parking': ['parkingImage1', 'parkingImage2'], - 'Anchor': ['anchorImage1', 'anchorImage2'], - 'Maps': ['mapImage1', 'mapImage2'] + // Use all images instead of filtering by category + if ( + this.realPropertyImages.length > 0 && + this.currentImageIndex < this.realPropertyImages.length + ) { + this.currentImage = this.realPropertyImages[this.currentImageIndex]; + // Revert: only enable drag & drop; no auto-wrap on click + const imgEl = this.template.querySelector( + ".property-image-step2, .review-image" + ); + if (imgEl) { + imgEl.setAttribute("draggable", "true"); + imgEl.addEventListener( + "dragstart", + this.handleImageDragStart.bind(this) + ); + imgEl.style.cursor = "zoom-in"; + imgEl.onclick = () => { + const w = window.open(); + if (w && w.document) { + w.document.write( + `` + ); + } }; + } - const fields = categoryFieldMap[category] || []; - const images = []; + // Auto-classify the image when it's updated (only if not already triggered by navigation) + // Also ensure first image gets classified immediately + if (!this.isClassifyingImage) { + this.autoClassifyCurrentImage(); + } + } + } - // Check if property has images for this category - fields.forEach(field => { - if (propertyData[field] && propertyData[field].trim() !== '') { - images.push({ - url: propertyData[field], - title: `${category} - ${field.replace('Image', ' View ')}`, - category: category - }); - } + // Ensure editor is always editable + ensureEditorEditable() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor) { + editor.setAttribute("contenteditable", "true"); + editor.style.userSelect = "text"; + editor.style.webkitUserSelect = "text"; + editor.style.cursor = "text"; + + // Ensure editor is a positioned container so absolutely positioned children + // (e.g., wrapped draggable images) are anchored relative to it, preventing jumps + const editorComputed = window.getComputedStyle(editor); + if (editorComputed.position === "static") { + editor.style.position = "relative"; + } + + // Remove any potential pointer-events restrictions + editor.style.pointerEvents = "auto"; + + // Add event listeners to ensure editing works + if (!editor.hasEditListeners) { + editor.addEventListener("input", this.handleContentChange.bind(this)); + editor.addEventListener("keydown", (e) => { + // Handle undo/redo and other special keys + this.handleEditorKeydown(e); + // Allow all key presses for editing + e.stopPropagation(); }); + editor.addEventListener("keyup", this.handleContentChange.bind(this)); + editor.addEventListener("paste", this.handleContentChange.bind(this)); + // NEW: single-click any image to show resize controls + editor.addEventListener( + "click", + (e) => { + const target = e.target; + if ( + target && + target.tagName && + target.tagName.toLowerCase() === "img" + ) { + e.preventDefault(); + e.stopPropagation(); + this.selectDraggableElement(target); + } + }, + true + ); - return images; + editor.hasEditListeners = true; + } + } + } + // Connected callback to initialize + connectedCallback() { + // Ensure editor is editable after component loads + setTimeout(() => { + this.ensureEditorEditable(); + }, 1000); + + // Add window resize listener for dynamic font sizing + this.resizeHandler = () => { + this.applyDynamicFontSizing(); + }; + window.addEventListener('resize', this.resizeHandler); + + // Keyboard shortcuts for Word-like experience + this._keyHandler = (e) => { + if (this.currentStep !== 3) return; + const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0; + const mod = isMac ? e.metaKey : e.ctrlKey; + if (!mod) return; + switch (e.key.toLowerCase()) { + case "b": + e.preventDefault(); + this.handleBold(); + break; + case "i": + e.preventDefault(); + this.handleItalic(); + break; + case "u": + e.preventDefault(); + this.handleUnderline(); + break; + case "z": + e.preventDefault(); + this.undo(); + break; + case "y": + e.preventDefault(); + this.redo(); + break; + } + }; + window.addEventListener("keydown", this._keyHandler); + + // Auto-fit when window resizes in Step 3 + this._resizeHandler = () => { + if (this.currentStep === 3 && this.fitToWidth) this.fitToWidth(); + }; + window.addEventListener("resize", this._resizeHandler); + } + // Called after template loads + renderedCallback() { + this.ensureEditorEditable(); + this.setupEditorClickHandler(); + this.addDeselectFunctionality(); + + // Set up drag and drop for gallery images + this.setupDragAndDropListeners(); + + // Save initial state for undo functionality + setTimeout(() => { + this.saveUndoState(); + }, 100); + // Ensure initial fit and proper dimensions + if (this.currentStep === 3 && this.fitToWidth) { + setTimeout(() => { + this.initializeViewportDimensions(); + this.fitToWidth(); + }, 0); + } + } + + // Initialize viewport with exact PDF dimensions + initializeViewportDimensions() { + const baseWidth = this.selectedPageSize === "A3" ? 1123 : 794; + const baseHeight = this.selectedPageSize === "A3" ? 1587 : 1123; + + // Update canvas dimensions to match PDF exactly + const canvas = this.template?.querySelector(".pdf-canvas"); + if (canvas) { + canvas.style.width = `${baseWidth}px`; + canvas.style.height = `${baseHeight}px`; + canvas.setAttribute('data-page-size', this.selectedPageSize); } - generateImagesFromPropertyData(category, propertyData) { - const images = []; - - // Generate placeholder images based on property type and category - const propertyType = propertyData.propertyType || 'Property'; - const location = propertyData.city || propertyData.community || 'Location'; - - // Create sample images based on category and property data - const sampleImages = { - 'Interior': [ - 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200', - 'https://images.unsplash.com/photo-1618221195710-dd6b41faaea6?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=800' - ], - 'Exterior': [ - 'https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200', - 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200' - ], - 'Kitchen': [ - 'https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200' - ], - 'Bedroom': [ - 'https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200' - ], - 'Living Area': [ - 'https://images.unsplash.com/photo-1618221195710-dd6b41faaea6?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=800' - ], - 'Parking': [ - 'https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200' - ], - 'Anchor': [ - 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200' - ], - 'Maps': [ - 'https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200' - ] + // Update preview pages dimensions + const previewPages = this.template?.querySelectorAll(".preview-page"); + if (previewPages) { + previewPages.forEach(page => { + page.style.width = `${baseWidth}px`; + page.style.minHeight = `${baseHeight}px`; + page.style.maxWidth = `${baseWidth}px`; + }); + } + + // Force proper HTML rendering after dimensions are set + setTimeout(() => { + this.forceHTMLRendering(); + }, 50); + } + + // Test editor functionality - can be called from toolbar + testEditor() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor) { + editor.focus(); + this.ensureEditorEditable(); + } + } + + // Helper method to determine if element is likely positioned over an image + isElementLikelyOverImage(element) { + if (!element || element.tagName === "IMG") return false; + + // Be much more restrictive - only trigger for elements that are clearly over images + const style = window.getComputedStyle(element); + + // Only check for images underneath if the element has strong indicators + const isTransparentText = this.isTransparentTextElement(element, style); + const isPositionedOverlay = this.isPositionedOverlay(element, style); + const hasImageParent = this.hasDirectImageParent(element); + + // Only return true if there are very specific indicators + return ( + (isTransparentText && hasImageParent) || + (isPositionedOverlay && this.checkBackgroundImages(element)) + ); + } + isTransparentTextElement(element, style) { + // Text elements that are likely overlays + const textTags = [ + "P", + "SPAN", + "H1", + "H2", + "H3", + "H4", + "H5", + "H6", + "A", + "STRONG", + "EM", + "B", + "I", + "DIV", + ]; + const isTextElement = textTags.includes(element.tagName); + + // Check if background is transparent or semi-transparent + const bg = style.backgroundColor; + const isTransparent = + bg === "rgba(0, 0, 0, 0)" || + bg === "transparent" || + bg === "" || + (bg.includes("rgba") && + (bg.includes(", 0)") || + (bg.includes(", 0.") && parseFloat(bg.split(",")[3]) < 0.5))); + + return isTextElement && isTransparent; + } + + isPositionedOverlay(element, style) { + const isPositioned = ["absolute", "relative", "fixed"].includes( + style.position + ); + const hasLowOpacity = parseFloat(style.opacity) < 1; + const hasTransformOrZ = + style.transform !== "none" || parseInt(style.zIndex) > 0; + + return isPositioned && (hasLowOpacity || hasTransformOrZ); + } + + hasDirectImageParent(element) { + // Only check immediate parent and grandparent + let current = element.parentElement; + let depth = 0; + + while (current && depth < 2) { + if (current.querySelector("img")) return true; + current = current.parentElement; + depth++; + } + return false; + } + + checkBackgroundImages(element) { + let current = element; + let depth = 0; + + while (current && depth < 5) { + const style = window.getComputedStyle(current); + if (style.backgroundImage && style.backgroundImage !== "none") { + return true; + } + current = current.parentElement; + depth++; + } + return false; + } + + // Helper method to check if there are actual image indicators before expensive search + hasImageIndicators(clickedElement, x, y, editor) { + // Quick check: if the element or its parents have background images + let current = clickedElement; + let depth = 0; + + while (current && current !== editor && depth < 3) { + const style = window.getComputedStyle(current); + if (style.backgroundImage && style.backgroundImage !== "none") { + return true; + } + current = current.parentElement; + depth++; + } + + // Quick check: if there are any img elements in the nearby area + const allElementsAtPoint = document.elementsFromPoint + ? document.elementsFromPoint(x, y) + : []; + const hasDirectImage = allElementsAtPoint.some((el) => { + return ( + el.tagName === "IMG" || + (el.querySelector && el.querySelector("img")) || + window.getComputedStyle(el).backgroundImage !== "none" + ); + }); + + if (!hasDirectImage) { + // Final check: look for images in the clicked element's container + const container = clickedElement.closest("div, section, article"); + if (container && container !== editor) { + return container.querySelector("img") !== null; + } + } + + return hasDirectImage; + } + // Helper method to find images under click coordinates - including low z-index images + findImageUnderClick(x, y, editor) { + // Check for images directly at the click point + const allElementsAtPoint = document.elementsFromPoint + ? document.elementsFromPoint(x, y) + : []; + + for (const element of allElementsAtPoint) { + // Skip if it's part of the UI (toolbar, navigation, etc.) + if ( + element.closest(".editor-left") || + element.closest(".toolbar-section") || + element.closest(".step-navigation") || + element.closest(".page-controls") + ) { + continue; + } + + // Direct image - highest priority + if (element.tagName === "IMG") { + return element; + } + + // Check for contained images + const containedImg = element.querySelector("img"); + if (containedImg) { + return containedImg; + } + + // Check for background images + const style = window.getComputedStyle(element); + if (style.backgroundImage && style.backgroundImage !== "none") { + return { + src: style.backgroundImage.slice(5, -2), + element: element, + isBackgroundImage: true, }; - - const urls = sampleImages[category] || []; - urls.forEach((url, index) => { - images.push({ - url: url, - title: `${propertyType} - ${category} View ${index + 1}`, - category: category - }); - }); - - return images; + } } - nextImage() { - if (this.currentImageIndex < this.totalImages - 1) { - this.currentImageIndex++; - this.updateCurrentImage(); - } else { - } + return null; + } + // Triple click handler for image replacement + handleImageClick(clickedImage, event) { + console.log("=== HANDLE IMAGE CLICK CALLED ==="); + console.log("Clicked image:", clickedImage); + console.log("Event:", event); + + // Prevent replacement if file dialog is open + if (this.isFileDialogOpen) { + console.log("❌ File dialog open, ignoring image click"); + return; } - previousImage() { - if (this.currentImageIndex > 0) { - this.currentImageIndex--; - this.updateCurrentImage(); - } else { - } + console.log("✅ Image clicked! Count:", this.imageClickCount + 1); + + // Clear any existing timeout + if (this.clickTimeout) { + clearTimeout(this.clickTimeout); } - // Add new method to update current image - updateCurrentImage() { - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - return; - } - - // Use the same filtering logic as filterImagesByCategory - const filteredImages = this.realPropertyImages.filter(img => { - const imgCategory = img.category || img.pcrm__Category__c; - - // Handle "None" category - show images with no category or empty category - if (this.selectedCategory === 'None') { - return !imgCategory || imgCategory === '' || imgCategory === null || imgCategory === undefined || imgCategory === 'None'; - } - - return imgCategory === this.selectedCategory; - }); - - if (filteredImages.length > 0 && this.currentImageIndex < filteredImages.length) { - this.currentImage = filteredImages[this.currentImageIndex]; - // Revert: only enable drag & drop; no auto-wrap on click - const imgEl = this.template.querySelector('.property-image-step2, .review-image'); - if (imgEl) { - imgEl.setAttribute('draggable', 'true'); - imgEl.addEventListener('dragstart', this.handleImageDragStart.bind(this)); - imgEl.style.cursor = 'zoom-in'; - imgEl.onclick = () => { - const w = window.open(); - if (w && w.document) { - w.document.write(``); - } - }; - } - } - } - - // Ensure editor is always editable - ensureEditorEditable() { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor) { - editor.setAttribute('contenteditable', 'true'); - editor.style.userSelect = 'text'; - editor.style.webkitUserSelect = 'text'; - editor.style.cursor = 'text'; - - // Remove any potential pointer-events restrictions - editor.style.pointerEvents = 'auto'; - - // Add event listeners to ensure editing works - if (!editor.hasEditListeners) { - editor.addEventListener('input', this.handleContentChange.bind(this)); - editor.addEventListener('keydown', (e) => { - // Handle undo/redo and other special keys - this.handleEditorKeydown(e); - // Allow all key presses for editing - e.stopPropagation(); - }); - editor.addEventListener('keyup', this.handleContentChange.bind(this)); - editor.addEventListener('paste', this.handleContentChange.bind(this)); - // NEW: single-click any image to show resize controls - editor.addEventListener('click', (e) => { - const target = e.target; - if (target && target.tagName && target.tagName.toLowerCase() === 'img') { - e.preventDefault(); - e.stopPropagation(); - this.selectDraggableElement(target); - } - }, true); - - editor.hasEditListeners = true; - } - - } - } - - // Connected callback to initialize - connectedCallback() { - - // Ensure editor is editable after component loads - setTimeout(() => { - this.ensureEditorEditable(); - }, 1000); + // Debug logging for image detection + const debugInfo = { + tagName: clickedImage.tagName, + isBackgroundImage: clickedImage.isBackgroundImage, + src: clickedImage.src, + backgroundImage: clickedImage.style.backgroundImage, + originalElement: clickedImage.originalElement, + isFooterImage: this.isFooterOrBackgroundImage(clickedImage), + parentElement: clickedImage.parentElement?.className || clickedImage.parentElement?.tagName + }; + console.log("Image debug info:", debugInfo); - // Keyboard shortcuts for Word-like experience - this._keyHandler = (e) => { - if (this.currentStep !== 3) return; - const isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0; - const mod = isMac ? e.metaKey : e.ctrlKey; - if (!mod) return; - switch (e.key.toLowerCase()) { - case 'b': e.preventDefault(); this.handleBold(); break; - case 'i': e.preventDefault(); this.handleItalic(); break; - case 'u': e.preventDefault(); this.handleUnderline(); break; - case 'z': e.preventDefault(); this.undo(); break; - case 'y': e.preventDefault(); this.redo(); break; - } - }; - window.addEventListener('keydown', this._keyHandler); + // Check if this is the same image as the last click using improved comparison + const isSameImage = this.isSameImageAsLast(clickedImage); - // Auto-fit when window resizes in Step 3 - this._resizeHandler = () => { if (this.currentStep === 3 && this.fitToWidth) this.fitToWidth(); }; - window.addEventListener('resize', this._resizeHandler); - } - - // Called after template loads - renderedCallback() { - this.ensureEditorEditable(); - this.setupEditorClickHandler(); - - // Save initial state for undo functionality - setTimeout(() => { - this.saveUndoState(); - }, 100); - // Ensure initial fit - if (this.currentStep === 3 && this.fitToWidth) { - setTimeout(() => this.fitToWidth(), 0); - } - - } - - // Test editor functionality - can be called from toolbar - testEditor() { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor) { - editor.focus(); - this.ensureEditorEditable(); - } + if (isSameImage) { + // Same image clicked, increment counter + this.imageClickCount++; + console.log("Same image clicked, count:", this.imageClickCount); + } else { + // Different image clicked, reset counter + this.imageClickCount = 1; + this.lastClickedImage = clickedImage; + console.log("Different image clicked, reset count to 1"); } - // Helper method to determine if element is likely positioned over an image - isElementLikelyOverImage(element) { - if (!element || element.tagName === 'IMG') return false; - - // Be much more restrictive - only trigger for elements that are clearly over images - const style = window.getComputedStyle(element); - - // Only check for images underneath if the element has strong indicators - const isTransparentText = this.isTransparentTextElement(element, style); - const isPositionedOverlay = this.isPositionedOverlay(element, style); - const hasImageParent = this.hasDirectImageParent(element); - - // Only return true if there are very specific indicators - return (isTransparentText && hasImageParent) || - (isPositionedOverlay && this.checkBackgroundImages(element)); - } - - isTransparentTextElement(element, style) { - // Text elements that are likely overlays - const textTags = ['P', 'SPAN', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'A', 'STRONG', 'EM', 'B', 'I', 'DIV']; - const isTextElement = textTags.includes(element.tagName); - - // Check if background is transparent or semi-transparent - const bg = style.backgroundColor; - const isTransparent = bg === 'rgba(0, 0, 0, 0)' || - bg === 'transparent' || - bg === '' || - (bg.includes('rgba') && (bg.includes(', 0)') || bg.includes(', 0.') && parseFloat(bg.split(',')[3]) < 0.5)); - - return isTextElement && isTransparent; - } - - isPositionedOverlay(element, style) { - const isPositioned = ['absolute', 'relative', 'fixed'].includes(style.position); - const hasLowOpacity = parseFloat(style.opacity) < 1; - const hasTransformOrZ = style.transform !== 'none' || parseInt(style.zIndex) > 0; - - return isPositioned && (hasLowOpacity || hasTransformOrZ); - } - - hasDirectImageParent(element) { - // Only check immediate parent and grandparent - let current = element.parentElement; - let depth = 0; - - while (current && depth < 2) { - if (current.querySelector('img')) return true; - current = current.parentElement; - depth++; - } - return false; - } - + // Set timeout to reset counter after 1 second (international standard) + this.clickTimeout = setTimeout(() => { + this.resetImageClickTracking(); + console.log("Click timeout reached, reset counter"); + }, 1000); - - checkBackgroundImages(element) { - let current = element; - let depth = 0; - - while (current && depth < 5) { - const style = window.getComputedStyle(current); - if (style.backgroundImage && style.backgroundImage !== 'none') { - return true; - } - current = current.parentElement; - depth++; - } - return false; - } + // Check if we've reached exactly 3 clicks + if (this.imageClickCount === 3) { + console.log("3 clicks reached! Opening image replacement modal"); + console.log("Clicked image details:", { + tagName: clickedImage.tagName, + src: clickedImage.src, + isBackgroundImage: clickedImage.isBackgroundImage, + className: clickedImage.className, + parentElement: clickedImage.parentElement?.className + }); - // Helper method to check if there are actual image indicators before expensive search - hasImageIndicators(clickedElement, x, y, editor) { - // Quick check: if the element or its parents have background images - let current = clickedElement; - let depth = 0; - - while (current && current !== editor && depth < 3) { - const style = window.getComputedStyle(current); - if (style.backgroundImage && style.backgroundImage !== 'none') { - return true; - } - current = current.parentElement; - depth++; - } - - // Quick check: if there are any img elements in the nearby area - const allElementsAtPoint = document.elementsFromPoint ? document.elementsFromPoint(x, y) : []; - const hasDirectImage = allElementsAtPoint.some(el => { - return el.tagName === 'IMG' || - (el.querySelector && el.querySelector('img')) || - (window.getComputedStyle(el).backgroundImage !== 'none'); - }); - - if (!hasDirectImage) { - // Final check: look for images in the clicked element's container - const container = clickedElement.closest('div, section, article'); - if (container && container !== editor) { - return container.querySelector('img') !== null; - } - } - - return hasDirectImage; - } + event.preventDefault(); + event.stopPropagation(); - // Helper method to find images under click coordinates - including low z-index images - findImageUnderClick(x, y, editor) { - // Check for images directly at the click point - const allElementsAtPoint = document.elementsFromPoint ? document.elementsFromPoint(x, y) : []; - - for (const element of allElementsAtPoint) { - // Skip if it's part of the UI (toolbar, navigation, etc.) - if (element.closest('.editor-left') || - element.closest('.toolbar-section') || - element.closest('.step-navigation') || - element.closest('.page-controls')) { - continue; - } - - // Direct image - highest priority - if (element.tagName === 'IMG') { - return element; - } - - // Check for contained images - const containedImg = element.querySelector('img'); - if (containedImg) { - return containedImg; - } - - // Check for background images - const style = window.getComputedStyle(element); - if (style.backgroundImage && style.backgroundImage !== 'none') { - return { - src: style.backgroundImage.slice(5, -2), - element: element, - isBackgroundImage: true - }; - } - } - - return null; - } - - // Triple click handler for image replacement - handleImageClick(clickedImage, event) { - // Clear any existing timeout - if (this.clickTimeout) { - clearTimeout(this.clickTimeout); - } - - // Debug logging for image detection - const debugInfo = { - tagName: clickedImage.tagName, - isBackgroundImage: clickedImage.isBackgroundImage, - src: clickedImage.src, - backgroundImage: clickedImage.style.backgroundImage, - originalElement: clickedImage.originalElement - }; - - // Check if this is the same image as the last click - const isSameImage = this.lastClickedImage && - ((this.lastClickedImage.src && clickedImage.src && this.lastClickedImage.src === clickedImage.src) || - (this.lastClickedImage.isBackgroundImage && clickedImage.isBackgroundImage && - this.lastClickedImage.style.backgroundImage === clickedImage.style.backgroundImage)); - - if (isSameImage) { - // Same image clicked, increment counter - this.imageClickCount++; - } else { - // Different image clicked, reset counter - this.imageClickCount = 1; - this.lastClickedImage = clickedImage; - } - - // Set timeout to reset counter after 1 second - this.clickTimeout = setTimeout(() => { - this.imageClickCount = 0; - this.lastClickedImage = null; - }, 1000); - - // Check if we've reached exactly 3 clicks - if (this.imageClickCount === 3) { - event.preventDefault(); - event.stopPropagation(); - this.openImageReplacement(clickedImage); - - // Reset counter after opening popup - this.imageClickCount = 0; - this.lastClickedImage = null; - if (this.clickTimeout) { - clearTimeout(this.clickTimeout); - this.clickTimeout = null; - } - } else { - // Show feedback for clicks 1 and 2, but don't open popup - if (this.imageClickCount === 1) { - this.showSuccess('Click 2 more times on the same image to replace it'); - } else if (this.imageClickCount === 2) { - this.showSuccess('Click 1 more time on the same image to replace it'); - } - - // Prevent any default behavior for clicks 1 and 2 - event.preventDefault(); - event.stopPropagation(); - } - } - - // Image Replacement Methods - openImageReplacement(imageElement) { - if (!imageElement) { - return; - } - - - this.selectedImageElement = imageElement; - this.showImageReplacement = true; - this.replacementActiveTab = 'property'; - - // Use smart category selection like Step 2 - this.replacementSelectedCategory = this.findFirstAvailableCategory(); - - this.uploadedImagePreview = null; - this.filterReplacementImages(); - - // Update category button states after filtering - setTimeout(() => { - const categoryButtons = this.template.querySelectorAll('.category-btn-step2'); - categoryButtons.forEach(btn => { - btn.classList.remove('active'); - if (btn.dataset.category === this.replacementSelectedCategory) { - btn.classList.add('active'); - } - }); - }, 100); - - // Prevent body scrolling - document.body.style.overflow = 'hidden'; - - // Log the selected image details for debugging - if (imageElement.isBackgroundImage) { - } else if (imageElement.tagName === 'IMG') { - } else { - } - } - - closeImageReplacement() { - this.showImageReplacement = false; - this.selectedImageElement = null; - this.uploadedImagePreview = null; - - // Clear click tracking + // Validate that the image can be replaced + if (this.canReplaceImage(clickedImage)) { + console.log("Image can be replaced, opening popup"); + this.openImageReplacement(clickedImage); this.resetImageClickTracking(); - - // Restore body scrolling - document.body.style.overflow = ''; + } else { + console.log("Image cannot be replaced, showing error"); + console.log("DEBUG: Attempting to show popup anyway for debugging"); + // Temporary: Show popup anyway for debugging + this.openImageReplacement(clickedImage); + this.resetImageClickTracking(); + // this.showError("This image cannot be replaced"); + } + } else { + // Show feedback for clicks 1 and 2, but don't open popup + if (this.imageClickCount === 1) { + this.showSuccess("Click 2 more times on the same image to replace it"); + } else if (this.imageClickCount === 2) { + this.showSuccess("Click 1 more time on the same image to replace it"); + } + + // Prevent any default behavior for clicks 1 and 2 + event.preventDefault(); + event.stopPropagation(); + } + } + + // Helper method to check if image is footer or background image + isFooterOrBackgroundImage(imageElement) { + if (!imageElement) { + console.log("isFooterOrBackgroundImage: No image element provided"); + return false; } - resetImageClickTracking() { - this.imageClickCount = 0; - this.lastClickedImage = null; - if (this.clickTimeout) { - clearTimeout(this.clickTimeout); - this.clickTimeout = null; + console.log("Checking if image is footer/background:", { + tagName: imageElement.tagName, + className: imageElement.className, + parentClassName: imageElement.parentElement?.className, + isBackgroundImage: imageElement.isBackgroundImage, + originalElement: imageElement.originalElement?.className + }); + + // Check if image is in footer + const footer = imageElement.closest('footer, .page-footer, .p1-footer, .agent-footer'); + if (footer) { + console.log("Image is in footer element:", footer.className); + return true; + } + + // Check if image has footer-related classes or attributes + const container = imageElement.parentElement; + if (container && ( + container.classList.contains('page-footer') || + container.classList.contains('p1-footer') || + container.classList.contains('agent-footer') || + container.classList.contains('company-logo') || + container.classList.contains('footer-logo') || + container.classList.contains('brand-logo') + )) { + console.log("Image parent has footer class:", container.className); + return true; + } + + // Check if it's a background image of a footer element + if (imageElement.isBackgroundImage) { + const parentElement = imageElement.originalElement || imageElement.parentElement; + if (parentElement && ( + parentElement.classList.contains('page-footer') || + parentElement.classList.contains('p1-footer') || + parentElement.classList.contains('agent-footer') || + parentElement.tagName === 'FOOTER' + )) { + console.log("Background image of footer element:", parentElement.className); + return true; + } + + // Additional check: if the background image is in a footer section but not a hero section + if (parentElement) { + const isInFooterSection = parentElement.closest('footer, .page-footer, .p1-footer, .agent-footer'); + const isHeroSection = parentElement.classList.contains('hero') || + parentElement.classList.contains('p1-image-side') || + parentElement.classList.contains('p2-image') || + parentElement.classList.contains('cover-page') || + parentElement.classList.contains('banner'); + + if (isInFooterSection && !isHeroSection) { + console.log("Background image is in footer section but not hero section:", parentElement.className); + return true; } + } } - selectPropertyImagesTab() { - this.replacementActiveTab = 'property'; - this.filterReplacementImages(); + console.log("Image is NOT footer/background - can be replaced"); + return false; + } + + // Improved image comparison method + isSameImageAsLast(clickedImage) { + if (!this.lastClickedImage) return false; + + // Compare by src if both have src + if (clickedImage.src && this.lastClickedImage.src) { + return clickedImage.src === this.lastClickedImage.src; } - selectLocalUploadTab() { - this.replacementActiveTab = 'upload'; - this.uploadedImagePreview = null; - - // Force re-render to ensure the upload area is visible + // Compare by background image if both are background images + if (clickedImage.isBackgroundImage && this.lastClickedImage.isBackgroundImage) { + return clickedImage.style.backgroundImage === this.lastClickedImage.style.backgroundImage; + } + + // Compare by element reference for inserted images + if (clickedImage === this.lastClickedImage) { + return true; + } + + // Compare by container ID for draggable images + const currentContainer = clickedImage.closest('.draggable-image-container'); + const lastContainer = this.lastClickedImage.closest('.draggable-image-container'); + if (currentContainer && lastContainer) { + return currentContainer.id === lastContainer.id; + } + + return false; + } + + // Check if image can be replaced + canReplaceImage(imageElement) { + console.log("Checking if image can be replaced:", imageElement); + console.log("Image details:", { + tagName: imageElement.tagName, + isBackgroundImage: imageElement.isBackgroundImage, + src: imageElement.src, + originalElement: imageElement.originalElement, + className: imageElement.className, + parentClassName: imageElement.parentElement?.className + }); + + // Don't replace footer images (strict check) + if (this.isFooterOrBackgroundImage(imageElement)) { + console.log("Image is footer/background - cannot replace"); + return false; + } + + // Allow replacement of inserted images + const container = imageElement.closest('.draggable-image-container'); + if (container && container.getAttribute('data-image-type') === 'inserted') { + console.log("Image is inserted - can replace"); + return true; + } + + // Allow replacement of regular IMG elements (including layered ones) + if (imageElement.tagName === 'IMG') { + console.log("Image is IMG element - can replace"); + return true; + } + + // Allow replacement of background images that are not in footers + if (imageElement.isBackgroundImage && !this.isFooterOrBackgroundImage(imageElement)) { + console.log("Image is background but not footer - can replace"); + return true; + } + + // Allow replacement of pseudo-element images (::before, ::after) + if (imageElement.isPseudoElement) { + console.log("Image is pseudo-element - can replace"); + return true; + } + + // Allow replacement of layered images with z-index + if (imageElement.originalElement) { + const computedStyle = window.getComputedStyle(imageElement.originalElement); + const zIndex = computedStyle.zIndex; + const position = computedStyle.position; + + if (zIndex && zIndex !== 'auto' && zIndex !== '0') { + console.log("Image has z-index - can replace (layered image)"); + return true; + } + + if (position === 'absolute' || position === 'fixed' || position === 'relative') { + console.log("Image is positioned - can replace (layered image)"); + return true; + } + } + + // Allow replacement of images in containers with specific classes + if (imageElement.originalElement) { + const classes = imageElement.originalElement.className || ''; + if (classes.includes('hero') || classes.includes('banner') || + classes.includes('card') || classes.includes('property') || + classes.includes('image') || classes.includes('photo') || + classes.includes('cover') || classes.includes('header') || + classes.includes('main') || classes.includes('content') || + classes.includes('section') || classes.includes('container') || + classes.includes('p1-image-side') || classes.includes('p2-image') || + classes.includes('vision-image') || classes.includes('cover-page')) { + console.log("Image is in content container - can replace"); + return true; + } + } + + // Special handling for hero sections - always allow replacement + if (imageElement.originalElement && ( + imageElement.originalElement.classList.contains('hero') || + imageElement.originalElement.classList.contains('p1-image-side') || + imageElement.originalElement.classList.contains('p2-image') + )) { + console.log("Image is in hero section - can replace"); + return true; + } + + // Allow replacement of any image that's not explicitly a footer + console.log("Image type not explicitly blocked - allowing replacement"); + return true; // Default to allowing replacement for unknown types + } + + // Image Replacement Methods + openImageReplacement(imageElement) { + if (!imageElement) { + console.error("No image element provided to openImageReplacement"); + return; + } + + console.log("Opening image replacement for:", imageElement); + + this.selectedImageElement = imageElement; + this.showImageReplacement = true; + this.replacementActiveTab = "property"; + + // Use smart category selection like Step 2 + this.replacementSelectedCategory = this.findFirstAvailableCategory(); + console.log("Selected category for replacement:", this.replacementSelectedCategory); + + this.uploadedImagePreview = null; + this.filterReplacementImages(); + + // Update category button states after filtering + setTimeout(() => { + const categoryButtons = this.template.querySelectorAll( + ".category-btn-step2" + ); + categoryButtons.forEach((btn) => { + btn.classList.remove("active"); + if (btn.dataset.category === this.replacementSelectedCategory) { + btn.classList.add("active"); + } + }); + }, 100); + + // Prevent body scrolling + document.body.style.overflow = "hidden"; + + // Log the selected image details for debugging + if (imageElement.isBackgroundImage) { + console.log("Replacing background image:", imageElement.style.backgroundImage); + } else if (imageElement.tagName === "IMG") { + console.log("Replacing IMG element:", imageElement.src); + } else { + console.log("Unknown image element type:", imageElement); + } + } + + closeImageReplacement() { + this.showImageReplacement = false; + this.selectedImageElement = null; + this.uploadedImagePreview = null; + this.selectedReplacementImage = null; + + // Clear click tracking + this.resetImageClickTracking(); + + // Restore body scrolling + document.body.style.overflow = ""; + } + + resetImageClickTracking() { + this.imageClickCount = 0; + this.lastClickedImage = null; + if (this.clickTimeout) { + clearTimeout(this.clickTimeout); + this.clickTimeout = null; + } + } + + selectPropertyImagesTab() { + this.replacementActiveTab = "property"; + this.filterReplacementImages(); + } + + selectLocalUploadTab() { + this.replacementActiveTab = "upload"; + this.uploadedImagePreview = null; + + // Force re-render to ensure the upload area is visible + this.forceRerender(); + + // Add a small delay to ensure DOM is updated + setTimeout(() => { + const uploadDropzone = this.template.querySelector(".upload-dropzone"); + if (uploadDropzone) { + } else { + } + }, 100); + } + selectReplacementCategory(event) { + const category = event.target.dataset.category; + + this.replacementSelectedCategory = category; + this.filterReplacementImages(); + + // Update active state for category buttons + const categoryButtons = this.template.querySelectorAll( + ".category-btn-step2" + ); + categoryButtons.forEach((btn) => { + btn.classList.remove("active"); + if (btn.dataset.category === this.replacementSelectedCategory) { + btn.classList.add("active"); + } + }); + } + + filterReplacementImages() { + if (!this.realPropertyImages || this.realPropertyImages.length === 0) { + this.filteredReplacementImages = []; + return; + } + + // Filter images by category using the same logic as Step 2 + const filteredImages = this.realPropertyImages.filter((img) => { + const imgCategory = img.category || img.pcrm__Category__c; + + // Handle "None" category - show images with no category or empty category + if (this.replacementSelectedCategory === "None") { + return ( + !imgCategory || + imgCategory === "" || + imgCategory === null || + imgCategory === undefined || + imgCategory === "None" + ); + } + + return imgCategory === this.replacementSelectedCategory; + }); + + this.filteredReplacementImages = filteredImages.map((img, index) => ({ + id: `${this.replacementSelectedCategory}-${index}`, + url: img.url, + title: img.title || img.name || `Image ${index + 1}`, + category: img.category || img.pcrm__Category__c || "None", + })); + } + triggerImageReplacementFileUpload() { + // Try to find the image upload input in the replacement modal + const fileInput = this.template.querySelector(".image-upload-input"); + if (fileInput) { + // Reset the input to allow selecting the same file again + fileInput.value = ""; + fileInput.click(); + } else { + // Fallback: create a new input programmatically + const input = document.createElement("input"); + input.type = "file"; + input.accept = "image/*"; + input.style.display = "none"; + input.onchange = (e) => this.handleImageUpload(e); + document.body.appendChild(input); + input.click(); + // Don't remove immediately, let the handler process first + setTimeout(() => { + if (document.body.contains(input)) { + document.body.removeChild(input); + } + }, 100); + } + } + + handleImageUpload(event) { + const file = event.target.files[0]; + + if (!file) { + return; + } + + // Validate file type + if (!file.type.startsWith("image/")) { + this.showError("Please select a valid image file (JPG, PNG, GIF, WebP)"); + return; + } + + // Validate file size (e.g., max 10MB) + const maxSize = 10 * 1024 * 1024; // 10MB + if (file.size > maxSize) { + this.showError("File size must be less than 10MB"); + return; + } + + const reader = new FileReader(); + reader.onload = (e) => { + this.uploadedImagePreview = e.target.result; + + // Show success message + this.showSuccess( + '✅ Image uploaded successfully! Click "Use This Image" to apply it.' + ); + + // Force re-render to show the preview + this.forceRerender(); + }; + + reader.onerror = (e) => { + this.showError("Error reading the selected file. Please try again."); + }; + + reader.readAsDataURL(file); + } + + useUploadedImage() { + if (this.uploadedImagePreview) { + this.replaceImageSrc(this.uploadedImagePreview); + this.closeImageReplacement(); + } + } + + // Drag and drop handlers for image upload + handleDragOver(event) { + event.preventDefault(); + event.stopPropagation(); + const dropzone = event.currentTarget; + dropzone.classList.add("drag-over"); + } + + handleDragLeave(event) { + event.preventDefault(); + event.stopPropagation(); + const dropzone = event.currentTarget; + dropzone.classList.remove("drag-over"); + } + + handleDrop(event) { + event.preventDefault(); + event.stopPropagation(); + + const dropzone = event.currentTarget; + dropzone.classList.remove("drag-over"); + + const files = event.dataTransfer.files; + if (files.length > 0) { + const file = files[0]; + + // Validate file type + if (!file.type.startsWith("image/")) { + this.showError("Please drop a valid image file (JPG, PNG, GIF, WebP)"); + return; + } + + // Validate file size (e.g., max 10MB) + const maxSize = 10 * 1024 * 1024; // 10MB + if (file.size > maxSize) { + this.showError("File size must be less than 10MB"); + return; + } + + // Process the dropped file + const reader = new FileReader(); + reader.onload = (e) => { + this.uploadedImagePreview = e.target.result; + + // Show success message + this.showSuccess( + '✅ Image uploaded successfully! Click "Use This Image" to apply it.' + ); + + // Force re-render to show the preview this.forceRerender(); - - // Add a small delay to ensure DOM is updated + }; + + reader.onerror = (e) => { + this.showError("Error reading the dropped file. Please try again."); + }; + + reader.readAsDataURL(file); + } + } + // Select replacement category for image replacement popup + selectReplacementCategory(event) { + const category = event.target.dataset.category; + this.replacementSelectedCategory = category; + + // Update button states + document.querySelectorAll(".category-btn-step2").forEach((btn) => { + btn.classList.remove("active"); + if (btn.dataset.category === category) { + btn.classList.add("active"); + } + }); + + // Filter images for the selected category + this.filterReplacementImages(); + } + + // Select replacement image from popup + selectReplacementImage(event) { + event.stopPropagation(); + const imageUrl = event.currentTarget.dataset.imageUrl; + const imageTitle = event.currentTarget.querySelector('.replacement-image-title')?.textContent || 'Selected Image'; + + console.log("Selecting replacement image:", imageUrl, imageTitle); + + if (!imageUrl) { + this.showError("Failed to get image URL. Please try again."); + return; + } + + // Store the selected image + this.selectedReplacementImage = { + url: imageUrl, + title: imageTitle + }; + + console.log("Selected replacement image stored:", this.selectedReplacementImage); + + // Update visual selection state + document.querySelectorAll('.replacement-image-item').forEach(item => { + item.classList.remove('selected'); + }); + event.currentTarget.classList.add('selected'); + } + + // Insert the selected replacement image + insertSelectedReplacementImage() { + if (!this.selectedReplacementImage) { + this.showError("Please select an image first."); + return; + } + + // Replace the image + this.replaceImageSrc(this.selectedReplacementImage.url); + this.closeImageReplacement(); + } + + replaceImageSrc(newImageUrl) { + console.log("replaceImageSrc called with:", newImageUrl); + console.log("selectedImageElement:", this.selectedImageElement); + + if (!this.selectedImageElement || !newImageUrl) { + console.error("Missing selectedImageElement or newImageUrl"); + return; + } + + try { + // Save undo state before making changes + this.saveUndoState(); + + // Store the current positioning before replacement + let currentPosition = null; + let currentSize = null; + let currentZIndex = null; + + if (this.selectedImageElement.tagName === "IMG") { + const container = this.selectedImageElement.closest(".draggable-image-container, .draggable-element"); + if (container) { + currentPosition = { + left: container.style.left, + top: container.style.top, + position: container.style.position + }; + currentSize = { + width: container.style.width, + height: container.style.height + }; + currentZIndex = container.style.zIndex; + console.log("Stored position:", currentPosition); + console.log("Stored size:", currentSize); + } + } + + // Handle background images + if (this.selectedImageElement.isBackgroundImage) { + // Handle pseudo-element images (::before, ::after) + if (this.selectedImageElement.isPseudoElement) { + const originalElement = this.selectedImageElement.originalElement; + if (originalElement) { + // Update the main element's background image (which the pseudo-element inherits) + originalElement.style.backgroundImage = `url("${newImageUrl}")`; + this.showSuccess("Pseudo-element image updated successfully!"); + return; + } + } + + // Use the stored original element reference if available + if (this.selectedImageElement.originalElement) { + this.selectedImageElement.originalElement.style.backgroundImage = `url("${newImageUrl}")`; + this.showSuccess("Background image updated successfully!"); + return; + } + + // Fallback: Find the actual DOM element that has the background image + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor) { + // Find all elements with background images and update the one that matches + const allElements = editor.querySelectorAll("*"); + for (let element of allElements) { + const computedStyle = window.getComputedStyle(element); + const currentBgImage = computedStyle.backgroundImage; + if (currentBgImage && currentBgImage !== "none") { + // Check if this is the element we want to update + const currentBgUrl = currentBgImage.replace( + /url\(['"]?(.+?)['"]?\)/, + "$1" + ); + if (currentBgUrl === this.selectedImageElement.src) { + element.style.backgroundImage = `url("${newImageUrl}")`; + this.showSuccess("Background image updated successfully!"); + return; + } + } + } + } + this.showError("Failed to update background image. Please try again."); + return; + } + + // Handle regular img elements + if (this.selectedImageElement.tagName === "IMG") { + this.selectedImageElement.src = newImageUrl; + + // If the image is inside a draggable container, ensure it maintains proper styling AND positioning + const draggableContainer = + this.selectedImageElement.closest(".draggable-image-container, .draggable-element"); + if (draggableContainer) { + // Preserve exact positioning + if (currentPosition) { + draggableContainer.style.position = currentPosition.position || "absolute"; + draggableContainer.style.left = currentPosition.left; + draggableContainer.style.top = currentPosition.top; + console.log("Restored position:", currentPosition); + } + + // Preserve exact size + if (currentSize) { + draggableContainer.style.width = currentSize.width; + draggableContainer.style.height = currentSize.height; + console.log("Restored size:", currentSize); + } + + // Preserve z-index + if (currentZIndex) { + draggableContainer.style.zIndex = currentZIndex; + } + + // Reset any max-width/max-height constraints that might interfere + this.selectedImageElement.style.width = "100%"; + this.selectedImageElement.style.height = "100%"; + this.selectedImageElement.style.objectFit = "cover"; + + // Ensure the container maintains its positioning + draggableContainer.style.boxSizing = "border-box"; + draggableContainer.style.overflow = "hidden"; + } + + this.showSuccess("Image updated successfully!"); + } else { + this.showError("Failed to update image: Invalid element type"); + } + } catch (error) { + console.error("Error in replaceImageSrc:", error); + this.showError("Failed to update image. Please try again."); + } + } + + // Template Save/Load/Export Methods + openSaveDialog() { + this.showSaveDialog = true; + this.saveTemplateName = ""; + document.body.style.overflow = "hidden"; + } + closeSaveDialog() { + this.showSaveDialog = false; + document.body.style.overflow = ""; + } + + handleSaveNameChange(event) { + this.saveTemplateName = event.target.value; + } + + saveTemplate() { + if (!this.saveTemplateName.trim()) { + this.showError("Please enter a template name"); + return; + } + + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) { + this.showError("No template content to save"); + return; + } + + // Clone the editor content to preserve all styles and positioning + const clonedEditor = editor.cloneNode(true); + + // Process draggable elements to ensure proper positioning is preserved + const draggableElements = clonedEditor.querySelectorAll( + ".draggable-element, .draggable-image-container, .draggable-table-container" + ); + + draggableElements.forEach((element) => { + // Ensure absolute positioning is maintained + if (element.style.position !== "absolute") { + element.style.position = "absolute"; + } + + // Ensure all positioning values are preserved + const computedStyle = window.getComputedStyle(element); + if (!element.style.left && computedStyle.left !== "auto") { + element.style.left = computedStyle.left; + } + if (!element.style.top && computedStyle.top !== "auto") { + element.style.top = computedStyle.top; + } + if (!element.style.width && computedStyle.width !== "auto") { + element.style.width = computedStyle.width; + } + if (!element.style.height && computedStyle.height !== "auto") { + element.style.height = computedStyle.height; + } + if (!element.style.zIndex && computedStyle.zIndex !== "auto") { + element.style.zIndex = computedStyle.zIndex; + } + + // Ensure images inside draggable containers maintain proper styling + const images = element.querySelectorAll("img"); + images.forEach((img) => { + img.style.width = "100%"; + img.style.height = "100%"; + img.style.objectFit = "cover"; + img.style.display = "block"; + }); + + // Remove any editor-specific classes or attributes that might interfere + element.classList.remove("selected", "dragging", "resizing"); + element.removeAttribute("data-draggable"); + }); + + // Get the processed HTML content + const processedContent = clonedEditor.innerHTML; + + const templateData = { + id: Date.now().toString(), + name: this.saveTemplateName.trim(), + content: processedContent, + pageSize: this.selectedPageSize, + baseTemplateId: this.selectedTemplateId, + propertyId: this.selectedPropertyId, + savedAt: new Date().toISOString(), + thumbnail: this.generateThumbnail(editor), + }; + + // Get existing saved templates from localStorage + const savedTemplates = JSON.parse( + localStorage.getItem("savedTemplates") || "[]" + ); + savedTemplates.push(templateData); + localStorage.setItem("savedTemplates", JSON.stringify(savedTemplates)); + + this.loadSavedTemplates(); + this.closeSaveDialog(); + this.showSuccess(`Template "${this.saveTemplateName}" saved successfully!`); + } + + generateThumbnail(editor) { + // Create a simple text preview of the template + const textContent = editor.textContent || editor.innerText || ""; + return ( + textContent.substring(0, 100) + (textContent.length > 100 ? "..." : "") + ); + } + + openLoadDialog() { + this.loadSavedTemplates(); + this.showLoadDialog = true; + document.body.style.overflow = "hidden"; + } + + closeLoadDialog() { + this.showLoadDialog = false; + document.body.style.overflow = ""; + } + + loadSavedTemplates() { + const saved = JSON.parse(localStorage.getItem("savedTemplates") || "[]"); + this.savedTemplates = saved.map((template) => ({ + ...template, + formattedDate: new Date(template.savedAt).toLocaleDateString(), + })); + } + + loadTemplate(event) { + const templateId = event.currentTarget.dataset.templateId; + const template = this.savedTemplates.find((t) => t.id === templateId); + + if (template) { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor) { + editor.innerHTML = template.content; + this.selectedPageSize = template.pageSize || "A4"; + this.htmlContent = template.content; + + // Update page size radio buttons + const pageRadios = this.template.querySelectorAll( + 'input[name="pageSize"]' + ); + pageRadios.forEach((radio) => { + radio.checked = radio.value === this.selectedPageSize; + }); + + this.closeLoadDialog(); + this.showSuccess(`Template "${template.name}" loaded successfully!`); + + // Re-setup editor functionality setTimeout(() => { - const uploadDropzone = this.template.querySelector('.upload-dropzone'); - if (uploadDropzone) { - } else { - } + this.ensureEditorEditable(); + this.setupEditorClickHandler(); }, 100); + } + } + } + + deleteTemplate(event) { + event.stopPropagation(); + const templateId = event.currentTarget.dataset.templateId; + const template = this.savedTemplates.find((t) => t.id === templateId); + + if ( + template && + confirm(`Are you sure you want to delete "${template.name}"?`) + ) { + const savedTemplates = JSON.parse( + localStorage.getItem("savedTemplates") || "[]" + ); + const filtered = savedTemplates.filter((t) => t.id !== templateId); + localStorage.setItem("savedTemplates", JSON.stringify(filtered)); + this.loadSavedTemplates(); + this.showSuccess("Template deleted successfully"); + } + } + exportHtml() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) { + this.showError("No template content to export"); + return; } - selectReplacementCategory(event) { - const category = event.target.dataset.category; - - this.replacementSelectedCategory = category; - this.filterReplacementImages(); - - // Update active state for category buttons - const categoryButtons = this.template.querySelectorAll('.category-btn-step2'); - categoryButtons.forEach(btn => { - btn.classList.remove('active'); - if (btn.dataset.category === this.replacementSelectedCategory) { - btn.classList.add('active'); - } - }); - } + // Clone the editor content to preserve all styles and positioning + const clonedEditor = editor.cloneNode(true); - filterReplacementImages() { - - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - this.filteredReplacementImages = []; - return; - } - - // Filter images by category using the same logic as Step 2 - const filteredImages = this.realPropertyImages.filter(img => { - const imgCategory = img.category || img.pcrm__Category__c; - - // Handle "None" category - show images with no category or empty category - if (this.replacementSelectedCategory === 'None') { - return !imgCategory || imgCategory === '' || imgCategory === null || imgCategory === undefined || imgCategory === 'None'; - } - - return imgCategory === this.replacementSelectedCategory; - }); - - - this.filteredReplacementImages = filteredImages.map((img, index) => ({ - id: `${this.replacementSelectedCategory}-${index}`, - url: img.url, - title: img.title || img.name || `Image ${index + 1}`, - category: img.category || img.pcrm__Category__c || 'None' - })); - } + // Process draggable elements to ensure proper positioning is preserved + const draggableElements = clonedEditor.querySelectorAll( + ".draggable-element, .draggable-image-container, .draggable-table-container" + ); - selectReplacementImage(event) { - const imageUrl = event.currentTarget.dataset.imageUrl; - - if (!imageUrl) { - this.showError('Failed to get image URL. Please try again.'); - return; - } - - this.replaceImageSrc(imageUrl); - this.closeImageReplacement(); - } + draggableElements.forEach((element) => { + // Ensure absolute positioning is maintained + if (element.style.position !== "absolute") { + element.style.position = "absolute"; + } - triggerImageReplacementFileUpload() { - - // Try to find the image upload input in the replacement modal - const fileInput = this.template.querySelector('.image-upload-input'); - if (fileInput) { - // Reset the input to allow selecting the same file again - fileInput.value = ''; - fileInput.click(); - } else { - // Fallback: create a new input programmatically - const input = document.createElement('input'); - input.type = 'file'; - input.accept = 'image/*'; - input.style.display = 'none'; - input.onchange = (e) => this.handleImageUpload(e); - document.body.appendChild(input); - input.click(); - // Don't remove immediately, let the handler process first - setTimeout(() => { - if (document.body.contains(input)) { - document.body.removeChild(input); - } - }, 100); - } - } + // Ensure all positioning values are preserved + const computedStyle = window.getComputedStyle(element); + if (!element.style.left && computedStyle.left !== "auto") { + element.style.left = computedStyle.left; + } + if (!element.style.top && computedStyle.top !== "auto") { + element.style.top = computedStyle.top; + } + if (!element.style.width && computedStyle.width !== "auto") { + element.style.width = computedStyle.width; + } + if (!element.style.height && computedStyle.height !== "auto") { + element.style.height = computedStyle.height; + } + if (!element.style.zIndex && computedStyle.zIndex !== "auto") { + element.style.zIndex = computedStyle.zIndex; + } - handleImageUpload(event) { - const file = event.target.files[0]; - - if (!file) { - return; - } - - - // Validate file type - if (!file.type.startsWith('image/')) { - this.showError('Please select a valid image file (JPG, PNG, GIF, WebP)'); - return; - } - - // Validate file size (e.g., max 10MB) - const maxSize = 10 * 1024 * 1024; // 10MB - if (file.size > maxSize) { - this.showError('File size must be less than 10MB'); - return; - } - - const reader = new FileReader(); - reader.onload = (e) => { - this.uploadedImagePreview = e.target.result; - - // Show success message - this.showSuccess('✅ Image uploaded successfully! Click "Use This Image" to apply it.'); - - // Force re-render to show the preview - this.forceRerender(); - }; - - reader.onerror = (e) => { - this.showError('Error reading the selected file. Please try again.'); - }; - - reader.readAsDataURL(file); - } + // Ensure images inside draggable containers maintain proper styling + const images = element.querySelectorAll("img"); + images.forEach((img) => { + img.style.width = "100%"; + img.style.height = "100%"; + img.style.objectFit = "cover"; + img.style.display = "block"; + }); - useUploadedImage() { - if (this.uploadedImagePreview) { - this.replaceImageSrc(this.uploadedImagePreview); - this.closeImageReplacement(); - } - } + // Remove any editor-specific classes or attributes that might interfere + element.classList.remove("selected", "dragging", "resizing"); + element.removeAttribute("data-draggable"); + }); - // Drag and drop handlers for image upload - handleDragOver(event) { - event.preventDefault(); - event.stopPropagation(); - const dropzone = event.currentTarget; - dropzone.classList.add('drag-over'); - } + // Get the processed HTML content + const htmlContent = clonedEditor.innerHTML; - handleDragLeave(event) { - event.preventDefault(); - event.stopPropagation(); - const dropzone = event.currentTarget; - dropzone.classList.remove('drag-over'); - } - - handleDrop(event) { - event.preventDefault(); - event.stopPropagation(); - - const dropzone = event.currentTarget; - dropzone.classList.remove('drag-over'); - - const files = event.dataTransfer.files; - if (files.length > 0) { - const file = files[0]; - - // Validate file type - if (!file.type.startsWith('image/')) { - this.showError('Please drop a valid image file (JPG, PNG, GIF, WebP)'); - return; - } - - // Validate file size (e.g., max 10MB) - const maxSize = 10 * 1024 * 1024; // 10MB - if (file.size > maxSize) { - this.showError('File size must be less than 10MB'); - return; - } - - // Process the dropped file - const reader = new FileReader(); - reader.onload = (e) => { - this.uploadedImagePreview = e.target.result; - - // Show success message - this.showSuccess('✅ Image uploaded successfully! Click "Use This Image" to apply it.'); - - // Force re-render to show the preview - this.forceRerender(); - }; - - reader.onerror = (e) => { - this.showError('Error reading the dropped file. Please try again.'); - }; - - reader.readAsDataURL(file); - } - } - - replaceImageSrc(newImageUrl) { - if (!this.selectedImageElement || !newImageUrl) { - return; - } - - try { - // Save undo state before making changes - this.saveUndoState(); - - // Handle background images - if (this.selectedImageElement.isBackgroundImage) { - // Use the stored original element reference if available - if (this.selectedImageElement.originalElement) { - this.selectedImageElement.originalElement.style.backgroundImage = `url("${newImageUrl}")`; - this.showSuccess('Background image updated successfully!'); - return; - } - - // Fallback: Find the actual DOM element that has the background image - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor) { - // Find all elements with background images and update the one that matches - const allElements = editor.querySelectorAll('*'); - for (let element of allElements) { - const computedStyle = window.getComputedStyle(element); - const currentBgImage = computedStyle.backgroundImage; - if (currentBgImage && currentBgImage !== 'none') { - // Check if this is the element we want to update - const currentBgUrl = currentBgImage.replace(/url\(['"]?(.+?)['"]?\)/, '$1'); - if (currentBgUrl === this.selectedImageElement.src) { - element.style.backgroundImage = `url("${newImageUrl}")`; - this.showSuccess('Background image updated successfully!'); - return; - } - } - } - } - this.showError('Failed to update background image. Please try again.'); - return; - } - - // Handle regular img elements - if (this.selectedImageElement.tagName === 'IMG') { - this.selectedImageElement.src = newImageUrl; - - // If the image is inside a draggable container, ensure it maintains proper styling - const draggableContainer = this.selectedImageElement.closest('.draggable-element'); - if (draggableContainer) { - // Reset any max-width/max-height constraints that might interfere - this.selectedImageElement.style.width = '100%'; - this.selectedImageElement.style.height = '100%'; - this.selectedImageElement.style.objectFit = 'cover'; - } - - this.showSuccess('Image updated successfully!'); - } else { - this.showError('Failed to update image: Invalid element type'); - } - } catch (error) { - this.showError('Failed to update image. Please try again.'); - } - } - - // Template Save/Load/Export Methods - openSaveDialog() { - this.showSaveDialog = true; - this.saveTemplateName = ''; - document.body.style.overflow = 'hidden'; - } - - closeSaveDialog() { - this.showSaveDialog = false; - document.body.style.overflow = ''; - } - - handleSaveNameChange(event) { - this.saveTemplateName = event.target.value; - } - - saveTemplate() { - if (!this.saveTemplateName.trim()) { - this.showError('Please enter a template name'); - return; - } - - const editor = this.template.querySelector('.enhanced-editor-content'); - if (!editor) { - this.showError('No template content to save'); - return; - } - - const templateData = { - id: Date.now().toString(), - name: this.saveTemplateName.trim(), - content: editor.innerHTML, - pageSize: this.selectedPageSize, - baseTemplateId: this.selectedTemplateId, - propertyId: this.selectedPropertyId, - savedAt: new Date().toISOString(), - thumbnail: this.generateThumbnail(editor) - }; - - // Get existing saved templates from localStorage - const savedTemplates = JSON.parse(localStorage.getItem('savedTemplates') || '[]'); - savedTemplates.push(templateData); - localStorage.setItem('savedTemplates', JSON.stringify(savedTemplates)); - - this.loadSavedTemplates(); - this.closeSaveDialog(); - this.showSuccess(`Template "${this.saveTemplateName}" saved successfully!`); - } - - generateThumbnail(editor) { - // Create a simple text preview of the template - const textContent = editor.textContent || editor.innerText || ''; - return textContent.substring(0, 100) + (textContent.length > 100 ? '...' : ''); - } - - openLoadDialog() { - this.loadSavedTemplates(); - this.showLoadDialog = true; - document.body.style.overflow = 'hidden'; - } - - closeLoadDialog() { - this.showLoadDialog = false; - document.body.style.overflow = ''; - } - - loadSavedTemplates() { - const saved = JSON.parse(localStorage.getItem('savedTemplates') || '[]'); - this.savedTemplates = saved.map(template => ({ - ...template, - formattedDate: new Date(template.savedAt).toLocaleDateString() - })); - } - - loadTemplate(event) { - const templateId = event.currentTarget.dataset.templateId; - const template = this.savedTemplates.find(t => t.id === templateId); - - if (template) { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor) { - editor.innerHTML = template.content; - this.selectedPageSize = template.pageSize || 'A4'; - this.htmlContent = template.content; - - // Update page size radio buttons - const pageRadios = this.template.querySelectorAll('input[name="pageSize"]'); - pageRadios.forEach(radio => { - radio.checked = radio.value === this.selectedPageSize; - }); - - this.closeLoadDialog(); - this.showSuccess(`Template "${template.name}" loaded successfully!`); - - // Re-setup editor functionality - setTimeout(() => { - this.ensureEditorEditable(); - this.setupEditorClickHandler(); - }, 100); - } - } - } - - deleteTemplate(event) { - event.stopPropagation(); - const templateId = event.currentTarget.dataset.templateId; - const template = this.savedTemplates.find(t => t.id === templateId); - - if (template && confirm(`Are you sure you want to delete "${template.name}"?`)) { - const savedTemplates = JSON.parse(localStorage.getItem('savedTemplates') || '[]'); - const filtered = savedTemplates.filter(t => t.id !== templateId); - localStorage.setItem('savedTemplates', JSON.stringify(filtered)); - this.loadSavedTemplates(); - this.showSuccess('Template deleted successfully'); - } - } - - exportHtml() { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (!editor) { - this.showError('No template content to export'); - return; - } - - // Use the raw HTML content - const htmlContent = editor.innerHTML; - - // Create a complete HTML document - const fullHtml = ` + // Create a complete HTML document with enhanced positioning support + const fullHtml = ` @@ -7404,13 +16616,39 @@ export default class PropertyTemplateSelector extends LightningElement { line-height: 1.6; } .template-content { - max-width: ${this.selectedPageSize === 'A3' ? '297mm' : '210mm'}; + position: relative; + max-width: ${this.selectedPageSize === "A3" ? "297mm" : "210mm"}; margin: 0 auto; background: white; box-shadow: 0 0 20px rgba(0,0,0,0.1); border-radius: 8px; overflow: hidden; + min-height: ${this.selectedPageSize === "A3" ? "420mm" : "297mm"}; } + + /* Enhanced positioning for draggable elements */ + .draggable-element, .draggable-image-container, .draggable-table-container { + position: absolute !important; + box-sizing: border-box !important; + display: block !important; + margin: 0 !important; + padding: 0 !important; + } + + .draggable-element img, .draggable-image-container img { + width: 100% !important; + height: 100% !important; + object-fit: cover !important; + display: block !important; + } + + .draggable-table-container table { + width: 100% !important; + height: 100% !important; + border-collapse: collapse !important; + } + + /* Regular content styling */ img { max-width: 100%; height: auto; @@ -7419,9 +16657,23 @@ export default class PropertyTemplateSelector extends LightningElement { ul { list-style-type: disc; padding-left: 22px; margin: 0 0 8px 0; } ol { list-style-type: decimal; padding-left: 22px; margin: 0 0 8px 0; } li { margin: 4px 0; } - .draggable-element { - position: relative; + + /* Remove any editor-specific styling */ + .delete-handle, .resize-handle, .delete-image-btn, .text-close-btn, .table-close-btn { + display: none !important; } + + /* A4 page editor styling */ + .a4-page { + isolation: isolate; + contain: layout; + } + + .a4-page .page-footer { + isolation: isolate; + contain: layout; + } + @media print { body { margin: 0; padding: 0; } .template-content { @@ -7429,6 +16681,57 @@ export default class PropertyTemplateSelector extends LightningElement { border-radius: 0; max-width: none; } + + /* A4 page print styling */ + .a4-page { + width: 210mm !important; + height: 297mm !important; + margin: 0 !important; + padding: 0 !important; + box-shadow: none !important; + border: none !important; + border-radius: 0 !important; + page-break-after: always; + page-break-inside: avoid !important; + position: relative !important; + overflow: hidden !important; + box-sizing: border-box !important; + } + + .a4-page:last-child { + page-break-after: avoid; + } + + .a4-page .page-content { + flex: 1 !important; + padding: 20px !important; + overflow: hidden !important; + display: flex !important; + flex-direction: column !important; + box-sizing: border-box !important; + max-height: calc(297mm - 60px) !important; + } + + .a4-page .page-footer { + flex-shrink: 0 !important; + height: 60px !important; + background-color: #003366 !important; + color: white !important; + padding: 10px 20px !important; + display: flex !important; + justify-content: space-between !important; + align-items: center !important; + font-size: 12px !important; + page-break-inside: avoid !important; + box-sizing: border-box !important; + margin: 0 !important; + } + + .a4-page .page-footer img { + height: 20px !important; + max-width: 40px !important; + object-fit: contain !important; + } } @@ -7439,1261 +16742,1721 @@ export default class PropertyTemplateSelector extends LightningElement { `; - this.exportedHtml = fullHtml; - this.showHtmlDialog = true; - document.body.style.overflow = 'hidden'; + this.exportedHtml = fullHtml; + this.showHtmlDialog = true; + document.body.style.overflow = "hidden"; + } + + closeHtmlDialog() { + this.showHtmlDialog = false; + document.body.style.overflow = ""; + } + + copyHtmlToClipboard() { + if (navigator.clipboard) { + navigator.clipboard + .writeText(this.exportedHtml) + .then(() => { + this.showSuccess("HTML copied to clipboard!"); + }) + .catch(() => { + this.fallbackCopyToClipboard(); + }); + } else { + this.fallbackCopyToClipboard(); + } + } + + fallbackCopyToClipboard() { + const textArea = document.createElement("textarea"); + textArea.value = this.exportedHtml; + textArea.style.position = "fixed"; + textArea.style.left = "-999999px"; + textArea.style.top = "-999999px"; + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + + try { + document.execCommand("copy"); + this.showSuccess("HTML copied to clipboard!"); + } catch (err) { + this.showError("Failed to copy HTML"); } - closeHtmlDialog() { - this.showHtmlDialog = false; - document.body.style.overflow = ''; + document.body.removeChild(textArea); + } + + showHtml() { + // Get the current template content + const previewFrame = this.template.querySelector(".enhanced-editor-content"); + if (!previewFrame) { + this.showError("No content found to export."); + return; } - copyHtmlToClipboard() { - if (navigator.clipboard) { - navigator.clipboard.writeText(this.exportedHtml).then(() => { - this.showSuccess('HTML copied to clipboard!'); - }).catch(() => { - this.fallbackCopyToClipboard(); - }); - } else { - this.fallbackCopyToClipboard(); + // Get the HTML content from the editor + let htmlContent = previewFrame.innerHTML; + + // If no content in editor, try to get from cached template + if (!htmlContent || htmlContent.trim() === "") { + htmlContent = this.htmlContent || this.createTemplateHTML(); + } + + // Create a complete HTML document + const fullHtml = ` + + + + + Property Brochure + + + +
    + ${htmlContent} +
    + +`; + + this.exportedHtml = fullHtml; + this.showHtmlDialog = true; + document.body.style.overflow = "hidden"; + } + + downloadHtml() { + if (!this.exportedHtml) { + this.showError("No HTML content to download. Please export HTML first."); + return; } - downloadHtml() { - const blob = new Blob([this.exportedHtml], { type: 'text/html' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `property-brochure-${Date.now()}.html`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - this.showSuccess('HTML file downloaded!'); + const blob = new Blob([this.exportedHtml], { type: "text/html" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `property-brochure-${Date.now()}.html`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + this.showSuccess("HTML file downloaded!"); + } + + // Table Dialog Methods + openTableDialog() { + this.showTableDialog = true; + document.body.style.overflow = "hidden"; + } + + closeTableDialog() { + this.showTableDialog = false; + document.body.style.overflow = ""; + } + + handleTableRowsChange(event) { + this.tableRows = parseInt(event.target.value) || 3; + } + + handleTableColsChange(event) { + this.tableCols = parseInt(event.target.value) || 3; + } + + handleHeaderChange(event) { + this.includeHeader = event.target.checked; + } + + insertTable() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) { + this.showError("Editor not found"); + return; } - // Table Dialog Methods - openTableDialog() { - this.showTableDialog = true; - document.body.style.overflow = 'hidden'; + // Save undo state + this.saveUndoState(); + + // Get center position for insertion + const centerPos = this.getCenterPositionForElement('table'); + + // Create table element using our new method (draggable/resizeable container like images) + const tableContainer = this.createTableElement(); + editor.appendChild(tableContainer); + // Place at center position + tableContainer.style.left = `${centerPos.x}px`; + tableContainer.style.top = `${centerPos.y}px`; + // Enable drag + resize + this.addTableResizeHandles(tableContainer); + this.makeDraggable(tableContainer); + this.setupTableEventListeners(tableContainer); + + this.closeTableDialog(); + this.showSuccess("Table inserted successfully!"); + } + + // ===== DYNAMIC IMAGE REPLACEMENT UTILITIES ===== + + // Get first image from a specific category + getFirstImageByCategory(category) { + if (!this.realPropertyImages || this.realPropertyImages.length === 0) { + return null; } - closeTableDialog() { - this.showTableDialog = false; - document.body.style.overflow = ''; + const categoryImages = this.realPropertyImages.filter((img) => { + const imgCategory = img.category || img.pcrm__Category__c; + return ( + imgCategory && imgCategory.toLowerCase() === category.toLowerCase() + ); + }); + + return categoryImages.length > 0 ? categoryImages[0] : null; + } + // Direct method to get exterior image URL + getExteriorImageUrl() { + if (!this.realPropertyImages || this.realPropertyImages.length === 0) { + return ""; } - handleTableRowsChange(event) { - this.tableRows = parseInt(event.target.value) || 3; + // Look for exterior images first + const exteriorImages = this.realPropertyImages.filter((img) => { + const category = img.category || img.pcrm__Category__c; + return category && category.toLowerCase().includes("exterior"); + }); + + if (exteriorImages.length > 0) { + return exteriorImages[0].url; } - handleTableColsChange(event) { - this.tableCols = parseInt(event.target.value) || 3; + // If no exterior, use first available image + if (this.realPropertyImages.length > 0) { + return this.realPropertyImages[0].url; } - handleHeaderChange(event) { - this.includeHeader = event.target.checked; + return ""; + } + // Direct method to get maps image URL + getMapsImageUrl() { + if (!this.realPropertyImages || this.realPropertyImages.length === 0) { + return ""; } - insertTable() { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (!editor) { - this.showError("Editor not found"); - return; + // Look for maps images first - check both exact match and contains + const mapsImages = this.realPropertyImages.filter((img) => { + const category = img.category || img.pcrm__Category__c; + return ( + category && + (category.toLowerCase() === "maps" || + category.toLowerCase().includes("maps")) + ); + }); + + if (mapsImages.length > 0) { + return mapsImages[0].url; + } + + // Look for anchor images as fallback + const anchorImages = this.realPropertyImages.filter((img) => { + const category = img.category || img.pcrm__Category__c; + return ( + category && + (category.toLowerCase() === "anchor" || + category.toLowerCase().includes("anchor")) + ); + }); + + if (anchorImages.length > 0) { + return anchorImages[0].url; + } + + return ""; + } + + // Get random image from property images + getRandomImage() { + const allImages = Array.isArray(this.realPropertyImages) ? this.realPropertyImages : []; + + if (allImages.length === 0) { + // Return a default placeholder image if no property images available + return 'https://via.placeholder.com/400x300?text=No+Image+Available'; + } + + // Get a random index + const randomIndex = Math.floor(Math.random() * allImages.length); + const randomImage = allImages[randomIndex]; + + // Ensure image URL is absolute for PDF generation + if (randomImage && randomImage.url) { + return randomImage.url.startsWith('http') ? randomImage.url : + `https://salesforce.tech4biz.io${randomImage.url}`; + } + + // Fallback to placeholder if image URL is invalid + return 'https://via.placeholder.com/400x300?text=No+Image+Available'; + } + + // Method to replace background-image URLs in CSS at runtime + replaceBackgroundImagesInHTML(htmlContent) { + if (!this.realPropertyImages || this.realPropertyImages.length === 0) { + return htmlContent; + } + + const exteriorImageUrl = this.getExteriorImageUrl(); + + // Replace any hardcoded background-image URLs with the property's exterior image + let updatedHTML = htmlContent; + + // Pattern to match background-image: url('...') or background-image: url("...") + const backgroundImagePattern = + /background-image\s*:\s*url\(['"][^'"]*['"]\)/gi; + + // Replace all background-image URLs with the property's exterior image + updatedHTML = updatedHTML.replace(backgroundImagePattern, (match) => { + return exteriorImageUrl + ? `background-image: url('${exteriorImageUrl}')` + : "background-image: none"; + }); + + return updatedHTML; + } + // Method to dynamically update CSS background-image rules after template loads + updateCSSBackgroundImages() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) return; + if (!this.realPropertyImages || this.realPropertyImages.length === 0) + return; + const exteriorImageUrl = this.getExteriorImageUrl(); + // Scope to styles inside the editor only + const styleElements = editor.querySelectorAll("style"); + styleElements.forEach((styleElement) => { + const cssText = styleElement.textContent || ""; + const backgroundImagePattern = + /background-image\s*:\s*url\(['"][^'"]*['"]\)/gi; + const updatedCSS = cssText.replace(backgroundImagePattern, (match) => { + return exteriorImageUrl + ? `background-image: url('${exteriorImageUrl}')` + : "background-image: none"; + }); + if (updatedCSS !== cssText) styleElement.textContent = updatedCSS; + }); + // Update inline background-image styles only within editor + const elementsWithBackground = editor.querySelectorAll( + '[style*="background-image"]' + ); + elementsWithBackground.forEach((element) => { + const currentStyle = element.getAttribute("style") || ""; + const backgroundImagePattern = + /background-image\s*:\s*url\(['"][^'"]*['"]\)/gi; + const updatedStyle = currentStyle.replace( + backgroundImagePattern, + (match) => { + return exteriorImageUrl + ? `background-image: url('${exteriorImageUrl}')` + : "background-image: none"; + } + ); + if (updatedStyle !== currentStyle) + element.setAttribute("style", updatedStyle); + }); + } + + // Force image reload to ensure proper display in viewport + forceImageReload() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) return; + + // Find all images in the viewport + const images = editor.querySelectorAll("img"); + images.forEach((img) => { + if (img.src) { + // Force reload by adding timestamp to URL + const originalSrc = img.src; + const url = new URL(originalSrc); + url.searchParams.set('t', Date.now().toString()); + img.src = url.toString(); + + // Add error handling for failed images + img.onerror = () => { + console.warn('Image failed to load:', originalSrc); + // Try to reload with original URL as fallback + img.src = originalSrc; + }; + + // Ensure images are properly sized for viewport + img.style.maxWidth = '100%'; + img.style.height = 'auto'; + img.style.display = 'block'; + } + }); + + // Also handle background images + const elementsWithBg = editor.querySelectorAll('[style*="background-image"]'); + elementsWithBg.forEach((element) => { + const style = element.getAttribute('style') || ''; + if (style.includes('background-image')) { + // Force reload by updating the style + const newStyle = style.replace(/url\(['"]([^'"]*)['"]\)/g, (match, url) => { + const urlObj = new URL(url); + urlObj.searchParams.set('t', Date.now().toString()); + return `url('${urlObj.toString()}')`; + }); + element.setAttribute('style', newStyle); + } + }); + } + + // Force proper HTML rendering to match PDF exactly + forceHTMLRendering() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) return; + + // Apply exact PDF dimensions and styling + const baseWidth = this.selectedPageSize === "A3" ? 1123 : 794; + const baseHeight = this.selectedPageSize === "A3" ? 1587 : 1123; + + // Set exact dimensions on the editor + editor.style.width = `${baseWidth}px`; + editor.style.minHeight = `${baseHeight}px`; + editor.style.maxWidth = `${baseWidth}px`; + editor.style.margin = '0'; + editor.style.padding = '0'; + editor.style.boxSizing = 'border-box'; + editor.style.background = 'white'; + editor.style.color = '#000'; + editor.style.fontSize = '12px'; + editor.style.lineHeight = '1.3'; + + // Ensure all content elements are properly sized + const allElements = editor.querySelectorAll('*'); + allElements.forEach((element) => { + // Reset any conflicting styles + element.style.boxSizing = 'border-box'; + + // Fix images + if (element.tagName === 'IMG') { + element.style.maxWidth = '100%'; + element.style.height = 'auto'; + element.style.display = 'block'; + element.style.margin = '0 0 6px 0'; + element.style.padding = '0'; + } + + // Fix headings + if (['H1', 'H2', 'H3', 'H4', 'H5', 'H6'].includes(element.tagName)) { + element.style.margin = '0 0 8px 0'; + element.style.padding = '0'; + element.style.fontWeight = 'bold'; + } + + // Fix paragraphs + if (element.tagName === 'P') { + element.style.margin = '0 0 6px 0'; + element.style.padding = '0'; + } + + // Fix tables + if (element.tagName === 'TABLE') { + element.style.borderCollapse = 'collapse'; + element.style.borderSpacing = '0'; + element.style.width = '100%'; + element.style.margin = '0 0 8px 0'; + element.style.padding = '0'; + } + + // Fix table cells + if (['TD', 'TH'].includes(element.tagName)) { + element.style.margin = '0'; + element.style.padding = '3px'; + element.style.border = 'none'; + element.style.verticalAlign = 'top'; + } + }); + + // Force a reflow to ensure changes take effect + editor.offsetHeight; + } + + // Get all images from a specific category + getAllImagesByCategory(category) { + if (!this.realPropertyImages || this.realPropertyImages.length === 0) { + return []; + } + + return this.realPropertyImages.filter( + (img) => + img.category && img.category.toLowerCase() === category.toLowerCase() + ); + } + + // Get uncategorized images (no category or category is null/empty) + getUncategorizedImages() { + if (!this.realPropertyImages || this.realPropertyImages.length === 0) { + return []; + } + + const uncategorized = this.realPropertyImages.filter( + (img) => + !img.category || + img.category.trim() === "" || + img.category.toLowerCase() === "none" + ); + + return uncategorized; + } + + // Smart image replacement - tries multiple categories in order of preference + getSmartImageForSection(sectionType, fallbackUrl) { + const categoryPriority = { + exterior: ["Exterior", "Anchor", "None"], + interior: ["Interior", "Living Area", "Kitchen", "Bedroom", "None"], + kitchen: ["Kitchen", "Interior", "Living Area", "None"], + bedroom: ["Bedroom", "Interior", "None"], + living: ["Living Area", "Interior", "Kitchen", "None"], + bathroom: ["Bathroom", "Interior", "None"], + parking: ["Parking", "Exterior", "None"], + maps: ["Maps", "Anchor", "Exterior", "None"], + gallery: [ + "Interior", + "Exterior", + "Kitchen", + "Bedroom", + "Living Area", + "None", + ], + }; + + const categories = categoryPriority[sectionType] || [ + "Interior", + "Exterior", + "None", + ]; + + for (const category of categories) { + const image = this.getFirstImageByCategory(category); + if (image && image.url) { + return image.url; + } + } + + return fallbackUrl; + } + + // Generate Property Gallery HTML for uncategorized images + generatePropertyGalleryHTML() { + if (!this.realPropertyImages || this.realPropertyImages.length === 0) { + return ""; + } + + let galleryHTML = ""; + this.realPropertyImages.forEach((image, index) => { + const title = + image.title || image.pcrm__Title__c || `Property Image ${index + 1}`; + galleryHTML += ``; + }); + + return galleryHTML; + } + // Generate gallery HTML for a provided subset of images + generatePropertyGalleryHTMLForImages(imagesSubset) { + if (!imagesSubset || imagesSubset.length === 0) { + return ""; + } + let galleryHTML = ""; + imagesSubset.forEach((image, index) => { + const title = + image.title || image.pcrm__Title__c || `Property Image ${index + 1}`; + galleryHTML += ``; + }); + return galleryHTML; + } + + // ===== TABLE DRAG AND DROP FUNCTIONALITY ===== + // Handle table drag start + handleTableDragStart(event) { + this.isDraggingTable = true; + + // Store table configuration data + this.draggedTableData = { + rows: this.tableRows, + cols: this.tableCols, + includeHeader: this.includeHeader, + }; + + // Set drag data + event.dataTransfer.setData("text/plain", "table"); + event.dataTransfer.effectAllowed = "copy"; + + // Add visual feedback + event.currentTarget.classList.add("dragging"); + + // Add drag over class to editor + setTimeout(() => { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor) { + editor.classList.add("drag-over"); + } + }, 100); + } + + // Handle editor drag over + handleEditorDragOver(event) { + // Allow dropping tables and images + event.preventDefault(); + event.dataTransfer.dropEffect = "copy"; + } + + // Handle editor drop + handleEditorDrop(event) { + event.preventDefault(); + + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) { + this.showError("Editor not found"); + return; + } + + const dataType = event.dataTransfer.getData("text/plain"); + if (dataType === "image" && this.currentImage) { + // Insert draggable image at drop + const img = document.createElement("img"); + img.src = this.currentImage.url; + img.style.maxWidth = "300px"; + img.style.height = "auto"; + img.className = "draggable-image"; + + const container = document.createElement("div"); + container.className = "draggable-image-container"; + container.style.position = "absolute"; + container.style.left = + event.clientX - editor.getBoundingClientRect().left + "px"; + container.style.top = + event.clientY - editor.getBoundingClientRect().top + "px"; + container.appendChild(img); + editor.appendChild(container); + this.makeImagesDraggableAndResizable([img]); + this.showSuccess("Image inserted via drag and drop!"); + return; + } + + if (!this.isDraggingTable || !this.draggedTableData) { + return; + } + + // Remove visual feedback + this.removeTableDragFeedback(); + + // Get drop position + // editor already resolved above + + // Save undo state before making changes + this.saveUndoState(); + + // Insert table at drop position + this.insertTableAtPosition(editor, this.draggedTableData, event); + + // Reset drag state + this.isDraggingTable = false; + this.draggedTableData = null; + + this.showSuccess("Table inserted via drag and drop!"); + } + // Insert table at specific position + insertTableAtPosition(editor, tableData, event) { + // Get cursor position relative to editor + const rect = editor.getBoundingClientRect(); + const x = event.clientX - rect.left; + const y = event.clientY - rect.top; + + // Create table element directly using DOM methods (same as insertTable) + const tableId = `table-${Date.now()}-${Math.random() + .toString(36) + .substr(2, 9)}`; + + // Create container div (use draggable-table-container to get drag/resize behavior) + const container = document.createElement("div"); + container.className = "draggable-table-container"; + container.setAttribute("data-table-id", tableId); + container.style.cssText = + "position: absolute; left: 0; top: 0; width: 400px; min-width: 200px; min-height: 150px; z-index: 1000; border: 2px dashed #667eea; border-radius: 8px; background: white; box-shadow: 0 4px 12px rgba(0,0,0,0.1); overflow: hidden;"; + + // Create table controls + const controls = document.createElement("div"); + controls.className = "table-controls"; + controls.style.cssText = + "position: absolute; top: -40px; left: 0; background: white; padding: 5px; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); opacity: 1; transition: opacity 0.2s;"; + + // Add control buttons (same as insertTable) + const controlGroup1 = document.createElement("div"); + controlGroup1.className = "table-control-group"; + controlGroup1.style.cssText = "display: flex; gap: 5px;"; + + const addRowBtn = document.createElement("button"); + addRowBtn.className = "table-control-btn"; + addRowBtn.setAttribute("data-table-id", tableId); + addRowBtn.textContent = "+ Row"; + addRowBtn.style.cssText = + "padding: 4px 8px; border: 1px solid #ddd; background: white; cursor: pointer;"; + + const addColBtn = document.createElement("button"); + addColBtn.className = "table-control-btn"; + addColBtn.setAttribute("data-table-id", tableId); + addColBtn.textContent = "+ Col"; + addColBtn.style.cssText = + "padding: 4px 8px; border: 1px solid #ddd; background: white; cursor: pointer;"; + + const delRowBtn = document.createElement("button"); + delRowBtn.className = "table-control-btn"; + delRowBtn.setAttribute("data-table-id", tableId); + delRowBtn.textContent = "- Row"; + delRowBtn.style.cssText = + "padding: 4px 8px; border: 1px solid #ddd; background: white; cursor: pointer;"; + + const delColBtn = document.createElement("button"); + delColBtn.className = "table-control-btn"; + delColBtn.setAttribute("data-table-id", tableId); + delColBtn.textContent = "- Col"; + delColBtn.style.cssText = + "padding: 4px 8px; border: 1px solid #ddd; background: white; cursor: pointer;"; + + const deleteBtn = document.createElement("button"); + deleteBtn.className = "table-control-btn delete"; + deleteBtn.setAttribute("data-table-id", tableId); + deleteBtn.textContent = "🗑️"; + deleteBtn.style.cssText = + "padding: 4px 8px; border: 1px solid #ff4444; background: #ff4444; color: white; cursor: pointer;"; + + controlGroup1.appendChild(addRowBtn); + controlGroup1.appendChild(addColBtn); + controlGroup1.appendChild(delRowBtn); + controlGroup1.appendChild(delColBtn); + + const controlGroup2 = document.createElement("div"); + controlGroup2.className = "table-control-group"; + controlGroup2.style.cssText = "display: flex; gap: 5px; margin-left: 10px;"; + controlGroup2.appendChild(deleteBtn); + + controls.appendChild(controlGroup1); + controls.appendChild(controlGroup2); + + // Create table + const table = document.createElement("table"); + table.className = "inserted-table"; + table.id = tableId; + table.style.cssText = + "border-collapse: collapse; width: 100%; margin: 1rem 0; border: 2px solid #333; background-color: white;"; + + // Create table body + const tbody = document.createElement("tbody"); + // Add header row if requested + if (tableData.includeHeader) { + const thead = document.createElement("thead"); + const headerRow = document.createElement("tr"); + + for (let col = 0; col < tableData.cols; col++) { + const th = document.createElement("th"); + th.style.cssText = + "border: 1px solid #333; padding: 12px; background-color: #4f46e5; color: white; font-weight: bold; text-align: center;"; + th.setAttribute("contenteditable", "true"); + th.textContent = `Header ${col + 1}`; + headerRow.appendChild(th); + } + + thead.appendChild(headerRow); + table.appendChild(thead); + } + // Add body rows + for (let row = 0; row < tableData.rows; row++) { + const tr = document.createElement("tr"); + + for (let col = 0; col < tableData.cols; col++) { + const td = document.createElement("td"); + td.style.cssText = + "border: 1px solid #333; padding: 12px; background-color: #f8f9fa; min-width: 100px; min-height: 40px;"; + td.setAttribute("contenteditable", "true"); + td.textContent = `Cell ${row + 1}-${col + 1}`; + tr.appendChild(td); + } + + tbody.appendChild(tr); + } + + table.appendChild(tbody); + + // Assemble the container + container.appendChild(controls); + container.appendChild(table); + + const tableElement = container; + + // Try to find the best insertion point + const range = document.createRange(); + const walker = document.createTreeWalker( + editor, + NodeFilter.SHOW_TEXT, + null, + false + ); + + let bestNode = null; + let bestDistance = Infinity; + let node; + + // Find the closest text node to the drop position + while ((node = walker.nextNode())) { + const nodeRect = node.getBoundingClientRect(); + const nodeX = nodeRect.left - rect.left; + const nodeY = nodeRect.top - rect.top; + const distance = Math.sqrt((x - nodeX) ** 2 + (y - nodeY) ** 2); + + if (distance < bestDistance) { + bestDistance = distance; + bestNode = node; + } + } + + // Insert into editor (append at end for simplicity) + editor.appendChild(tableElement); + // Position at drop point + tableElement.style.left = + Math.max(0, Math.min(x, editor.clientWidth - tableElement.offsetWidth)) + + "px"; + tableElement.style.top = + Math.max( + 0, + Math.min(y, editor.scrollHeight - tableElement.offsetHeight) + ) + "px"; + + // Add drag/resize to the new table + this.addTableResizeHandles(tableElement); + this.makeDraggable(tableElement); + this.setupTableEventListeners(tableElement); + } + + // Remove table drag feedback + removeTableDragFeedback() { + // Remove dragging class from button + const tableBtn = this.template.querySelector(".draggable-table-btn"); + if (tableBtn) { + tableBtn.classList.remove("dragging"); + } + + // Remove drag-over class from editor + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor) { + editor.classList.remove("drag-over"); + } + } + + // ===== TABLE EDITING FUNCTIONALITY ===== + + // Add row to table + addTableRow(event) { + const tableId = event.currentTarget.dataset.tableId; + const table = document.getElementById(tableId); + if (!table) return; + + this.saveUndoState(); + + const tbody = table.querySelector("tbody"); + const firstRow = tbody.querySelector("tr"); + if (!firstRow) return; + + const newRow = firstRow.cloneNode(true); + const cells = newRow.querySelectorAll("td, th"); + cells.forEach((cell, index) => { + cell.textContent = `Cell ${tbody.children.length + 1}-${index + 1}`; + }); + + tbody.appendChild(newRow); + this.showSuccess("Row added successfully!"); + } + // Add column to table + addTableColumn(event) { + const tableId = event.currentTarget.dataset.tableId; + const table = document.getElementById(tableId); + if (!table) return; + + this.saveUndoState(); + + const rows = table.querySelectorAll("tr"); + rows.forEach((row, rowIndex) => { + const newCell = document.createElement( + row.cells[0].tagName.toLowerCase() + ); + newCell.style.border = "1px solid #ddd"; + newCell.style.padding = "8px"; + newCell.contentEditable = "true"; + + if (row.cells[0].tagName === "TH") { + newCell.style.backgroundColor = "#f2f2f2"; + newCell.style.fontWeight = "bold"; + newCell.textContent = `Header ${row.cells.length + 1}`; + } else { + newCell.textContent = `Cell ${rowIndex}-${row.cells.length + 1}`; + } + + row.appendChild(newCell); + }); + + this.showSuccess("Column added successfully!"); + } + + // Delete row from table + deleteTableRow(event) { + const tableId = event.currentTarget.dataset.tableId; + const table = document.getElementById(tableId); + if (!table) return; + + const tbody = table.querySelector("tbody"); + if (tbody.children.length <= 1) { + this.showError("Cannot delete the last row!"); + return; + } + + this.saveUndoState(); + tbody.removeChild(tbody.lastChild); + this.showSuccess("Row deleted successfully!"); + } + + // Delete column from table + deleteTableColumn(event) { + const tableId = event.currentTarget.dataset.tableId; + const table = document.getElementById(tableId); + if (!table) return; + + const firstRow = table.querySelector("tr"); + if (!firstRow || firstRow.cells.length <= 1) { + this.showError("Cannot delete the last column!"); + return; + } + + this.saveUndoState(); + + const rows = table.querySelectorAll("tr"); + rows.forEach((row) => { + if (row.cells.length > 0) { + row.removeChild(row.lastChild); + } + }); + + this.showSuccess("Column deleted successfully!"); + } + // Delete entire table + deleteTable(event) { + const tableId = event.currentTarget.dataset.tableId; + const tableContainer = document.querySelector( + `[data-table-id="${tableId}"]` + ); + if (!tableContainer) return; + + this.saveUndoState(); + tableContainer.remove(); + this.showSuccess("Table deleted successfully!"); + } + // Handle table drag start (for moving tables) + handleTableContainerDragStart(event) { + if (event.target.classList.contains("table-control-btn")) { + event.preventDefault(); + return; + } + + const tableId = event.currentTarget.dataset.tableId; + + event.dataTransfer.setData("text/plain", "table-container"); + event.dataTransfer.setData("table-id", tableId); + event.dataTransfer.effectAllowed = "move"; + + // Add visual feedback + event.currentTarget.style.opacity = "0.5"; + event.currentTarget.style.transform = "rotate(2deg)"; + } + + // Handle table container drag end + handleTableContainerDragEnd(event) { + event.currentTarget.style.opacity = "1"; + event.currentTarget.style.transform = "rotate(0deg)"; + } + + // Setup event listeners for table controls + setupTableEventListeners(tableContainer) { + const tableId = tableContainer.dataset.tableId; + + // Add row button + const addRowBtn = tableContainer.querySelector( + '.table-control-btn[title="Add Row"]' + ); + if (addRowBtn) { + addRowBtn.addEventListener("click", (e) => { + e.stopPropagation(); + this.addTableRow(e); + }); + } + + // Add column button + const addColBtn = tableContainer.querySelector( + '.table-control-btn[title="Add Column"]' + ); + if (addColBtn) { + addColBtn.addEventListener("click", (e) => { + e.stopPropagation(); + this.addTableColumn(e); + }); + } + + // Delete row button + const deleteRowBtn = tableContainer.querySelector( + '.table-control-btn[title="Delete Row"]' + ); + if (deleteRowBtn) { + deleteRowBtn.addEventListener("click", (e) => { + e.stopPropagation(); + this.deleteTableRow(e); + }); + } + + // Delete column button + const deleteColBtn = tableContainer.querySelector( + '.table-control-btn[title="Delete Column"]' + ); + if (deleteColBtn) { + deleteColBtn.addEventListener("click", (e) => { + e.stopPropagation(); + this.deleteTableColumn(e); + }); + } + + // Delete table button + const deleteTableBtn = tableContainer.querySelector( + '.table-control-btn[title="Delete Table"]' + ); + if (deleteTableBtn) { + deleteTableBtn.addEventListener("click", (e) => { + e.stopPropagation(); + this.deleteTable(e); + }); + } + + // Drag and drop for table container + tableContainer.addEventListener("dragstart", (e) => { + this.handleTableContainerDragStart(e); + }); + + tableContainer.addEventListener("dragend", (e) => { + this.handleTableContainerDragEnd(e); + }); + + // Prevent drag on control buttons + const controlButtons = + tableContainer.querySelectorAll(".table-control-btn"); + controlButtons.forEach((btn) => { + btn.addEventListener("dragstart", (e) => { + e.preventDefault(); + }); + }); + } + // Improved text insertion that's draggable anywhere + insertDraggableText() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) { + this.showError("Editor not found"); + return; + } + + // Save undo state before making changes + this.saveUndoState(); + + // Get center position for insertion + const centerPos = this.getCenterPositionForElement('text'); + + // Create draggable text element + const textElement = document.createElement("div"); + textElement.className = "draggable-element draggable-text"; + textElement.contentEditable = true; + textElement.innerHTML = "Click to edit text"; + + // Position absolutely for free placement + textElement.style.position = "absolute"; + textElement.style.left = `${centerPos.x}px`; + textElement.style.top = `${centerPos.y}px`; + textElement.style.minWidth = "150px"; + textElement.style.minHeight = "30px"; + textElement.style.padding = "8px"; + textElement.style.border = "2px solid transparent"; + textElement.style.borderRadius = "4px"; + textElement.style.backgroundColor = "rgba(255, 255, 255, 0.9)"; + textElement.style.zIndex = "1000"; + textElement.style.cursor = "move"; + textElement.style.fontFamily = "Inter, sans-serif"; + textElement.style.fontSize = "14px"; + textElement.style.lineHeight = "1.4"; + + // Add delete button/cross to text + this.addDeleteButton(textElement); + + // Add to editor + editor.appendChild(textElement); + + // Make it draggable and resizable + this.addResizeHandles(textElement); + this.makeDraggable(textElement); + + // Select the text for immediate editing + setTimeout(() => { + textElement.focus(); + const selection = window.getSelection(); + const range = document.createRange(); + range.selectNodeContents(textElement); + selection.removeAllRanges(); + selection.addRange(range); + }, 100); + } + + // Undo/Redo functionality + saveUndoState() { + try { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) { + console.warn("No editor found for saveUndoState"); + return; + } + + const currentState = { + content: editor.innerHTML, + timestamp: Date.now(), + }; + + this.undoStack.push(currentState); + + // Limit undo stack size + if (this.undoStack.length > this.maxUndoSteps) { + this.undoStack.shift(); + } + + // Clear redo stack when new action is performed + this.redoStack = []; + } catch (error) { + console.error("Error in saveUndoState:", error); + } + } + undo() { + if (this.undoStack.length === 0) return; + + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) return; + + // Save current state to redo stack + const currentState = { + content: editor.innerHTML, + timestamp: Date.now(), + }; + this.redoStack.push(currentState); + + // Restore previous state + const previousState = this.undoStack.pop(); + editor.innerHTML = previousState.content; + + // Re-setup event handlers for any dynamic elements + this.setupEditorEventHandlers(); + } + redo() { + if (this.redoStack.length === 0) return; + + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) return; + + // Save current state to undo stack + const currentState = { + content: editor.innerHTML, + timestamp: Date.now(), + }; + this.undoStack.push(currentState); + + // Restore next state + const nextState = this.redoStack.pop(); + editor.innerHTML = nextState.content; + + // Re-setup event handlers for any dynamic elements + this.setupEditorEventHandlers(); + } + + // Setup editor event handlers after undo/redo + setupEditorEventHandlers() { + this.setupEditorClickHandler(); + this.ensureEditorEditable(); + } + + // Find the first available category that has images + findFirstAvailableCategory() { + if (!this.realPropertyImages || this.realPropertyImages.length === 0) { + return "None"; + } + + // Define the order of categories to check + const categoryOrder = [ + "Interior", + "Exterior", + "Kitchen", + "Bedroom", + "Living Area", + "Parking", + "Anchor", + "Maps", + "None", + ]; + + // Check each category in order + for (let category of categoryOrder) { + const hasImages = this.realPropertyImages.some((img) => { + const imgCategory = img.category || img.pcrm__Category__c; + + if (category === "None") { + return ( + !imgCategory || + imgCategory === "" || + imgCategory === null || + imgCategory === undefined || + imgCategory === "None" + ); } - // Save undo state + return imgCategory === category; + }); + + if (hasImages) { + return category; + } + } + + // Fallback to None if no specific category has images + return "None"; + } + + // Ensure smart category selection only on initial load + ensureSmartCategorySelection() { + // Only run if initial category selection hasn't been done yet + if ( + !this.initialCategorySelected && + this.realPropertyImages && + this.realPropertyImages.length > 0 + ) { + const firstAvailableCategory = this.findFirstAvailableCategory(); + this.selectedCategory = firstAvailableCategory; + this.filterImagesByCategory(firstAvailableCategory); + this.initialCategorySelected = true; + + // Update button states + const categoryButtons = this.template.querySelectorAll( + ".category-btn-step2" + ); + categoryButtons.forEach((btn) => { + btn.classList.remove("active"); + if (btn.dataset.category === firstAvailableCategory) { + btn.classList.add("active"); + } + }); + } + } + + // Enhanced keyboard event handler + handleEditorKeydown(event) { + // Check for Ctrl+Z (Undo) + if ( + (event.ctrlKey || event.metaKey) && + event.key === "z" && + !event.shiftKey + ) { + event.preventDefault(); + this.undo(); + return; + } + + // Check for Ctrl+Y or Ctrl+Shift+Z (Redo) + if ( + (event.ctrlKey || event.metaKey) && + (event.key === "y" || (event.key === "z" && event.shiftKey)) + ) { + event.preventDefault(); + this.redo(); + return; + } + + // Save state before modifications (with debouncing) + if (!this.pendingUndoSave) { + this.pendingUndoSave = true; + setTimeout(() => { this.saveUndoState(); + this.pendingUndoSave = false; + }, 500); + } + } + // Enhanced image manipulation methods + addResizeHandles(container) { + const handles = ["nw", "ne", "sw", "se", "n", "s", "w", "e"]; - // Create table element using our new method (draggable/resizeable container like images) - const tableContainer = this.createTableElement(); - editor.appendChild(tableContainer); - // Default placement similar to images - tableContainer.style.left = '50px'; - tableContainer.style.top = '50px'; - // Enable drag + resize - this.addTableResizeHandles(tableContainer); - this.makeDraggable(tableContainer); - this.setupTableEventListeners(tableContainer); - - this.closeTableDialog(); - this.showSuccess('Table inserted successfully!'); + handles.forEach((handle) => { + const resizeHandle = document.createElement("div"); + resizeHandle.className = `resize-handle ${handle}`; + resizeHandle.style.position = "absolute"; + resizeHandle.style.background = "#4f46e5"; + resizeHandle.style.border = "2px solid white"; + resizeHandle.style.borderRadius = "50%"; + resizeHandle.style.width = "12px"; + resizeHandle.style.height = "12px"; + resizeHandle.style.zIndex = "1001"; + + // Position handles + switch (handle) { + case "nw": + resizeHandle.style.top = "-6px"; + resizeHandle.style.left = "-6px"; + resizeHandle.style.cursor = "nw-resize"; + break; + case "ne": + resizeHandle.style.top = "-6px"; + resizeHandle.style.right = "-6px"; + resizeHandle.style.cursor = "ne-resize"; + break; + case "sw": + resizeHandle.style.bottom = "-6px"; + resizeHandle.style.left = "-6px"; + resizeHandle.style.cursor = "sw-resize"; + break; + case "se": + resizeHandle.style.bottom = "-6px"; + resizeHandle.style.right = "-6px"; + resizeHandle.style.cursor = "se-resize"; + break; + case "n": + resizeHandle.style.top = "-6px"; + resizeHandle.style.left = "50%"; + resizeHandle.style.transform = "translateX(-50%)"; + resizeHandle.style.cursor = "n-resize"; + break; + case "s": + resizeHandle.style.bottom = "-6px"; + resizeHandle.style.left = "50%"; + resizeHandle.style.transform = "translateX(-50%)"; + resizeHandle.style.cursor = "s-resize"; + break; + case "w": + resizeHandle.style.top = "50%"; + resizeHandle.style.left = "-6px"; + resizeHandle.style.transform = "translateY(-50%)"; + resizeHandle.style.cursor = "w-resize"; + break; + case "e": + resizeHandle.style.top = "50%"; + resizeHandle.style.right = "-6px"; + resizeHandle.style.transform = "translateY(-50%)"; + resizeHandle.style.cursor = "e-resize"; + break; + } + + // Add resize functionality + this.addResizeFunctionality(resizeHandle, container, handle); + + container.appendChild(resizeHandle); + }); + } + // Add delete handle to image + addDeleteHandle(container) { + const deleteHandle = document.createElement("button"); + deleteHandle.className = "delete-handle"; + deleteHandle.innerHTML = "×"; + deleteHandle.style.position = "absolute"; + deleteHandle.style.top = "-8px"; + deleteHandle.style.right = "-8px"; + deleteHandle.style.background = "#ef4444"; + deleteHandle.style.color = "white"; + deleteHandle.style.border = "none"; + deleteHandle.style.borderRadius = "50%"; + deleteHandle.style.width = "20px"; + deleteHandle.style.height = "20px"; + deleteHandle.style.fontSize = "12px"; + deleteHandle.style.cursor = "pointer"; + deleteHandle.style.zIndex = "1002"; + deleteHandle.style.display = "flex"; + deleteHandle.style.alignItems = "center"; + deleteHandle.style.justifyContent = "center"; + deleteHandle.style.boxShadow = "0 2px 4px rgba(0, 0, 0, 0.2)"; + + deleteHandle.addEventListener("click", (e) => { + e.stopPropagation(); + this.saveUndoState(); + container.remove(); + }); + + deleteHandle.addEventListener("mouseenter", () => { + deleteHandle.style.background = "#dc2626"; + deleteHandle.style.transform = "scale(1.1)"; + }); + + deleteHandle.addEventListener("mouseleave", () => { + deleteHandle.style.background = "#ef4444"; + deleteHandle.style.transform = "scale(1)"; + }); + + container.appendChild(deleteHandle); + } + + // Add resize functionality to handle + addResizeFunctionality(handle, container, direction) { + let isResizing = false; + let startX, startY, startWidth, startHeight, startLeft, startTop; + + handle.addEventListener("mousedown", (e) => { + e.stopPropagation(); + isResizing = true; + + startX = e.clientX; + startY = e.clientY; + startWidth = parseInt(window.getComputedStyle(container).width, 10); + startHeight = parseInt(window.getComputedStyle(container).height, 10); + startLeft = parseInt(window.getComputedStyle(container).left, 10); + startTop = parseInt(window.getComputedStyle(container).top, 10); + + document.addEventListener("mousemove", handleResize); + document.addEventListener("mouseup", stopResize); + }); + + const handleResize = (e) => { + if (!isResizing) return; + + const deltaX = e.clientX - startX; + const deltaY = e.clientY - startY; + + let newWidth = startWidth; + let newHeight = startHeight; + let newLeft = startLeft; + let newTop = startTop; + + switch (direction) { + case "se": + newWidth = Math.max(50, startWidth + deltaX); + newHeight = Math.max(50, startHeight + deltaY); + break; + case "sw": + newWidth = Math.max(50, startWidth - deltaX); + newHeight = Math.max(50, startHeight + deltaY); + newLeft = startLeft + (startWidth - newWidth); + break; + case "ne": + newWidth = Math.max(50, startWidth + deltaX); + newHeight = Math.max(50, startHeight - deltaY); + newTop = startTop + (startHeight - newHeight); + break; + case "nw": + newWidth = Math.max(50, startWidth - deltaX); + newHeight = Math.max(50, startHeight - deltaY); + newLeft = startLeft + (startWidth - newWidth); + newTop = startTop + (startHeight - newHeight); + break; + case "e": + newWidth = Math.max(50, startWidth + deltaX); + break; + case "w": + newWidth = Math.max(50, startWidth - deltaX); + newLeft = startLeft + (startWidth - newWidth); + break; + case "s": + newHeight = Math.max(50, startHeight + deltaY); + break; + case "n": + newHeight = Math.max(50, startHeight - deltaY); + newTop = startTop + (startHeight - newHeight); + break; + } + + container.style.width = newWidth + "px"; + container.style.height = newHeight + "px"; + container.style.left = newLeft + "px"; + container.style.top = newTop + "px"; + }; + + const stopResize = () => { + isResizing = false; + document.removeEventListener("mousemove", handleResize); + document.removeEventListener("mouseup", stopResize); + }; + } + // Make element draggable (enhanced version) + makeDraggable(element) { + let isDragging = false; + let startX, startY, startLeft, startTop; + + element.addEventListener("mousedown", (e) => { + // Don't start drag if clicking on resize handles or delete button + if ( + e.target.classList.contains("resize-handle") || + e.target.classList.contains("delete-handle") + ) { + return; + } + + isDragging = true; + startX = e.clientX; + startY = e.clientY; + startLeft = parseInt(window.getComputedStyle(element).left, 10); + startTop = parseInt(window.getComputedStyle(element).top, 10); + + element.style.cursor = "grabbing"; + document.addEventListener("mousemove", handleDrag); + document.addEventListener("mouseup", stopDrag); + }); + + const handleDrag = (e) => { + if (!isDragging) return; + + const deltaX = e.clientX - startX; + const deltaY = e.clientY - startY; + + // Calculate new position with pixel precision + const newLeft = Math.round(startLeft + deltaX); + const newTop = Math.round(startTop + deltaY); + + // Ensure absolute positioning is maintained + element.style.position = "absolute"; + element.style.left = newLeft + "px"; + element.style.top = newTop + "px"; + + // Ensure box-sizing is correct for precise positioning + element.style.boxSizing = "border-box"; + + // Ensure z-index is maintained + if (!element.style.zIndex) { + element.style.zIndex = "1000"; + } + + // Log position for debugging + console.log("Dragging to position:", { + left: newLeft + "px", + top: newTop + "px", + deltaX: deltaX, + deltaY: deltaY + }); + }; + + const stopDrag = () => { + isDragging = false; + element.style.cursor = "move"; + + // Lock in the final position with precise pixel values + const finalLeft = Math.round(parseFloat(element.style.left) || 0); + const finalTop = Math.round(parseFloat(element.style.top) || 0); + + // Ensure all positioning is locked precisely + element.style.position = "absolute"; + element.style.left = finalLeft + "px"; + element.style.top = finalTop + "px"; + element.style.boxSizing = "border-box"; + element.style.zIndex = element.style.zIndex || "1000"; + + // Add a data attribute to track the position for debugging + element.setAttribute('data-final-left', finalLeft); + element.setAttribute('data-final-top', finalTop); + + console.log("Final position locked:", { + left: finalLeft + "px", + top: finalTop + "px", + zIndex: element.style.zIndex, + element: element.className + }); + + document.removeEventListener("mousemove", handleDrag); + document.removeEventListener("mouseup", stopDrag); + }; + } + // Select draggable element + selectDraggableElement(element) { + // Remove selection from all draggable elements + const editor = this.template.querySelector(".enhanced-editor-content"); + if (editor) { + const allDraggable = editor.querySelectorAll( + ".draggable-element, .draggable-image-container, .draggable-table-container" + ); + allDraggable.forEach((el) => { + if (el !== element) { + el.classList.remove("selected"); + // Remove any resize handles + const resizeHandles = el.querySelectorAll(".resize-handle"); + resizeHandles.forEach((handle) => handle.remove()); + // Remove any delete buttons + const deleteButtons = el.querySelectorAll( + ".delete-handle, .delete-image-btn" + ); + deleteButtons.forEach((btn) => btn.remove()); + } + }); } - // ===== DYNAMIC IMAGE REPLACEMENT UTILITIES ===== - - // Get first image from a specific category - getFirstImageByCategory(category) { - - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - return null; - } - - const categoryImages = this.realPropertyImages.filter(img => { - const imgCategory = img.category || img.pcrm__Category__c; - return imgCategory && imgCategory.toLowerCase() === category.toLowerCase(); - }); - - - return categoryImages.length > 0 ? categoryImages[0] : null; + // Add selection to clicked element + element.classList.add("selected"); + + // Add resize handles and controls to the selected element + if (element.classList.contains("draggable-image-container")) { + const img = element.querySelector("img"); + if (img) { + this.addResizeHandles(img); + this.addDeleteButton(element); + } + } else if (element.classList.contains("draggable-table-container")) { + this.addTableResizeHandles(element); + this.addDeleteButton(element); + } else if (element.tagName && element.tagName.toLowerCase() === "img") { + // If already wrapped, ensure handles on container + if ( + element.parentElement && + element.parentElement.classList && + element.parentElement.classList.contains("draggable-image-container") + ) { + const container = element.parentElement; + this.addResizeHandles(container); + this.makeDraggable(container); + this.addDeleteButton(container); + this.highlightSelectedElement(container); + // Do NOT change position here; keep intact unless dragged + return; + } + // Wrap plain image and add handles; preserve on-screen size and position + const editor = this.template.querySelector(".enhanced-editor-content"); + const rect = element.getBoundingClientRect(); + const editorRect = editor + ? editor.getBoundingClientRect() + : { left: 0, top: 0 }; + const scale = this.zoom || 1; + const currentWidth = rect.width / scale; + const currentHeight = rect.height / scale; + + // Insert a placeholder to avoid layout shift in the original flow + const placeholder = document.createElement("div"); + placeholder.style.width = currentWidth + "px"; + placeholder.style.height = currentHeight + "px"; + placeholder.style.display = + window.getComputedStyle(element).display || "inline-block"; + + const container = document.createElement("div"); + container.className = "draggable-image-container"; + container.style.position = "absolute"; // anchored to editor (set to relative elsewhere) + // Account for preview zoom scale to avoid displacement + container.style.left = + (rect.left - editorRect.left + (editor ? editor.scrollLeft : 0)) / + scale + + "px"; + container.style.top = + (rect.top - editorRect.top + (editor ? editor.scrollTop : 0)) / scale + + "px"; + container.style.zIndex = + window.getComputedStyle(element).zIndex || "auto"; + container.style.display = "inline-block"; + + // Move the image into container and preserve size + // Set container and image sizing + container.style.width = currentWidth + "px"; + container.style.height = currentHeight + "px"; + container.style.boxSizing = "border-box"; + element.style.width = "100%"; + element.style.height = "100%"; + element.style.maxWidth = "none"; + element.style.maxHeight = "none"; + element.style.margin = "0"; + element.style.display = "block"; + element.style.boxSizing = "border-box"; + element.style.objectFit = + window.getComputedStyle(element).objectFit || "cover"; + + // Replace the image in the flow with placeholder, then move image to absolute container + const originalParent = element.parentNode; + originalParent.insertBefore(placeholder, element); + if (editor) { + editor.appendChild(container); + } else { + originalParent.insertBefore(container, placeholder); + } + container.appendChild(element); + container.classList.add("no-frame"); + this.addResizeHandles(container); + this.makeDraggable(container); + this.addDeleteButton(container); + this.highlightSelectedElement(container); } - - // Direct method to get exterior image URL - getExteriorImageUrl() { - - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - return 'https://images.unsplash.com/photo-1568605114967-8130f3a36994?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'; - } - - // Look for exterior images first - const exteriorImages = this.realPropertyImages.filter(img => { - const category = img.category || img.pcrm__Category__c; - return category && category.toLowerCase().includes('exterior'); - }); - - if (exteriorImages.length > 0) { - return exteriorImages[0].url; - } - - // If no exterior, use first available image - if (this.realPropertyImages.length > 0) { - return this.realPropertyImages[0].url; - } - - return 'https://images.unsplash.com/photo-1568605114967-8130f3a36994?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'; - } - - // Direct method to get maps image URL - getMapsImageUrl() { - - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - return 'https://images.unsplash.com/photo-1524661135-423995f22d0b?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'; - } - - // Look for maps images first - check both exact match and contains - const mapsImages = this.realPropertyImages.filter(img => { - const category = img.category || img.pcrm__Category__c; - return category && (category.toLowerCase() === 'maps' || category.toLowerCase().includes('maps')); - }); - - - if (mapsImages.length > 0) { - return mapsImages[0].url; - } - - // Look for anchor images as fallback - const anchorImages = this.realPropertyImages.filter(img => { - const category = img.category || img.pcrm__Category__c; - return category && (category.toLowerCase() === 'anchor' || category.toLowerCase().includes('anchor')); - }); - - - if (anchorImages.length > 0) { - return anchorImages[0].url; - } - - return 'https://images.unsplash.com/photo-1524661135-423995f22d0b?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'; - } - - // Method to replace background-image URLs in CSS at runtime - replaceBackgroundImagesInHTML(htmlContent) { - - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - return htmlContent; - } - - const exteriorImageUrl = this.getExteriorImageUrl(); - - // Replace any hardcoded background-image URLs with the property's exterior image - let updatedHTML = htmlContent; - - // Pattern to match background-image: url('...') or background-image: url("...") - const backgroundImagePattern = /background-image\s*:\s*url\(['"][^'"]*['"]\)/gi; - - // Replace all background-image URLs with the property's exterior image - updatedHTML = updatedHTML.replace(backgroundImagePattern, `background-image: url('${exteriorImageUrl}')`); - - return updatedHTML; - } - - // Method to dynamically update CSS background-image rules after template loads - updateCSSBackgroundImages() { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (!editor) return; - if (!this.realPropertyImages || this.realPropertyImages.length === 0) return; - const exteriorImageUrl = this.getExteriorImageUrl(); - // Scope to styles inside the editor only - const styleElements = editor.querySelectorAll('style'); - styleElements.forEach(styleElement => { - const cssText = styleElement.textContent || ''; - const backgroundImagePattern = /background-image\s*:\s*url\(['"][^'"]*['"]\)/gi; - const updatedCSS = cssText.replace(backgroundImagePattern, `background-image: url('${exteriorImageUrl}')`); - if (updatedCSS !== cssText) styleElement.textContent = updatedCSS; - }); - // Update inline background-image styles only within editor - const elementsWithBackground = editor.querySelectorAll('[style*="background-image"]'); - elementsWithBackground.forEach(element => { - const currentStyle = element.getAttribute('style') || ''; - const backgroundImagePattern = /background-image\s*:\s*url\(['"][^'"]*['"]\)/gi; - const updatedStyle = currentStyle.replace(backgroundImagePattern, `background-image: url('${exteriorImageUrl}')`); - if (updatedStyle !== currentStyle) element.setAttribute('style', updatedStyle); - }); - } - - // Get all images from a specific category - getAllImagesByCategory(category) { - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - return []; - } - - return this.realPropertyImages.filter(img => - img.category && img.category.toLowerCase() === category.toLowerCase() - ); - } - - // Get uncategorized images (no category or category is null/empty) - getUncategorizedImages() { - - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - return []; - } - - const uncategorized = this.realPropertyImages.filter(img => - !img.category || img.category.trim() === '' || img.category.toLowerCase() === 'none' - ); - - - return uncategorized; - } - - // Smart image replacement - tries multiple categories in order of preference - getSmartImageForSection(sectionType, fallbackUrl) { - - const categoryPriority = { - 'exterior': ['Exterior', 'Anchor', 'None'], - 'interior': ['Interior', 'Living Area', 'Kitchen', 'Bedroom', 'None'], - 'kitchen': ['Kitchen', 'Interior', 'Living Area', 'None'], - 'bedroom': ['Bedroom', 'Interior', 'None'], - 'living': ['Living Area', 'Interior', 'Kitchen', 'None'], - 'bathroom': ['Bathroom', 'Interior', 'None'], - 'parking': ['Parking', 'Exterior', 'None'], - 'maps': ['Maps', 'Anchor', 'Exterior', 'None'], - 'gallery': ['Interior', 'Exterior', 'Kitchen', 'Bedroom', 'Living Area', 'None'] - }; - - const categories = categoryPriority[sectionType] || ['Interior', 'Exterior', 'None']; - - for (const category of categories) { - const image = this.getFirstImageByCategory(category); - if (image && image.url) { - return image.url; - } - } - - return fallbackUrl; - } - - // Generate Property Gallery HTML for uncategorized images - generatePropertyGalleryHTML() { - - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - return '
    No images available
    '; - } - - - let galleryHTML = ''; - this.realPropertyImages.forEach((image, index) => { - const title = image.title || image.pcrm__Title__c || `Property Image ${index + 1}`; - galleryHTML += `${title}`; - }); - - return galleryHTML; + } + + // Ensure all draggable elements maintain their exact positions + preserveElementPositions() { + const editor = this.template.querySelector(".enhanced-editor-content"); + if (!editor) return; + + const draggableElements = editor.querySelectorAll( + ".draggable-element, .draggable-image-container, .draggable-table-container" + ); + + draggableElements.forEach((element) => { + // Ensure absolute positioning + element.style.position = "absolute"; + element.style.boxSizing = "border-box"; + + // Preserve existing position values + if (element.style.left) { + const leftValue = parseInt(element.style.left, 10); + element.style.left = leftValue + "px"; + } + if (element.style.top) { + const topValue = parseInt(element.style.top, 10); + element.style.top = topValue + "px"; + } + + // Ensure z-index is maintained + if (!element.style.zIndex) { + element.style.zIndex = "1000"; + } + + console.log("Preserved position for element:", { + left: element.style.left, + top: element.style.top, + position: element.style.position + }); + }); + } + + // Add delete button to element + addDeleteButton(element) { + // Remove existing delete button if any + const existingDelete = element.querySelector( + ".delete-handle, .delete-image-btn" + ); + if (existingDelete) { + existingDelete.remove(); } - - // Generate gallery HTML for a provided subset of images - generatePropertyGalleryHTMLForImages(imagesSubset) { - if (!imagesSubset || imagesSubset.length === 0) { - return '
    No images available
    '; - } - let galleryHTML = ''; - imagesSubset.forEach((image, index) => { - const title = image.title || image.pcrm__Title__c || `Property Image ${index + 1}`; - galleryHTML += `${title}`; - }); - return galleryHTML; - } - - - // ===== TABLE DRAG AND DROP FUNCTIONALITY ===== - - // Handle table drag start - handleTableDragStart(event) { - this.isDraggingTable = true; - - // Store table configuration data - this.draggedTableData = { - rows: this.tableRows, - cols: this.tableCols, - includeHeader: this.includeHeader - }; - - // Set drag data - event.dataTransfer.setData('text/plain', 'table'); - event.dataTransfer.effectAllowed = 'copy'; - - // Add visual feedback - event.currentTarget.classList.add('dragging'); - - // Add drag over class to editor - setTimeout(() => { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor) { - editor.classList.add('drag-over'); - } - }, 100); - } - - // Handle editor drag over - handleEditorDragOver(event) { - // Allow dropping tables and images - event.preventDefault(); - event.dataTransfer.dropEffect = 'copy'; - } - - // Handle editor drop - handleEditorDrop(event) { - event.preventDefault(); - - const editor = this.template.querySelector('.enhanced-editor-content'); - if (!editor) { this.showError("Editor not found"); return; } - - const dataType = event.dataTransfer.getData('text/plain'); - if (dataType === 'image' && this.currentImage) { - // Insert draggable image at drop - const img = document.createElement('img'); - img.src = this.currentImage.url; - img.style.maxWidth = '300px'; - img.style.height = 'auto'; - img.className = 'draggable-image'; - - const container = document.createElement('div'); - container.className = 'draggable-image-container'; - container.style.position = 'absolute'; - container.style.left = (event.clientX - editor.getBoundingClientRect().left) + 'px'; - container.style.top = (event.clientY - editor.getBoundingClientRect().top) + 'px'; - container.appendChild(img); - editor.appendChild(container); - this.makeImagesDraggableAndResizable([img]); - this.showSuccess('Image inserted via drag and drop!'); - return; - } - - if (!this.isDraggingTable || !this.draggedTableData) { - return; - } - - - // Remove visual feedback - this.removeTableDragFeedback(); - - // Get drop position - // editor already resolved above - - // Save undo state before making changes - this.saveUndoState(); - - // Insert table at drop position - this.insertTableAtPosition(editor, this.draggedTableData, event); - - // Reset drag state - this.isDraggingTable = false; - this.draggedTableData = null; - - this.showSuccess('Table inserted via drag and drop!'); - } - - - - // Insert table at specific position - insertTableAtPosition(editor, tableData, event) { - // Get cursor position relative to editor - const rect = editor.getBoundingClientRect(); - const x = event.clientX - rect.left; - const y = event.clientY - rect.top; - - // Create table element directly using DOM methods (same as insertTable) - const tableId = `table-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; - - // Create container div (use draggable-table-container to get drag/resize behavior) - const container = document.createElement('div'); - container.className = 'draggable-table-container'; - container.setAttribute('data-table-id', tableId); - container.style.cssText = 'position: absolute; left: 0; top: 0; width: 400px; min-width: 200px; min-height: 150px; z-index: 1000; border: 2px dashed #667eea; border-radius: 8px; background: white; box-shadow: 0 4px 12px rgba(0,0,0,0.1); overflow: hidden;'; - - // Create table controls - const controls = document.createElement('div'); - controls.className = 'table-controls'; - controls.style.cssText = 'position: absolute; top: -40px; left: 0; background: white; padding: 5px; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); opacity: 0; transition: opacity 0.2s;'; - - // Add control buttons (same as insertTable) - const controlGroup1 = document.createElement('div'); - controlGroup1.className = 'table-control-group'; - controlGroup1.style.cssText = 'display: flex; gap: 5px;'; - - const addRowBtn = document.createElement('button'); - addRowBtn.className = 'table-control-btn'; - addRowBtn.setAttribute('data-table-id', tableId); - addRowBtn.textContent = '+ Row'; - addRowBtn.style.cssText = 'padding: 4px 8px; border: 1px solid #ddd; background: white; cursor: pointer;'; - - const addColBtn = document.createElement('button'); - addColBtn.className = 'table-control-btn'; - addColBtn.setAttribute('data-table-id', tableId); - addColBtn.textContent = '+ Col'; - addColBtn.style.cssText = 'padding: 4px 8px; border: 1px solid #ddd; background: white; cursor: pointer;'; - - const delRowBtn = document.createElement('button'); - delRowBtn.className = 'table-control-btn'; - delRowBtn.setAttribute('data-table-id', tableId); - delRowBtn.textContent = '- Row'; - delRowBtn.style.cssText = 'padding: 4px 8px; border: 1px solid #ddd; background: white; cursor: pointer;'; - - const delColBtn = document.createElement('button'); - delColBtn.className = 'table-control-btn'; - delColBtn.setAttribute('data-table-id', tableId); - delColBtn.textContent = '- Col'; - delColBtn.style.cssText = 'padding: 4px 8px; border: 1px solid #ddd; background: white; cursor: pointer;'; - - const deleteBtn = document.createElement('button'); - deleteBtn.className = 'table-control-btn delete'; - deleteBtn.setAttribute('data-table-id', tableId); - deleteBtn.textContent = '🗑️'; - deleteBtn.style.cssText = 'padding: 4px 8px; border: 1px solid #ff4444; background: #ff4444; color: white; cursor: pointer;'; - - controlGroup1.appendChild(addRowBtn); - controlGroup1.appendChild(addColBtn); - controlGroup1.appendChild(delRowBtn); - controlGroup1.appendChild(delColBtn); - - const controlGroup2 = document.createElement('div'); - controlGroup2.className = 'table-control-group'; - controlGroup2.style.cssText = 'display: flex; gap: 5px; margin-left: 10px;'; - controlGroup2.appendChild(deleteBtn); - - controls.appendChild(controlGroup1); - controls.appendChild(controlGroup2); - - // Create table - const table = document.createElement('table'); - table.className = 'inserted-table'; - table.id = tableId; - table.style.cssText = 'border-collapse: collapse; width: 100%; margin: 1rem 0; border: 2px solid #333; background-color: white;'; - - // Create table body - const tbody = document.createElement('tbody'); - - // Add header row if requested - if (tableData.includeHeader) { - const thead = document.createElement('thead'); - const headerRow = document.createElement('tr'); - - for (let col = 0; col < tableData.cols; col++) { - const th = document.createElement('th'); - th.style.cssText = 'border: 1px solid #333; padding: 12px; background-color: #4f46e5; color: white; font-weight: bold; text-align: center;'; - th.setAttribute('contenteditable', 'true'); - th.textContent = `Header ${col + 1}`; - headerRow.appendChild(th); - } - - thead.appendChild(headerRow); - table.appendChild(thead); - } - - // Add body rows - for (let row = 0; row < tableData.rows; row++) { - const tr = document.createElement('tr'); - - for (let col = 0; col < tableData.cols; col++) { - const td = document.createElement('td'); - td.style.cssText = 'border: 1px solid #333; padding: 12px; background-color: #f8f9fa; min-width: 100px; min-height: 40px;'; - td.setAttribute('contenteditable', 'true'); - td.textContent = `Cell ${row + 1}-${col + 1}`; - tr.appendChild(td); - } - - tbody.appendChild(tr); - } - - table.appendChild(tbody); - - // Assemble the container - container.appendChild(controls); - container.appendChild(table); - - const tableElement = container; - - // Try to find the best insertion point - const range = document.createRange(); - const walker = document.createTreeWalker( - editor, - NodeFilter.SHOW_TEXT, - null, - false - ); - - let bestNode = null; - let bestDistance = Infinity; - let node; - - // Find the closest text node to the drop position - while (node = walker.nextNode()) { - const nodeRect = node.getBoundingClientRect(); - const nodeX = nodeRect.left - rect.left; - const nodeY = nodeRect.top - rect.top; - const distance = Math.sqrt((x - nodeX) ** 2 + (y - nodeY) ** 2); - - if (distance < bestDistance) { - bestDistance = distance; - bestNode = node; - } - } - - // Insert into editor (append at end for simplicity) - editor.appendChild(tableElement); - // Position at drop point - tableElement.style.left = Math.max(0, Math.min(x, editor.clientWidth - tableElement.offsetWidth)) + 'px'; - tableElement.style.top = Math.max(0, Math.min(y, editor.scrollHeight - tableElement.offsetHeight)) + 'px'; - - // Add drag/resize to the new table - this.addTableResizeHandles(tableElement); - this.makeDraggable(tableElement); - this.setupTableEventListeners(tableElement); - } - - // Remove table drag feedback - removeTableDragFeedback() { - // Remove dragging class from button - const tableBtn = this.template.querySelector('.draggable-table-btn'); - if (tableBtn) { - tableBtn.classList.remove('dragging'); - } - - // Remove drag-over class from editor - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor) { - editor.classList.remove('drag-over'); - } - } - - // ===== TABLE EDITING FUNCTIONALITY ===== - - // Add row to table - addTableRow(event) { - const tableId = event.currentTarget.dataset.tableId; - const table = document.getElementById(tableId); - if (!table) return; - - this.saveUndoState(); - - const tbody = table.querySelector('tbody'); - const firstRow = tbody.querySelector('tr'); - if (!firstRow) return; - - const newRow = firstRow.cloneNode(true); - const cells = newRow.querySelectorAll('td, th'); - cells.forEach((cell, index) => { - cell.textContent = `Cell ${tbody.children.length + 1}-${index + 1}`; - }); - - tbody.appendChild(newRow); - this.showSuccess('Row added successfully!'); - } - - // Add column to table - addTableColumn(event) { - const tableId = event.currentTarget.dataset.tableId; - const table = document.getElementById(tableId); - if (!table) return; - - this.saveUndoState(); - - const rows = table.querySelectorAll('tr'); - rows.forEach((row, rowIndex) => { - const newCell = document.createElement(row.cells[0].tagName.toLowerCase()); - newCell.style.border = '1px solid #ddd'; - newCell.style.padding = '8px'; - newCell.contentEditable = 'true'; - - if (row.cells[0].tagName === 'TH') { - newCell.style.backgroundColor = '#f2f2f2'; - newCell.style.fontWeight = 'bold'; - newCell.textContent = `Header ${row.cells.length + 1}`; - } else { - newCell.textContent = `Cell ${rowIndex}-${row.cells.length + 1}`; - } - - row.appendChild(newCell); - }); - - this.showSuccess('Column added successfully!'); - } - - // Delete row from table - deleteTableRow(event) { - const tableId = event.currentTarget.dataset.tableId; - const table = document.getElementById(tableId); - if (!table) return; - - const tbody = table.querySelector('tbody'); - if (tbody.children.length <= 1) { - this.showError('Cannot delete the last row!'); - return; - } - - this.saveUndoState(); - tbody.removeChild(tbody.lastChild); - this.showSuccess('Row deleted successfully!'); - } - - // Delete column from table - deleteTableColumn(event) { - const tableId = event.currentTarget.dataset.tableId; - const table = document.getElementById(tableId); - if (!table) return; - - const firstRow = table.querySelector('tr'); - if (!firstRow || firstRow.cells.length <= 1) { - this.showError('Cannot delete the last column!'); - return; - } - - this.saveUndoState(); - - const rows = table.querySelectorAll('tr'); - rows.forEach(row => { - if (row.cells.length > 0) { - row.removeChild(row.lastChild); - } - }); - - this.showSuccess('Column deleted successfully!'); - } - - // Delete entire table - deleteTable(event) { - const tableId = event.currentTarget.dataset.tableId; - const tableContainer = document.querySelector(`[data-table-id="${tableId}"]`); - if (!tableContainer) return; - - this.saveUndoState(); - tableContainer.remove(); - this.showSuccess('Table deleted successfully!'); - } - - // Handle table drag start (for moving tables) - handleTableContainerDragStart(event) { - if (event.target.classList.contains('table-control-btn')) { - event.preventDefault(); - return; - } - - const tableId = event.currentTarget.dataset.tableId; - - event.dataTransfer.setData('text/plain', 'table-container'); - event.dataTransfer.setData('table-id', tableId); - event.dataTransfer.effectAllowed = 'move'; - - // Add visual feedback - event.currentTarget.style.opacity = '0.5'; - event.currentTarget.style.transform = 'rotate(2deg)'; - } - - // Handle table container drag end - handleTableContainerDragEnd(event) { - event.currentTarget.style.opacity = '1'; - event.currentTarget.style.transform = 'rotate(0deg)'; - } - - // Setup event listeners for table controls - setupTableEventListeners(tableContainer) { - const tableId = tableContainer.dataset.tableId; - - // Add row button - const addRowBtn = tableContainer.querySelector('.table-control-btn[title="Add Row"]'); - if (addRowBtn) { - addRowBtn.addEventListener('click', (e) => { - e.stopPropagation(); - this.addTableRow(e); - }); - } - - // Add column button - const addColBtn = tableContainer.querySelector('.table-control-btn[title="Add Column"]'); - if (addColBtn) { - addColBtn.addEventListener('click', (e) => { - e.stopPropagation(); - this.addTableColumn(e); - }); - } - - // Delete row button - const deleteRowBtn = tableContainer.querySelector('.table-control-btn[title="Delete Row"]'); - if (deleteRowBtn) { - deleteRowBtn.addEventListener('click', (e) => { - e.stopPropagation(); - this.deleteTableRow(e); - }); - } - - // Delete column button - const deleteColBtn = tableContainer.querySelector('.table-control-btn[title="Delete Column"]'); - if (deleteColBtn) { - deleteColBtn.addEventListener('click', (e) => { - e.stopPropagation(); - this.deleteTableColumn(e); - }); - } - - // Delete table button - const deleteTableBtn = tableContainer.querySelector('.table-control-btn[title="Delete Table"]'); - if (deleteTableBtn) { - deleteTableBtn.addEventListener('click', (e) => { - e.stopPropagation(); - this.deleteTable(e); - }); - } - - // Drag and drop for table container - tableContainer.addEventListener('dragstart', (e) => { - this.handleTableContainerDragStart(e); - }); - - tableContainer.addEventListener('dragend', (e) => { - this.handleTableContainerDragEnd(e); - }); - - // Prevent drag on control buttons - const controlButtons = tableContainer.querySelectorAll('.table-control-btn'); - controlButtons.forEach(btn => { - btn.addEventListener('dragstart', (e) => { - e.preventDefault(); - }); - }); - } - - // Improved text insertion that's draggable anywhere - insertDraggableText() { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (!editor) { - this.showError('Editor not found'); - return; - } - - // Save undo state before making changes - this.saveUndoState(); - - // Create draggable text element - const textElement = document.createElement('div'); - textElement.className = 'draggable-element draggable-text'; - textElement.contentEditable = true; - textElement.innerHTML = 'Click to edit text'; - - // Position absolutely for free placement - textElement.style.position = 'absolute'; - textElement.style.left = '20px'; - textElement.style.top = '20px'; - textElement.style.minWidth = '150px'; - textElement.style.minHeight = '30px'; - textElement.style.padding = '8px'; - textElement.style.border = '2px dashed #4f46e5'; - textElement.style.borderRadius = '4px'; - textElement.style.backgroundColor = 'rgba(255, 255, 255, 0.9)'; - textElement.style.zIndex = '1000'; - textElement.style.cursor = 'move'; - textElement.style.fontFamily = 'Inter, sans-serif'; - textElement.style.fontSize = '14px'; - textElement.style.lineHeight = '1.4'; - - // Add to editor - editor.appendChild(textElement); - - // Make it draggable and resizable - this.addResizeHandles(textElement); - this.makeDraggable(textElement); - - // Select the text for immediate editing - setTimeout(() => { - textElement.focus(); - const selection = window.getSelection(); - const range = document.createRange(); - range.selectNodeContents(textElement); - selection.removeAllRanges(); - selection.addRange(range); - }, 100); - - } - - // Undo/Redo functionality - saveUndoState() { - const editor = this.template.querySelector('.enhanced-editor-content'); - if (!editor) return; - - const currentState = { - content: editor.innerHTML, - timestamp: Date.now() - }; - - this.undoStack.push(currentState); - - // Limit undo stack size - if (this.undoStack.length > this.maxUndoSteps) { - this.undoStack.shift(); - } - - // Clear redo stack when new action is performed - this.redoStack = []; - } - - undo() { - if (this.undoStack.length === 0) return; - - const editor = this.template.querySelector('.enhanced-editor-content'); - if (!editor) return; - - // Save current state to redo stack - const currentState = { - content: editor.innerHTML, - timestamp: Date.now() - }; - this.redoStack.push(currentState); - - // Restore previous state - const previousState = this.undoStack.pop(); - editor.innerHTML = previousState.content; - - // Re-setup event handlers for any dynamic elements - this.setupEditorEventHandlers(); - - } - - redo() { - if (this.redoStack.length === 0) return; - - const editor = this.template.querySelector('.enhanced-editor-content'); - if (!editor) return; - - // Save current state to undo stack - const currentState = { - content: editor.innerHTML, - timestamp: Date.now() - }; - this.undoStack.push(currentState); - - // Restore next state - const nextState = this.redoStack.pop(); - editor.innerHTML = nextState.content; - - // Re-setup event handlers for any dynamic elements - this.setupEditorEventHandlers(); - - } - - // Setup editor event handlers after undo/redo - setupEditorEventHandlers() { - this.setupEditorClickHandler(); - this.ensureEditorEditable(); - } - - // Find the first available category that has images - findFirstAvailableCategory() { - if (!this.realPropertyImages || this.realPropertyImages.length === 0) { - return 'None'; - } - - // Define the order of categories to check - const categoryOrder = ['Interior', 'Exterior', 'Kitchen', 'Bedroom', 'Living Area', 'Parking', 'Anchor', 'Maps', 'None']; - - // Check each category in order - for (let category of categoryOrder) { - const hasImages = this.realPropertyImages.some(img => { - const imgCategory = img.category || img.pcrm__Category__c; - - if (category === 'None') { - return !imgCategory || imgCategory === '' || imgCategory === null || imgCategory === undefined || imgCategory === 'None'; - } - - return imgCategory === category; - }); - - if (hasImages) { - return category; - } - } - - // Fallback to None if no specific category has images - return 'None'; - } - - // Ensure smart category selection only on initial load - ensureSmartCategorySelection() { - // Only run if initial category selection hasn't been done yet - if (!this.initialCategorySelected && this.realPropertyImages && this.realPropertyImages.length > 0) { - const firstAvailableCategory = this.findFirstAvailableCategory(); - this.selectedCategory = firstAvailableCategory; - this.filterImagesByCategory(firstAvailableCategory); - this.initialCategorySelected = true; - - // Update button states - const categoryButtons = this.template.querySelectorAll('.category-btn-step2'); - categoryButtons.forEach(btn => { - btn.classList.remove('active'); - if (btn.dataset.category === firstAvailableCategory) { - btn.classList.add('active'); - } - }); - } - } - - - - // Enhanced keyboard event handler - handleEditorKeydown(event) { - // Check for Ctrl+Z (Undo) - if ((event.ctrlKey || event.metaKey) && event.key === 'z' && !event.shiftKey) { - event.preventDefault(); - this.undo(); - return; - } - - // Check for Ctrl+Y or Ctrl+Shift+Z (Redo) - if ((event.ctrlKey || event.metaKey) && - (event.key === 'y' || (event.key === 'z' && event.shiftKey))) { - event.preventDefault(); - this.redo(); - return; - } - - // Save state before modifications (with debouncing) - if (!this.pendingUndoSave) { - this.pendingUndoSave = true; - setTimeout(() => { - this.saveUndoState(); - this.pendingUndoSave = false; - }, 500); - } - } - - // Enhanced image manipulation methods - addResizeHandles(container) { - const handles = ['nw', 'ne', 'sw', 'se', 'n', 's', 'w', 'e']; - - handles.forEach(handle => { - const resizeHandle = document.createElement('div'); - resizeHandle.className = `resize-handle ${handle}`; - resizeHandle.style.position = 'absolute'; - resizeHandle.style.background = '#4f46e5'; - resizeHandle.style.border = '2px solid white'; - resizeHandle.style.borderRadius = '50%'; - resizeHandle.style.width = '12px'; - resizeHandle.style.height = '12px'; - resizeHandle.style.zIndex = '1001'; - - // Position handles - switch(handle) { - case 'nw': resizeHandle.style.top = '-6px'; resizeHandle.style.left = '-6px'; resizeHandle.style.cursor = 'nw-resize'; break; - case 'ne': resizeHandle.style.top = '-6px'; resizeHandle.style.right = '-6px'; resizeHandle.style.cursor = 'ne-resize'; break; - case 'sw': resizeHandle.style.bottom = '-6px'; resizeHandle.style.left = '-6px'; resizeHandle.style.cursor = 'sw-resize'; break; - case 'se': resizeHandle.style.bottom = '-6px'; resizeHandle.style.right = '-6px'; resizeHandle.style.cursor = 'se-resize'; break; - case 'n': resizeHandle.style.top = '-6px'; resizeHandle.style.left = '50%'; resizeHandle.style.transform = 'translateX(-50%)'; resizeHandle.style.cursor = 'n-resize'; break; - case 's': resizeHandle.style.bottom = '-6px'; resizeHandle.style.left = '50%'; resizeHandle.style.transform = 'translateX(-50%)'; resizeHandle.style.cursor = 's-resize'; break; - case 'w': resizeHandle.style.top = '50%'; resizeHandle.style.left = '-6px'; resizeHandle.style.transform = 'translateY(-50%)'; resizeHandle.style.cursor = 'w-resize'; break; - case 'e': resizeHandle.style.top = '50%'; resizeHandle.style.right = '-6px'; resizeHandle.style.transform = 'translateY(-50%)'; resizeHandle.style.cursor = 'e-resize'; break; - } - - // Add resize functionality - this.addResizeFunctionality(resizeHandle, container, handle); - - container.appendChild(resizeHandle); - }); - } - - // Add delete handle to image - addDeleteHandle(container) { - const deleteHandle = document.createElement('button'); - deleteHandle.className = 'delete-handle'; - deleteHandle.innerHTML = '×'; - deleteHandle.style.position = 'absolute'; - deleteHandle.style.top = '-8px'; - deleteHandle.style.right = '-8px'; - deleteHandle.style.background = '#ef4444'; - deleteHandle.style.color = 'white'; - deleteHandle.style.border = 'none'; - deleteHandle.style.borderRadius = '50%'; - deleteHandle.style.width = '20px'; - deleteHandle.style.height = '20px'; - deleteHandle.style.fontSize = '12px'; - deleteHandle.style.cursor = 'pointer'; - deleteHandle.style.zIndex = '1002'; - deleteHandle.style.display = 'flex'; - deleteHandle.style.alignItems = 'center'; - deleteHandle.style.justifyContent = 'center'; - deleteHandle.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.2)'; - - deleteHandle.addEventListener('click', (e) => { - e.stopPropagation(); - this.saveUndoState(); - container.remove(); - }); - - deleteHandle.addEventListener('mouseenter', () => { - deleteHandle.style.background = '#dc2626'; - deleteHandle.style.transform = 'scale(1.1)'; - }); - - deleteHandle.addEventListener('mouseleave', () => { - deleteHandle.style.background = '#ef4444'; - deleteHandle.style.transform = 'scale(1)'; - }); - - container.appendChild(deleteHandle); - } - - // Add resize functionality to handle - addResizeFunctionality(handle, container, direction) { - let isResizing = false; - let startX, startY, startWidth, startHeight, startLeft, startTop; - - handle.addEventListener('mousedown', (e) => { - e.stopPropagation(); - isResizing = true; - - startX = e.clientX; - startY = e.clientY; - startWidth = parseInt(window.getComputedStyle(container).width, 10); - startHeight = parseInt(window.getComputedStyle(container).height, 10); - startLeft = parseInt(window.getComputedStyle(container).left, 10); - startTop = parseInt(window.getComputedStyle(container).top, 10); - - document.addEventListener('mousemove', handleResize); - document.addEventListener('mouseup', stopResize); - }); - - const handleResize = (e) => { - if (!isResizing) return; - - const deltaX = e.clientX - startX; - const deltaY = e.clientY - startY; - - let newWidth = startWidth; - let newHeight = startHeight; - let newLeft = startLeft; - let newTop = startTop; - - switch(direction) { - case 'se': - newWidth = Math.max(50, startWidth + deltaX); - newHeight = Math.max(50, startHeight + deltaY); - break; - case 'sw': - newWidth = Math.max(50, startWidth - deltaX); - newHeight = Math.max(50, startHeight + deltaY); - newLeft = startLeft + (startWidth - newWidth); - break; - case 'ne': - newWidth = Math.max(50, startWidth + deltaX); - newHeight = Math.max(50, startHeight - deltaY); - newTop = startTop + (startHeight - newHeight); - break; - case 'nw': - newWidth = Math.max(50, startWidth - deltaX); - newHeight = Math.max(50, startHeight - deltaY); - newLeft = startLeft + (startWidth - newWidth); - newTop = startTop + (startHeight - newHeight); - break; - case 'e': - newWidth = Math.max(50, startWidth + deltaX); - break; - case 'w': - newWidth = Math.max(50, startWidth - deltaX); - newLeft = startLeft + (startWidth - newWidth); - break; - case 's': - newHeight = Math.max(50, startHeight + deltaY); - break; - case 'n': - newHeight = Math.max(50, startHeight - deltaY); - newTop = startTop + (startHeight - newHeight); - break; - } - - container.style.width = newWidth + 'px'; - container.style.height = newHeight + 'px'; - container.style.left = newLeft + 'px'; - container.style.top = newTop + 'px'; - }; - - const stopResize = () => { - isResizing = false; - document.removeEventListener('mousemove', handleResize); - document.removeEventListener('mouseup', stopResize); - }; - } - - // Make element draggable (enhanced version) - makeDraggable(element) { - let isDragging = false; - let startX, startY, startLeft, startTop; - - element.addEventListener('mousedown', (e) => { - // Don't start drag if clicking on resize handles or delete button - if (e.target.classList.contains('resize-handle') || e.target.classList.contains('delete-handle')) { - return; - } - - isDragging = true; - startX = e.clientX; - startY = e.clientY; - startLeft = parseInt(window.getComputedStyle(element).left, 10); - startTop = parseInt(window.getComputedStyle(element).top, 10); - - element.style.cursor = 'grabbing'; - document.addEventListener('mousemove', handleDrag); - document.addEventListener('mouseup', stopDrag); - }); - - const handleDrag = (e) => { - if (!isDragging) return; - - const deltaX = e.clientX - startX; - const deltaY = e.clientY - startY; - - element.style.left = (startLeft + deltaX) + 'px'; - element.style.top = (startTop + deltaY) + 'px'; - }; - - const stopDrag = () => { - isDragging = false; - element.style.cursor = 'move'; - document.removeEventListener('mousemove', handleDrag); - document.removeEventListener('mouseup', stopDrag); - }; - } - - // Select draggable element - selectDraggableElement(element) { - // Remove selection from all draggable elements - const editor = this.template.querySelector('.enhanced-editor-content'); - if (editor) { - const allDraggable = editor.querySelectorAll('.draggable-element, .draggable-image-container, .draggable-table-container'); - allDraggable.forEach(el => { - if (el !== element) { - el.classList.remove('selected'); - // Remove any resize handles - const resizeHandles = el.querySelectorAll('.resize-handle'); - resizeHandles.forEach(handle => handle.remove()); - // Remove any delete buttons - const deleteButtons = el.querySelectorAll('.delete-handle, .delete-image-btn'); - deleteButtons.forEach(btn => btn.remove()); - } - }); - } - - // Add selection to clicked element - element.classList.add('selected'); - - // Add resize handles and controls to the selected element - if (element.classList.contains('draggable-image-container')) { - const img = element.querySelector('img'); - if (img) { - this.addResizeHandles(img); - this.addDeleteButton(element); - } - } else if (element.classList.contains('draggable-table-container')) { - this.addTableResizeHandles(element); - this.addDeleteButton(element); - } else if (element.tagName && element.tagName.toLowerCase() === 'img') { - // If already wrapped, ensure handles on container - if (element.parentElement && element.parentElement.classList && element.parentElement.classList.contains('draggable-image-container')) { - const container = element.parentElement; - this.addResizeHandles(container); - this.makeDraggable(container); - this.addDeleteButton(container); - this.highlightSelectedElement(container); - return; - } - // Wrap plain image and add handles; preserve on-screen size and position - const editor = this.template.querySelector('.enhanced-editor-content'); - const rect = element.getBoundingClientRect(); - const editorRect = editor ? editor.getBoundingClientRect() : { left: 0, top: 0 }; - const currentWidth = element.offsetWidth; - const currentHeight = element.offsetHeight; - - const container = document.createElement('div'); - container.className = 'draggable-image-container'; - container.style.position = 'absolute'; - container.style.left = (rect.left - editorRect.left + (editor ? editor.scrollLeft : 0)) + 'px'; - container.style.top = (rect.top - editorRect.top + (editor ? editor.scrollTop : 0)) + 'px'; - container.style.zIndex = window.getComputedStyle(element).zIndex || 'auto'; - container.style.display = 'inline-block'; - - // Move the image into container and preserve size - // Set container and image sizing - container.style.width = currentWidth + 'px'; - container.style.height = currentHeight + 'px'; - element.style.width = '100%'; - element.style.height = '100%'; - element.style.display = 'block'; - element.style.objectFit = 'cover'; - - if (editor) { - editor.appendChild(container); - } else { - element.parentNode.insertBefore(container, element); - } - container.appendChild(element); - container.classList.add('no-frame'); - this.addResizeHandles(container); - this.makeDraggable(container); - this.addDeleteButton(container); - this.highlightSelectedElement(container); - } - } - - // Add delete button to element - addDeleteButton(element) { - // Remove existing delete button if any - const existingDelete = element.querySelector('.delete-handle, .delete-image-btn'); - if (existingDelete) { - existingDelete.remove(); - } - - const deleteBtn = document.createElement('div'); - deleteBtn.className = 'delete-handle'; - deleteBtn.innerHTML = '×'; - deleteBtn.style.cssText = ` + const deleteBtn = document.createElement("div"); + deleteBtn.className = "delete-handle"; + deleteBtn.innerHTML = "×"; + deleteBtn.style.cssText = ` position: absolute; top: -10px; right: -10px; @@ -8711,31 +18474,30 @@ export default class PropertyTemplateSelector extends LightningElement { z-index: 1000; box-shadow: 0 2px 4px rgba(0,0,0,0.2); `; - - deleteBtn.addEventListener('click', (e) => { - e.stopPropagation(); - element.remove(); - }); - - element.appendChild(deleteBtn); - } - - // Add table resize handles - addTableResizeHandles(tableContainer) { - // Remove existing resize handles if any - const existingHandles = tableContainer.querySelectorAll('.resize-handle'); - existingHandles.forEach(handle => handle.remove()); - - const table = tableContainer.querySelector('table'); - if (!table) return; - - // Add resize handles to table corners - const positions = ['nw', 'ne', 'sw', 'se']; - positions.forEach(pos => { - const handle = document.createElement('div'); - handle.className = `resize-handle resize-${pos}`; - handle.dataset.position = pos; - handle.style.cssText = ` + + deleteBtn.addEventListener("click", (e) => { + e.stopPropagation(); + element.remove(); + }); + + element.appendChild(deleteBtn); + } + // Add table resize handles + addTableResizeHandles(tableContainer) { + // Remove existing resize handles if any + const existingHandles = tableContainer.querySelectorAll(".resize-handle"); + existingHandles.forEach((handle) => handle.remove()); + + const table = tableContainer.querySelector("table"); + if (!table) return; + + // Add resize handles to table corners + const positions = ["nw", "ne", "sw", "se"]; + positions.forEach((pos) => { + const handle = document.createElement("div"); + handle.className = `resize-handle resize-${pos}`; + handle.dataset.position = pos; + handle.style.cssText = ` position: absolute; width: 8px; height: 8px; @@ -8744,112 +18506,104 @@ export default class PropertyTemplateSelector extends LightningElement { cursor: ${pos}-resize; z-index: 1000; `; - - // Position the handle - switch(pos) { - case 'nw': - handle.style.top = '-4px'; - handle.style.left = '-4px'; - break; - case 'ne': - handle.style.top = '-4px'; - handle.style.right = '-4px'; - break; - case 'sw': - handle.style.bottom = '-4px'; - handle.style.left = '-4px'; - break; - case 'se': - handle.style.bottom = '-4px'; - handle.style.right = '-4px'; - break; - } - - // Enable resizing using shared startResize - handle.addEventListener('mousedown', (e) => { - e.preventDefault(); - e.stopPropagation(); - this.startResize(e, tableContainer, pos); - }); - - tableContainer.appendChild(handle); - }); + + // Position the handle + switch (pos) { + case "nw": + handle.style.top = "-4px"; + handle.style.left = "-4px"; + break; + case "ne": + handle.style.top = "-4px"; + handle.style.right = "-4px"; + break; + case "sw": + handle.style.bottom = "-4px"; + handle.style.left = "-4px"; + break; + case "se": + handle.style.bottom = "-4px"; + handle.style.right = "-4px"; + break; + } + + // Enable resizing using shared startResize + handle.addEventListener("mousedown", (e) => { + e.preventDefault(); + e.stopPropagation(); + this.startResize(e, tableContainer, pos); + }); + + tableContainer.appendChild(handle); + }); + } + + // Force re-render by updating a tracked property + forceRerender() { + // Update a dummy property to force reactivity + this.renderKey = this.renderKey ? this.renderKey + 1 : 1; + } + + // Debug method to log current state + logCurrentState() { } + // Test method to manually set an image (for debugging) + testSetImage() { + this.selectedImageUrl = + "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200"; + this.selectedImageName = "Test Image"; + this.insertButtonDisabled = false; + this.logCurrentState(); + } + + // Global function to get a random image from available images + getRandomImage() { + const fallbackImage = + "https://plus.unsplash.com/premium_photo-1676467963268-5a20d7a7a448?q=80&w=687&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"; + + // Get all available images from different sources + const allImages = []; + + // Add real property images + if ( + Array.isArray(this.realPropertyImages) && + this.realPropertyImages.length > 0 + ) { + allImages.push(...this.realPropertyImages); } - // Force re-render by updating a tracked property - forceRerender() { - // Update a dummy property to force reactivity - this.renderKey = this.renderKey ? this.renderKey + 1 : 1; - } - - // Debug method to log current state - logCurrentState() { - } - - // Test method to manually set an image (for debugging) - testSetImage() { - this.selectedImageUrl = 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&w=1200'; - this.selectedImageName = 'Test Image'; - this.insertButtonDisabled = false; - this.logCurrentState(); + // Add property images + if (Array.isArray(this.propertyImages) && this.propertyImages.length > 0) { + allImages.push(...this.propertyImages); } - connectedCallback() { - this.loadSavedTemplates(); + // Add images from all categories + Object.values(this.imagesByCategory).forEach((categoryImages) => { + if (Array.isArray(categoryImages) && categoryImages.length > 0) { + allImages.push(...categoryImages); + } + }); + + // If no images available, return fallback + if (allImages.length === 0) { + return fallbackImage; } - - // Helper method for generating amenities HTML - generateAmenitiesHTML(data) { - const amenities = [ - { key: 'Swimming Pool', icon: 'fa-swimming-pool', value: data.Swimming_Pool__c || 'Yes' }, - { key: 'Gym', icon: 'fa-dumbbell', value: data.Gym__c || 'Yes' }, - { key: 'Parking', icon: 'fa-car', value: data.Parking_Spaces__c || '2' }, - { key: 'Garden', icon: 'fa-tree', value: data.Garden__c || 'Yes' }, - { key: 'Security', icon: 'fa-shield-alt', value: data.Security__c || '24/7' }, - { key: 'Balcony', icon: 'fa-home', value: data.Balcony__c || 'Yes' }, - { key: 'Air Conditioning', icon: 'fa-snowflake', value: data.AC__c || 'Central' }, - { key: 'WiFi', icon: 'fa-wifi', value: data.WiFi__c || 'Included' } - ]; - - return amenities.map(amenity => ` -
    - - ${amenity.key}: ${amenity.value} -
    - `).join(''); + + // Get a random image from available images + const randomIndex = Math.floor(Math.random() * allImages.length); + const randomImage = allImages[randomIndex]; + + // Return the image URL, handling different possible structures + return randomImage?.url || randomImage?.src || randomImage || fallbackImage; + } + + connectedCallback() { + this.loadSavedTemplates(); + } + + disconnectedCallback() { + // Clean up event listeners + if (this.resizeHandler) { + window.removeEventListener('resize', this.resizeHandler); } - - // Helper method for generating property gallery HTML - generatePropertyGalleryHTML() { - const images = this.realPropertyImages || []; - if (images.length === 0) { - const exteriorImage = this.getExteriorImageUrl(); - return ` - - - - `; - } - - return images.slice(0, 6).map(image => ` - - `).join(''); - } - - // Helper method for getting smart images for sections - getSmartImageForSection(section, fallback) { - const images = this.realPropertyImages || []; - const sectionImage = images.find(img => - img.category && img.category.toLowerCase().includes(section.toLowerCase()) - ); - return sectionImage ? sectionImage.url : fallback; - } -} \ No newline at end of file + } +}