/** * Content Editor Module * จัดการการแก้ไขเนื้อหาแบบอินไลน์ด้วยแถบเครื่องมือลอย */ (function(global) { 'use strict'; /** * คลาส ContentEditor * ให้ความสามารถในการแก้ไขเนื้อหาแบบอินไลน์สำหรับอิลิเมนต์ข้อความ */ class ContentEditor { constructor(editor) { this.editor = editor; this.isEditing = false; this.currentElement = null; this.textToolbar = null; this.history = []; this.historyIndex = -1; this.maxHistorySize = 50; this.autoSaveTimer = null; } /** * เริ่มต้นโมดูลตัวแก้ไขเนื้อหา */ init() { this.createTextToolbar(); this.setupEventListeners(); // ทำเครื่องหมายอิลิเมนต์ที่แก้ไขได้ this.markEditableElements(); if (this.editor.config.debug) { console.log('ContentEditor เริ่มต้นแล้ว'); } } /** * ทำเครื่องหมายอิลิเมนต์ที่แก้ไขได้ในคอนเทนเนอร์ */ markEditableElements() { const textElements = this.editor.container.querySelectorAll('h1, h2, h3, h4, h5, h6, p, span, div, li, td, th, a, button'); textElements.forEach(element => { // ข้ามอิลิเมนต์ที่มีคลาสหรือแอตทริบิวต์เฉพาะ if (element.classList.contains('editor-no-edit') || element.hasAttribute('data-no-edit') || element.closest('.editor-no-edit')) { return; } // ทำเครื่องหมายว่าแก้ไขได้ element.setAttribute('data-editable', 'true'); }); } /** * ตั้งค่า event listeners */ setupEventListeners() { // ฟังเหตุการณ์การคลิกอิลิเมนต์ this.editor.container.addEventListener('click', (e) => { if (this.editor.state !== 'edit') return; const editableElement = e.target.closest('[data-editable="true"]'); if (editableElement) { e.preventDefault(); this.enableEditing(editableElement); } }); // ฟังเหตุการณ์การเปลี่ยนแปลงโหมดตัวแก้ไข this.editor.on('editor:mode-changed', (data) => { if (data.mode === 'preview' && this.isEditing) { this.disableEditing(); } }); // ฟังเหตุการณ์คำสั่งจากแถบเครื่องมือ this.editor.on('content:command', (data) => { if (this.isEditing) { this.executeCommand(data.command, data.value); } }); // ฟังเหตุการณ์การเลิกทำ/ทำซ้ำ this.editor.on('toolbar:undo', () => { this.undo(); }); this.editor.on('toolbar:redo', () => { this.redo(); }); // ฟังเหตุการณ์การเปลี่ยนแปลงเนื้อหา this.editor.on('content:changed', () => { this.startAutoSave(); }); } /** * สร้างแถบเครื่องมือข้อความลอย */ createTextToolbar() { this.textToolbar = this.editor.domUtils.createElement('div', { 'class': 'editor-text-toolbar', 'id': 'editor-text-toolbar' }); // สร้างกลุ่มแถบเครื่องมือ const formattingGroup = this.createToolbarGroup('formatting', [ {command: 'bold', icon: 'format_bold', title: 'ตัวหนา'}, {command: 'italic', icon: 'format_italic', title: 'ตัวเอียง'}, {command: 'underline', icon: 'format_underlined', title: 'ขีดเส้นใต้'}, {command: 'strikeThrough', icon: 'format_strikethrough', title: 'ขีดฆ่า'} ]); const alignmentGroup = this.createToolbarGroup('alignment', [ {command: 'justifyLeft', icon: 'format_align_left', title: 'จัดชิดซ้าย'}, {command: 'justifyCenter', icon: 'format_align_center', title: 'จัดกึ่งกลาง'}, {command: 'justifyRight', icon: 'format_align_right', title: 'จัดชิดขวา'}, {command: 'justifyFull', icon: 'format_align_justify', title: 'เต็มแถว'} ]); const listGroup = this.createToolbarGroup('list', [ {command: 'insertUnorderedList', icon: 'format_list_bulleted', title: 'รายการแบบจุด'}, {command: 'insertOrderedList', icon: 'format_list_numbered', title: 'รายการแบบลำดับ'}, {command: 'outdent', icon: 'format_indent_decrease', title: 'ลดระยะย่อหน้า'}, {command: 'indent', icon: 'format_indent_increase', title: 'เพิ่มระยะย่อหน้า'} ]); const insertGroup = this.createToolbarGroup('insert', [ {command: 'createLink', icon: 'link', title: 'แทรกลิงก์'}, {command: 'insertImage', icon: 'image', title: 'แทรกรูปภาพ'}, {command: 'insertTable', icon: 'grid_on', title: 'แทรกตาราง'} ]); const styleGroup = this.createToolbarGroup('style', [ {command: 'removeFormat', icon: 'format_clear', title: 'ล้างรูปแบบ'}, {command: 'formatBlock', icon: 'text_format', title: 'รูปแบบย่อหน้า', value: 'p'} ]); // เพิ่มกลุ่มในแถบเครื่องมือ this.textToolbar.appendChild(formattingGroup); this.textToolbar.appendChild(alignmentGroup); this.textToolbar.appendChild(listGroup); this.textToolbar.appendChild(insertGroup); this.textToolbar.appendChild(styleGroup); // เพิ่มปุ่มปิด const closeButton = this.editor.domUtils.createElement('button', { 'class': 'editor-toolbar-close', 'title': 'ปิด' }, '×'); closeButton.addEventListener('click', () => { this.disableEditing(); }); this.textToolbar.appendChild(closeButton); // เพิ่มในเอกสาร document.body.appendChild(this.textToolbar); // ซ่อนเริ่มต้น this.textToolbar.style.display = 'none'; } /** * สร้างกลุ่มแถบเครื่องมือ */ createToolbarGroup(name, buttons) { const group = this.editor.domUtils.createElement('div', { 'class': `editor-toolbar-group editor-toolbar-group-${name}` }); buttons.forEach(button => { const btn = this.editor.domUtils.createElement('button', { 'class': 'editor-toolbar-btn', 'data-command': button.command, 'title': button.title }); // เพิ่มไอคอน const icon = this.editor.domUtils.createElement('i', { 'class': 'material-icons' }, button.icon); btn.appendChild(icon); // เพิ่มค่าถ้ามี if (button.value) { btn.setAttribute('data-value', button.value); } // เพิ่มเหตุการณ์คลิก btn.addEventListener('click', (e) => { e.preventDefault(); const value = btn.getAttribute('data-value'); this.editor.emit('content:command', {command: button.command, value}); }); group.appendChild(btn); }); return group; } /** * เปิดใช้งานการแก้ไขแบบอินไลน์สำหรับอิลิเมนต์ */ enableEditing(element) { if (this.isEditing && this.currentElement === element) { return; // กำลังแก้ไขอิลิเมนต์นี้อยู่แล้ว } // ปิดการแก้ไขปัจจุบัน if (this.isEditing) { this.disableEditing(); } // บันทึกสถานะปัจจุบันในประวัติ this.saveToHistory(); // ตั้งค่าอิลิเมนต์ปัจจุบัน this.currentElement = element; this.isEditing = true; // เพิ่มคลาสการแก้ไข element.classList.add('editor-editing'); // ทำให้เนื้อหาแก้ไขได้ element.contentEditable = true; // โฟกัสที่อิลิเมนต์ element.focus(); // เลือกข้อความทั้งหมด const range = document.createRange(); range.selectNodeContents(element); const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range); // แสดงแถบเครื่องมือข้อความ this.showTextToolbar(element); // เพิ่ม event listeners สำหรับอิลิเมนต์ this.addElementEventListeners(element); // ส่งเหตุการณ์ this.editor.emit('content:editing-started', {element}); } /** * ปิดใช้งานการแก้ไขแบบอินไลน์ */ disableEditing() { if (!this.isEditing) return; // ลบคลาสการแก้ไข this.currentElement.classList.remove('editor-editing'); // ทำให้เนื้อหาไม่สามารถแก้ไขได้ this.currentElement.contentEditable = false; // ลบ event listeners this.removeElementEventListeners(this.currentElement); // ซ่อนแถบเครื่องมือข้อความ this.hideTextToolbar(); // รีเซ็ตสถานะ this.isEditing = false; this.currentElement = null; // ส่งเหตุการณ์ this.editor.emit('content:editing-ended'); } /** * เพิ่ม event listeners ให้กับอิลิเมนต์ที่แก้ไขได้ */ addElementEventListeners(element) { // เก็บการอ้างอิงถึง this สำหรับตัวจัดการเหตุการณ์ const self = this; // เหตุการณ์อินพุต element.addEventListener('input', this.elementInputHandler = function() { self.editor.emit('content:changed', {element: this}); }); // เหตุการณ์คีย์ดาวน์ element.addEventListener('keydown', this.elementKeydownHandler = function(e) { // จัดการการผสมคีย์เฉพาะ if (e.ctrlKey || e.metaKey) { switch (e.key) { case 'b': e.preventDefault(); self.executeCommand('bold'); break; case 'i': e.preventDefault(); self.executeCommand('italic'); break; case 'u': e.preventDefault(); self.executeCommand('underline'); break; case 'z': e.preventDefault(); if (e.shiftKey) { self.redo(); } else { self.undo(); } break; case 'y': e.preventDefault(); self.redo(); break; case 's': e.preventDefault(); self.editor.saveTemplate(); break; } } // จัดการปุ่ม Escape if (e.key === 'Escape') { e.preventDefault(); self.disableEditing(); } // จัดการปุ่ม Enter if (e.key === 'Enter') { // ถ้าเป็นอิลิเมนต์ที่ไม่ใช่บล็อก ให้ป้องกันการสร้างบรรทัดใหม่ if (element.tagName !== 'DIV' && element.tagName !== 'P' && element.tagName !== 'LI') { e.preventDefault(); self.disableEditing(); } } }); // เหตุการณ์คีย์อัพเพื่ออัปเดตตำแหน่งแถบเครื่องมือ element.addEventListener('keyup', this.elementKeyupHandler = function() { self.updateTextToolbarPosition(); }); // เหตุการณ์เมาส์อัพเพื่ออัปเดตตำแหน่งแถบเครื่องมือ element.addEventListener('mouseup', this.elementMouseupHandler = function() { self.updateTextToolbarPosition(); }); // เหตุการณ์คลิกเพื่อป้องกันการแพร่กระจาย element.addEventListener('click', this.elementClickHandler = function(e) { e.stopPropagation(); }); } /** * ลบ event listeners จากอิลิเมนต์ที่แก้ไขได้ */ removeElementEventListeners(element) { if (this.elementInputHandler) { element.removeEventListener('input', this.elementInputHandler); this.elementInputHandler = null; } if (this.elementKeydownHandler) { element.removeEventListener('keydown', this.elementKeydownHandler); this.elementKeydownHandler = null; } if (this.elementKeyupHandler) { element.removeEventListener('keyup', this.elementKeyupHandler); this.elementKeyupHandler = null; } if (this.elementMouseupHandler) { element.removeEventListener('mouseup', this.elementMouseupHandler); this.elementMouseupHandler = null; } if (this.elementClickHandler) { element.removeEventListener('click', this.elementClickHandler); this.elementClickHandler = null; } } /** * แสดงแถบเครื่องมือข้อความ */ showTextToolbar(element) { // อัปเดตตำแหน่งแถบเครื่องมือ this.updateTextToolbarPosition(); // แสดงแถบเครื่องมือ this.textToolbar.style.display = 'flex'; // เพิ่มคลาสที่ใช้งานสำหรับแอนิเมชัน setTimeout(() => { this.textToolbar.classList.add('editor-toolbar-active'); }, 10); } /** * ซ่อนแถบเครื่องมือข้อความ */ hideTextToolbar() { // ลบคลาสที่ใช้งาน this.textToolbar.classList.remove('editor-toolbar-active'); // ซ่อนหลังแอนิเมชัน setTimeout(() => { this.textToolbar.style.display = 'none'; }, 200); } /** * อัปเดตตำแหน่งของแถบเครื่องมือข้อความ */ updateTextToolbarPosition() { if (!this.isEditing || !this.currentElement) return; const selection = window.getSelection(); if (selection.rangeCount === 0) return; const range = selection.getRangeAt(0); const rect = range.getBoundingClientRect(); // วางแถบเครื่องมือเหนือการเลือก let top = rect.top + window.scrollY - this.textToolbar.offsetHeight - 10; let left = rect.left + window.scrollX + (rect.width / 2) - (this.textToolbar.offsetWidth / 2); // ตรวจสอบให้แน่ใจว่าแถบเครื่องมืออยู่ในวิวพอร์ต if (top < window.scrollY + 10) { top = rect.bottom + window.scrollY + 10; } if (left < window.scrollX + 10) { left = window.scrollX + 10; } if (left + this.textToolbar.offsetWidth > window.scrollX + window.innerWidth - 10) { left = window.scrollX + window.innerWidth - this.textToolbar.offsetWidth - 10; } // ใช้ตำแหน่ง this.textToolbar.style.top = `${top}px`; this.textToolbar.style.left = `${left}px`; } /** * ดำเนินการคำสั่งเนื้อหา */ executeCommand(command, value = null) { if (!this.isEditing) return; // จัดการคำสั่งพิเศษ switch (command) { case 'createLink': const url = prompt('ป้อน URL:'); if (url) { document.execCommand(command, false, url); } break; case 'insertImage': const imageUrl = prompt('ป้อน URL รูปภาพ:'); if (imageUrl) { document.execCommand(command, false, imageUrl); } break; case 'insertTable': const rows = prompt('จำนวนแถว:', '2'); const cols = prompt('จำนวนคอลัมน์:', '2'); if (rows && cols) { this.insertTable(parseInt(rows), parseInt(cols)); } break; case 'formatBlock': if (value) { document.execCommand(command, false, value); } break; default: document.execCommand(command, false, value); } // โฟกัสกลับไปที่อิลิเมนต์ this.currentElement.focus(); // ส่งเหตุการณ์คำสั่งที่ดำเนินการ this.editor.emit('content:command-executed', {command, value}); } /** * แทรกตาราง */ insertTable(rows, cols) { if (!this.isEditing) return; let tableHTML = '
| '; } tableHTML += ' |