/** * Simple WYSIWYG Editor * Lightweight rich text editor for publication content */ class WYSIWYGEditor { constructor(textareaId, options = {}) { this.textarea = document.getElementById(textareaId); this.options = { toolbar: ['bold', 'italic', 'underline', '|', 'link', 'image', '|', 'ul', 'ol', '|', 'h1', 'h2', 'h3'], ...options }; this.selectedImages = []; this.createEditor(); this.bindEvents(); this.setupImageUpload(); } createEditor() { // Create editor container this.container = document.createElement('div'); this.container.className = 'wysiwyg-container'; // Create toolbar this.toolbar = document.createElement('div'); this.toolbar.className = 'wysiwyg-toolbar'; this.createToolbarButtons(); // Create content area this.content = document.createElement('div'); this.content.className = 'wysiwyg-content'; this.content.contentEditable = true; // Create character count this.charCount = document.createElement('div'); this.charCount.className = 'wysiwyg-char-count'; // Assemble editor this.container.appendChild(this.toolbar); this.container.appendChild(this.content); this.container.appendChild(this.charCount); // Replace textarea this.textarea.parentNode.insertBefore(this.container, this.textarea); this.textarea.style.display = 'none'; // Initialize content this.content.innerHTML = this.textarea.value; this.updateCharCount(); } createToolbarButtons() { this.options.toolbar.forEach(item => { if (item === '|') { const separator = document.createElement('div'); separator.className = 'wysiwyg-separator'; this.toolbar.appendChild(separator); } else { const button = document.createElement('button'); button.className = 'wysiwyg-btn'; button.type = 'button'; button.innerHTML = this.getButtonLabel(item); button.dataset.command = item; button.addEventListener('click', () => this.execCommand(item)); this.toolbar.appendChild(button); } }); } getButtonLabel(command) { const labels = { 'bold': 'B', 'italic': 'I', 'underline': 'U', 'link': '🔗', 'image': '🖼️', 'ul': '• List', 'ol': '1. List', 'h1': 'H1', 'h2': 'H2', 'h3': 'H3' }; return labels[command] || command; } execCommand(command) { switch (command) { case 'bold': document.execCommand('bold', false, null); break; case 'italic': document.execCommand('italic', false, null); break; case 'underline': document.execCommand('underline', false, null); break; case 'link': this.insertLink(); break; case 'image': this.insertImage(); break; case 'ul': document.execCommand('insertUnorderedList', false, null); break; case 'ol': document.execCommand('insertOrderedList', false, null); break; case 'h1': this.formatHeading('h1'); break; case 'h2': this.formatHeading('h2'); break; case 'h3': this.formatHeading('h3'); break; } this.content.focus(); } insertLink() { const selection = window.getSelection(); const url = prompt('Enter URL:'); if (url) { document.execCommand('createLink', false, url); } } insertImage() { this.openImageGallery(); } openImageGallery() { this.selectedImages = []; this.loadGalleryImages(); document.getElementById('imageGallery').classList.add('open'); } closeImageGallery() { document.getElementById('imageGallery').classList.remove('open'); this.selectedImages = []; this.updateInsertButton(); } showGalleryTab(tab) { // Hide all tabs document.getElementById('galleryBrowse').style.display = 'none'; document.getElementById('galleryUpload').style.display = 'none'; // Remove active class from all tabs document.querySelectorAll('.gallery-tab').forEach(tab => { tab.classList.remove('active'); }); // Show selected tab and add active class if (tab === 'browse') { document.getElementById('galleryBrowse').style.display = 'block'; document.querySelectorAll('.gallery-tab')[0].classList.add('active'); } else if (tab === 'upload') { document.getElementById('galleryUpload').style.display = 'block'; document.querySelectorAll('.gallery-tab')[1].classList.add('active'); } } async loadGalleryImages() { try { const response = await fetch('upload_image.php'); const data = await response.json(); if (data.success) { this.renderGalleryImages(data.images); } else { console.error('Failed to load images:', data.error); } } catch (error) { console.error('Error loading images:', error); } } renderGalleryImages(images) { const grid = document.getElementById('galleryGrid'); grid.innerHTML = ''; images.forEach(image => { const item = document.createElement('div'); item.className = 'gallery-item'; item.dataset.imageId = image.id; item.dataset.imageUrl = image.url; item.dataset.imageName = image.original_name; item.innerHTML = ` ${image.original_name} `; item.addEventListener('click', () => this.selectImage(item)); grid.appendChild(item); }); } selectImage(item) { const imageId = item.dataset.imageId; if (item.classList.contains('selected')) { item.classList.remove('selected'); this.selectedImages = this.selectedImages.filter(id => id !== imageId); } else { item.classList.add('selected'); this.selectedImages.push(imageId); } this.updateInsertButton(); } updateInsertButton() { const insertBtn = document.getElementById('galleryInsertBtn'); insertBtn.disabled = this.selectedImages.length === 0; } insertSelectedImage() { if (this.selectedImages.length === 0) return; const selectedItems = document.querySelectorAll('.gallery-item.selected'); const images = []; selectedItems.forEach(item => { images.push({ url: item.dataset.imageUrl, name: item.dataset.imageName }); }); // Insert images into editor images.forEach(image => { const img = document.createElement('img'); img.src = image.url; img.alt = image.name; img.style.maxWidth = '100%'; img.style.height = 'auto'; // Insert at cursor position const selection = window.getSelection(); if (selection.rangeCount > 0) { const range = selection.getRangeAt(0); range.insertNode(img); range.collapse(false); } else { this.content.appendChild(img); } }); // Update textarea and close gallery this.textarea.value = this.content.innerHTML; this.closeImageGallery(); } setupImageUpload() { const uploadArea = document.getElementById('uploadArea'); const fileInput = document.getElementById('fileInput'); // Click to upload uploadArea.addEventListener('click', () => { fileInput.click(); }); // File selection fileInput.addEventListener('change', (e) => { this.handleFileUpload(e.target.files); }); // Drag and drop uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.classList.add('dragover'); }); uploadArea.addEventListener('dragleave', () => { uploadArea.classList.remove('dragover'); }); uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.classList.remove('dragover'); this.handleFileUpload(e.dataTransfer.files); }); } async handleFileUpload(files) { const formData = new FormData(); for (let i = 0; i < files.length; i++) { formData.append('images[]', files[i]); } try { this.showUploadProgress(); const response = await fetch('upload_image.php', { method: 'POST', body: formData }); const data = await response.json(); if (data.success) { this.showUploadSuccess('Images uploaded successfully!'); // Refresh gallery this.loadGalleryImages(); // Switch to browse tab this.showGalleryTab('browse'); } else { this.showUploadError('Upload failed: ' + data.errors.join(', ')); } } catch (error) { this.showUploadError('Upload failed: ' + error.message); } finally { this.hideUploadProgress(); } } showUploadProgress() { document.getElementById('uploadProgress').classList.add('active'); document.getElementById('uploadStatus').textContent = 'Uploading...'; document.getElementById('progressFill').style.width = '50%'; } hideUploadProgress() { document.getElementById('uploadProgress').classList.remove('active'); document.getElementById('progressFill').style.width = '0%'; } showUploadSuccess(message) { const status = document.getElementById('uploadStatus'); status.textContent = message; status.className = 'upload-status success'; setTimeout(() => { this.hideUploadProgress(); }, 3000); } showUploadError(message) { const status = document.getElementById('uploadStatus'); status.textContent = message; status.className = 'upload-status error'; setTimeout(() => { this.hideUploadProgress(); }, 5000); } formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } formatHeading(tag) { const selection = window.getSelection(); const range = selection.getRangeAt(0); const heading = document.createElement(tag); heading.textContent = range.toString(); range.deleteContents(); range.insertNode(heading); } bindEvents() { // Update textarea when content changes this.content.addEventListener('input', () => { this.textarea.value = this.content.innerHTML; this.updateCharCount(); }); // Update content when textarea changes (for form submission) this.textarea.addEventListener('input', () => { this.content.innerHTML = this.textarea.value; this.updateCharCount(); }); // Handle paste events this.content.addEventListener('paste', (e) => { e.preventDefault(); const text = e.clipboardData.getData('text/html') || e.clipboardData.getData('text/plain'); document.execCommand('insertHTML', false, text); }); // Keyboard shortcuts this.content.addEventListener('keydown', (e) => { if (e.ctrlKey || e.metaKey) { switch (e.key) { case 'b': e.preventDefault(); this.execCommand('bold'); break; case 'i': e.preventDefault(); this.execCommand('italic'); break; case 'u': e.preventDefault(); this.execCommand('underline'); break; } } }); } updateCharCount() { const text = this.content.innerText || this.content.textContent || ''; const count = text.length; this.charCount.textContent = `Characters: ${count}`; } getContent() { return this.content.innerHTML; } setContent(html) { this.content.innerHTML = html; this.textarea.value = html; this.updateCharCount(); } destroy() { this.textarea.style.display = 'block'; this.textarea.value = this.content.innerHTML; this.container.remove(); } } // Initialize editor when DOM is ready document.addEventListener('DOMContentLoaded', () => { const editor = new WYSIWYGEditor('content'); // Make editor globally accessible window.wysiwygEditor = editor; // Handle form submission const form = document.querySelector('.publication-form'); if (form) { form.addEventListener('submit', () => { // Ensure textarea has latest content document.getElementById('content').value = editor.getContent(); }); } });