window.jvbQuill = function(form) { const textareas = form.querySelectorAll('textarea[data-editor=true]'); const instances = []; textareas.forEach(textarea => { let container, editor, toolbar; //create it if it doesn't exist if(!textarea.parentNode.querySelector('.editor-container')){ container = document.createElement('div'); container.className = 'editor-container'; editor = document.createElement('div'); editor.className = 'editor'; toolbar = document.createElement('div'); toolbar.className = 'toolbar'; const image = textarea.dataset.allowimage === true ? `` : ''; toolbar.id = `toolbar-${textarea.id}`; toolbar.innerHTML = ` ${image} `; container.appendChild(toolbar); container.appendChild(editor); textarea.parentNode.insertBefore(container, textarea); textarea.style.display = 'none'; editor.innerHTML = textarea.value; }else{ container = textarea.parentNode.querySelector('.editor-container'); editor = container.querySelector('.editor'); toolbar = container.querySelector('.toolbar'); } const quill = new Quill(editor, { theme: 'snow', modules: { toolbar: { container: toolbar, handlers: { p: function() { this.quill.format('header', false); }, h1: function() { this.quill.format('header', 1); }, h2: function() { this.quill.format('header', 2); }, h3: function() { this.quill.format('header', 3); }, 'jvb_bold': function() {this.quill.format('bold', true)}, 'jvb_italic': function() {this.quill.format('italic', true)}, 'jvb_strike': function() {this.quill.format('strike', true)}, 'jvb_underline': function() {this.quill.format('underline', true)}, 'jvb_align': function(value) { this.quill.format('align', value === this.quill.getFormat().list ? false : value); }, 'jvb_list': function(value) { this.quill.format('list', value === this.quill.getFormat().list ? false : value); }, 'jvb_link': function(value) { if (value) { const range = this.quill.getSelection(); if (range == null || range.length === 0) return; const existingLink = this.quill.getFormat(range).link; // Create modal for link input const modal = document.createElement('dialog'); modal.className = 'quill-link-modal'; modal.innerHTML = ` `; document.body.appendChild(modal); modal.showModal(); const input = modal.querySelector('input'); input.focus(); // Handle save modal.querySelector('.save').addEventListener('click', () => { const url = input.value; if (url) { this.quill.format('link', url); } modal.remove(); }); // Handle remove if link exists const removeBtn = modal.querySelector('.remove'); if (removeBtn) { removeBtn.addEventListener('click', () => { this.quill.format('link', false); modal.remove(); }); } // Handle cancel modal.querySelector('.cancel').addEventListener('click', () => { modal.remove(); }); // Handle Enter key input.addEventListener('keyup', (e) => { if (e.key === 'Enter') { const url = input.value; if (url) { this.quill.format('link', url); } modal.remove(); } }); } }, 'jvb_image': function() { const input = document.createElement('input'); input.setAttribute('type', 'file'); input.setAttribute('accept', 'image/jpeg,image/png,image/gif,image/webp'); input.style.display = 'none'; document.body.appendChild(input); input.onchange = async (e) => { const file = e.target.files?.[0]; if (!file) return; // Validate file const maxSize = 5242880; // 5MB if (file.size > maxSize) { this.quill.insertText(range.index, 'File too large. Maximum size is 5MB', { 'color': '#f00', 'italic': true }, true); input.remove(); return; } const range = this.quill.getSelection(true); const formData = new FormData(); formData.append('image', file); if (objectID) { formData.append('post_id', objectID); } try { const response = await fetch( `${jvbSettings.api}uploads/`, { method: 'POST', headers: { 'X-WP-Nonce': window.auth.getNonce() }, body: formData } ); if (!response.ok) { throw new Error('Upload failed'); } const result = await response.json(); // Insert the image at cursor position this.quill.insertEmbed(range.index, 'image', result.url); } catch (error) { this.handleError('Upload error:', error); this.quill.insertText(range.index, 'Failed to upload image. Please try again.', { 'color': '#f00', 'italic': true }, true); } finally { input.remove(); } }; input.click(); } } }, history: { delay: 2000, maxStack: 500 }, clipboard: { matchVisual: false } } }); instances.push(quill); quill.on('selection-change', function(range) { const alignmentTools = toolbar.querySelector('.ql-align'); if (alignmentTools) { if (range && range.length === 0) { // Get the focused element const [leaf] = this.quill.getLeaf(range.index); if (leaf && leaf.domNode && leaf.domNode.tagName === 'IMG') { alignmentTools.style.display = 'inline-block'; return; } } alignmentTools.style.display = 'none'; } }); // Update hidden textarea and trigger form change quill.on('text-change', () => { textarea.value = quill.root.innerHTML; textarea.dispatchEvent(new Event('change', { bubbles: true })); }); }); return instances; };