/** * Drag & Drop Module * จัดการฟังก์ชันการลากและวางสำหรับคอมโพเนนต์และอิลิเมนต์ */ (function(global) { 'use strict'; /** * คลาส DragDrop * ให้ความสามารถในการลากและวางคอมโพเนนต์และอิลิเมนต์ */ class DragDrop { constructor(editor) { this.editor = editor; this.draggedElement = null; this.draggedComponent = null; this.dropIndicator = null; this.dropZones = []; this.isDragging = false; this.dragStartX = 0; this.dragStartY = 0; } /** * เริ่มต้นโมดูลลากและวาง */ init() { this.createDropIndicator(); this.setupEventListeners(); this.identifyDropZones(); if (this.editor.config.debug) { console.log('DragDrop เริ่มต้นแล้ว'); } } /** * สร้างตัวบ่งชี้การวาง */ createDropIndicator() { this.dropIndicator = this.editor.domUtils.createElement('div', { 'class': 'editor-drop-indicator', 'id': 'editor-drop-indicator' }); document.body.appendChild(this.dropIndicator); } /** * ตั้งค่า event listeners */ setupEventListeners() { // ฟังเหตุการณ์การเริ่มลากคอมโพเนนต์ document.addEventListener('dragstart', (e) => { if (e.target.classList.contains('editor-component-item')) { this.handleComponentDragStart(e); } else if (e.target.classList.contains('editor-draggable')) { this.handleElementDragStart(e); } }); // ฟังเหตุการณ์การลาก document.addEventListener('dragover', (e) => { if (this.isDragging) { e.preventDefault(); this.handleDragOver(e); } }); // ฟังเหตุการณ์การวาง document.addEventListener('drop', (e) => { if (this.isDragging) { e.preventDefault(); this.handleDrop(e); } }); // ฟังเหตุการณ์สิ้นสุดการลาก document.addEventListener('dragend', (e) => { if (this.isDragging) { this.handleDragEnd(e); } }); // ฟังเหตุการณ์การเปลี่ยนแปลงโหมดตัวแก้ไข this.editor.on('editor:mode-changed', (data) => { if (data.mode === 'edit') { this.enableDragDrop(); } else { this.disableDragDrop(); } }); } /** * ระบุโซนการวาง */ identifyDropZones() { // ระบุโซนการวางทั้งหมดในคอนเทนเนอร์ตัวแก้ไข this.dropZones = Array.from(this.editor.container.querySelectorAll('.editor-drop-zone, .editor-component, section, div')); // เพิ่มคลาสและแอตทริบิวต์ให้กับโซนการวาง this.dropZones.forEach(zone => { if (!zone.classList.contains('editor-no-drop')) { zone.classList.add('editor-drop-zone'); } }); } /** * เปิดใช้งานการลากและวาง */ enableDragDrop() { // ทำเครื่องหมายอิลิเมนต์ที่ลากได้ const draggableElements = this.editor.container.querySelectorAll('.editor-component, section, div'); draggableElements.forEach(element => { if (!element.classList.contains('editor-no-drag')) { element.classList.add('editor-draggable'); element.draggable = true; } }); // ทำเครื่องหมายรายการคอมโพเนนต์ที่ลากได้ const componentItems = document.querySelectorAll('.editor-component-item'); componentItems.forEach(item => { item.draggable = true; }); } /** * ปิดใช้งานการลากและวาง */ disableDragDrop() { // ลบคลาสและแอตทริบิวต์จากอิลิเมนต์ที่ลากได้ const draggableElements = this.editor.container.querySelectorAll('.editor-draggable'); draggableElements.forEach(element => { element.classList.remove('editor-draggable'); element.draggable = false; }); // ลบแอตทริบิวต์จากรายการคอมโพเนนต์ const componentItems = document.querySelectorAll('.editor-component-item'); componentItems.forEach(item => { item.draggable = false; }); } /** * จัดการการเริ่มลากคอมโพเนนต์ */ handleComponentDragStart(e) { const componentType = e.target.getAttribute('data-component'); if (!componentType) return; this.isDragging = true; this.draggedComponent = componentType; this.draggedElement = null; // จัดเก็บตำแหน่งเริ่มต้น this.dragStartX = e.clientX; this.dragStartY = e.clientY; // ตั้งค่าข้อมูลการลาก e.dataTransfer.effectAllowed = 'copy'; e.dataTransfer.setData('text/html', e.target.innerHTML); // เพิ่มคลาสการลาก e.target.classList.add('editor-dragging'); // ส่งเหตุการณ์ this.editor.emit('dragdrop:component-drag-start', { component: componentType, element: e.target }); } /** * จัดการการเริ่มลากอิลิเมนต์ */ handleElementDragStart(e) { this.isDragging = true; this.draggedElement = e.target; this.draggedComponent = null; // จัดเก็บตำแหน่งเริ่มต้น this.dragStartX = e.clientX; this.dragStartY = e.clientY; // ตั้งค่าข้อมูลการลาก e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/html', e.target.outerHTML); // เพิ่มคลาสการลาก e.target.classList.add('editor-dragging'); // ส่งเหตุการณ์ this.editor.emit('dragdrop:element-drag-start', { element: e.target }); } /** * จัดการการลาก */ handleDragOver(e) { // หาโซนการวางที่ใกล้ที่สุด const dropZone = this.findClosestDropZone(e.target); if (dropZone) { // คำนวณตำแหน่งการวาง const position = this.calculateDropPosition(dropZone, e.clientX, e.clientY); // แสดงตัวบ่งชี้การวาง this.showDropIndicator(dropZone, position); // อัปเดตคลาสโซนการวาง this.dropZones.forEach(zone => { zone.classList.remove('editor-drop-over'); }); dropZone.classList.add('editor-drop-over'); } } /** * จัดการการวาง */ handleDrop(e) { // หาโซนการวางที่ใกล้ที่สุด const dropZone = this.findClosestDropZone(e.target); if (!dropZone) return; // คำนวณตำแหน่งการวาง const position = this.calculateDropPosition(dropZone, e.clientX, e.clientY); // วางอิลิเมนต์หรือคอมโพเนนต์ if (this.draggedComponent) { this.dropComponent(dropZone, position); } else if (this.draggedElement) { this.dropElement(dropZone, position); } // ส่งเหตุการณ์ this.editor.emit('dragdrop:dropped', { dropZone, position, component: this.draggedComponent, element: this.draggedElement }); } /** * จัดการสิ้นสุดการลาก */ handleDragEnd(e) { // ลบคลาสการลาก const draggingElements = document.querySelectorAll('.editor-dragging'); draggingElements.forEach(element => { element.classList.remove('editor-dragging'); }); // ลบคลาสโซนการวาง this.dropZones.forEach(zone => { zone.classList.remove('editor-drop-over'); }); // ซ่อนตัวบ่งชี้การวาง this.hideDropIndicator(); // รีเซ็ตสถานะ this.isDragging = false; this.draggedElement = null; this.draggedComponent = null; // ส่งเหตุการณ์ this.editor.emit('dragdrop:drag-end'); } /** * หาโซนการวางที่ใกล้ที่สุด */ findClosestDropZone(element) { // ถ้าอิลิเมนต์เป็นโซนการวาง ให้คืนค่าอิลิเมนต์นั้น if (element.classList.contains('editor-drop-zone')) { return element; } // ค้นหาพาเรนต์ที่เป็นโซนการวาง let parent = element.parentElement; while (parent) { if (parent.classList.contains('editor-drop-zone')) { return parent; } parent = parent.parentElement; } // ถ้าไม่พบ ให้คืนค่าคอนเทนเนอร์ตัวแก้ไข return this.editor.container; } /** * คำนวณตำแหน่งการวาง */ calculateDropPosition(dropZone, x, y) { const rect = dropZone.getBoundingClientRect(); // ตรวจสอบว่าเป็นการวางแนวนอนหรือแนวตั้ง const isHorizontal = this.isHorizontalLayout(dropZone); if (isHorizontal) { // คำนวณตำแหน่งแนวนอน const midPoint = rect.left + rect.width / 2; return x < midPoint ? 'before' : 'after'; } else { // คำนวณตำแหน่งแนวตั้ง const midPoint = rect.top + rect.height / 2; return y < midPoint ? 'before' : 'after'; } } /** * ตรวจสอบว่าเป็นเลย์เอาต์แนวนอนหรือไม่ */ isHorizontalLayout(element) { const style = window.getComputedStyle(element); // ตรวจสอบคุณสมบัติ flex if (style.display === 'flex') { return style.flexDirection === 'row' || style.flexDirection === 'row-reverse'; } // ตรวจสอบคุณสมบัติ grid if (style.display === 'grid') { return parseInt(style.gridAutoFlow) === 1 || style.gridAutoFlow === 'column'; } // ตรวจสอบจำนวนอิลิเมนต์ลูกและความกว้าง const children = Array.from(element.children); if (children.length > 0) { const firstChildRect = children[0].getBoundingClientRect(); const secondChildRect = children[1] ? children[1].getBoundingClientRect() : null; if (secondChildRect) { // ถ้าอิลิเมนต์ลูกถัดไปอยู่ทางขวา ให้ถือว่าเป็นแนวนอน return secondChildRect.left > firstChildRect.right; } } // ค่าเริ่มต้น: แนวตั้ง return false; } /** * แสดงตัวบ่งชี้การวาง */ showDropIndicator(dropZone, position) { const rect = dropZone.getBoundingClientRect(); // ตั้งค่าคลาสตามตำแหน่ง this.dropIndicator.className = 'editor-drop-indicator'; if (position === 'before') { if (this.isHorizontalLayout(dropZone)) { this.dropIndicator.classList.add('horizontal'); this.dropIndicator.style.top = `${rect.top + window.scrollY}px`; this.dropIndicator.style.left = `${rect.left + window.scrollX}px`; this.dropIndicator.style.width = '3px'; this.dropIndicator.style.height = `${rect.height}px`; } else { this.dropIndicator.classList.add('horizontal'); this.dropIndicator.style.top = `${rect.top + window.scrollY}px`; this.dropIndicator.style.left = `${rect.left + window.scrollX}px`; this.dropIndicator.style.width = `${rect.width}px`; this.dropIndicator.style.height = '3px'; } } else { if (this.isHorizontalLayout(dropZone)) { this.dropIndicator.classList.add('horizontal'); this.dropIndicator.style.top = `${rect.top + window.scrollY}px`; this.dropIndicator.style.left = `${rect.right + window.scrollX - 3}px`; this.dropIndicator.style.width = '3px'; this.dropIndicator.style.height = `${rect.height}px`; } else { this.dropIndicator.classList.add('horizontal'); this.dropIndicator.style.top = `${rect.bottom + window.scrollY - 3}px`; this.dropIndicator.style.left = `${rect.left + window.scrollX}px`; this.dropIndicator.style.width = `${rect.width}px`; this.dropIndicator.style.height = '3px'; } } // แสดงตัวบ่งชี้ this.dropIndicator.style.display = 'block'; } /** * ซ่อนตัวบ่งชี้การวาง */ hideDropIndicator() { this.dropIndicator.style.display = 'none'; } /** * วางคอมโพเนนต์ */ dropComponent(dropZone, position) { // รับเทมเพลตคอมโพเนนต์จากปลั๊กอิน let componentHTML = ''; if (this.editor.plugins[this.draggedComponent]) { componentHTML = this.editor.plugins[this.draggedComponent].getTemplate(); } else { // ใช้เทมเพลตเริ่มต้น componentHTML = this.getDefaultComponentTemplate(this.draggedComponent); } // สร้างอิลิเมนต์จาก HTML const tempDiv = document.createElement('div'); tempDiv.innerHTML = componentHTML; const componentElement = tempDiv.firstChild; // เพิ่มคลาสและแอตทริบิวต์ componentElement.classList.add('editor-component'); componentElement.setAttribute('data-component', this.draggedComponent); componentElement.classList.add('editor-draggable'); componentElement.draggable = true; // ทำเครื่องหมายอิลิเมนต์ย่อยที่แก้ไขได้ this.markEditableElements(componentElement); // แทรกอิลิเมนต์ในตำแหน่งที่เหมาะสม if (position === 'before') { dropZone.parentNode.insertBefore(componentElement, dropZone); } else { dropZone.parentNode.insertBefore(componentElement, dropZone.nextSibling); } // อัปเดตโซนการวาง this.identifyDropZones(); // ส่งเหตุการณ์ this.editor.emit('dragdrop:component-dropped', { component: this.draggedComponent, element: componentElement, dropZone, position }); } /** * วางอิลิเมนต์ */ dropElement(dropZone, position) { // ตรวจสอบว่าไม่ได้วางในตัวเอง if (dropZone === this.draggedElement || dropZone.contains(this.draggedElement)) { return; } // แทรกอิลิเมนต์ในตำแหน่งที่เหมาะสม if (position === 'before') { dropZone.parentNode.insertBefore(this.draggedElement, dropZone); } else { dropZone.parentNode.insertBefore(this.draggedElement, dropZone.nextSibling); } // อัปเดตโซนการวาง this.identifyDropZones(); // ส่งเหตุการณ์ this.editor.emit('dragdrop:element-dropped', { element: this.draggedElement, dropZone, position }); } /** * รับเทมเพลตคอมโพเนนต์เริ่มต้น */ getDefaultComponentTemplate(componentType) { switch (componentType) { case 'header': return `

ส่วนหัวเว็บไซต์

`; case 'footer': return ` `; case 'hero': return `

ยินดีต้อนรับสู่เว็บไซต์ของเรา

นี่คือส่วนแสดงผลหลักของเว็บไซต์

`; case 'carousel': return ` `; case 'card': return `
รูปภาพการ์ด

หัวข้อการ์ด

นี่คือเนื้อหาของการ์ด สามารถแก้ไขได้ตามต้องการ

อ่านเพิ่มเติม
`; case 'grid': return `

รายการ 1

เนื้อหารายการที่ 1

รายการ 2

เนื้อหารายการที่ 2

รายการ 3

เนื้อหารายการที่ 3

`; default: return `

คอมโพเนนต์: ${componentType}

`; } } /** * ทำเครื่องหมายอิลิเมนต์ที่แก้ไขได้ */ markEditableElements(element) { const textElements = element.querySelectorAll('h1, h2, h3, h4, h5, h6, p, span, div, li, td, th, a, button'); textElements.forEach(el => { // ข้ามอิลิเมนต์ที่มีคลาสหรือแอตทริบิวต์เฉพาะ if (el.classList.contains('editor-no-edit') || el.hasAttribute('data-no-edit') || el.closest('.editor-no-edit')) { return; } // ทำเครื่องหมายว่าแก้ไขได้ el.setAttribute('data-editable', 'true'); }); } } // เปิดเผยคลาส DragDrop ทั่วโลก global.DragDrop = DragDrop; })(typeof window !== 'undefined' ? window : this);