| | |
| | | window.jvbQuill = function(form) { |
| | | const textareas = form.querySelectorAll('textarea[data-editor=true]'); |
| | | const instances = []; |
| | | |
| | | textareas.forEach(textarea => { |
| | | let container, editor, toolbar; |
| | |
| | | editor.className = 'editor'; |
| | | toolbar = document.createElement('div'); |
| | | toolbar.className = 'toolbar'; |
| | | const image = textarea.dataset.allowimage === true ? `<button type="button" class="ql-jvb_image">\n ${dashboardSettings.icons.image}\n </button>` : ''; |
| | | const image = textarea.dataset.allowimage === true ? `<button type="button" class="ql-jvb_image">\n ${window.getIcon('image')}\n </button>` : ''; |
| | | toolbar.id = `toolbar-${textarea.id}`; |
| | | toolbar.innerHTML = ` |
| | | <span class="ql-formats"> |
| | | <button type="button" class="ql-p"> |
| | | ${jvbSettings.icons.paragraph} |
| | | <i class="icon icon-paragraph"></i> |
| | | </button> |
| | | <button type="button" class="ql-h1"> |
| | | ${jvbSettings.icons.h1} |
| | | <i class="icon icon-text-h-one"></i> |
| | | </button> |
| | | <button type="button" class="ql-h2"> |
| | | ${jvbSettings.icons.h2} |
| | | <i class="icon icon-text-h-two"></i> |
| | | </button> |
| | | <button type="button" class="ql-h3"> |
| | | ${jvbSettings.icons.h3} |
| | | <i class="icon icon-text-h-three"></i> |
| | | </button> |
| | | </span> |
| | | <span class="ql-formats"> |
| | | <button type="button" class="ql-jvb_bold"> |
| | | ${jvbSettings.icons['bold']} |
| | | <i class="icon icon-text-b-fi"></i> |
| | | </button> |
| | | <button type="button" class="ql-jvb_italic"> |
| | | ${jvbSettings.icons['italic']} |
| | | <i class="icon icon-text-italic"></i> |
| | | </button> |
| | | <button type="button" class="ql-jvb_underline"> |
| | | ${jvbSettings.icons['underline']} |
| | | <i class="icon icon-text-underline"></i> |
| | | </button> |
| | | <button type="button" class="ql-jvb_strike"> |
| | | ${jvbSettings.icons['strike']} |
| | | <i class="icon icon-text-strikethrough"></i> |
| | | </button> |
| | | </span> |
| | | <span class="ql-formats"> |
| | | <button type="button" class="ql-jvb_list" value="bullet"> |
| | | ${jvbSettings.icons['list-bullets']} |
| | | <i class="icon icon-list-dashes"></i> |
| | | </button> |
| | | <button type="button" class="ql-jvb_list" value="ordered"> |
| | | ${jvbSettings.icons['list-numbers']} |
| | | <i class="icon icon-list-numbers"></i> |
| | | </button> |
| | | </span> |
| | | <span class="ql-formats"> |
| | | <button type="button" class="ql-jvb_align" value="left"> |
| | | ${jvbSettings.icons['align-left']} |
| | | <i class="icon icon-text-align-left"></i> |
| | | </button> |
| | | <button type="button" class="ql-jvb_align" value="center"> |
| | | ${jvbSettings.icons['align-center']} |
| | | <i class="icon icon-text-align-center"></i> |
| | | </button> |
| | | <button type="button" class="ql-jvb_align" value="right"> |
| | | ${jvbSettings.icons['align-right']} |
| | | <i class="icon icon-text-align-right"></i> |
| | | </button> |
| | | </span> |
| | | <span class="ql-formats"> |
| | | <button type="button" class="ql-jvb_link"> |
| | | ${jvbSettings.icons.link} |
| | | <i class="icon icon-link"></i> |
| | | </button> |
| | | ${image} |
| | | </span> |
| | |
| | | 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_bold': function() { |
| | | const format = this.quill.getFormat(); |
| | | this.quill.format('bold', !format.bold); |
| | | }, |
| | | 'jvb_italic': function() { |
| | | const format = this.quill.getFormat(); |
| | | this.quill.format('italic', !format.italic); |
| | | }, |
| | | 'jvb_strike': function() { |
| | | const format = this.quill.getFormat(); |
| | | this.quill.format('strike', !format.strike); |
| | | }, |
| | | 'jvb_underline': function() { |
| | | const format = this.quill.getFormat(); |
| | | this.quill.format('underline', !format.underline); |
| | | }, |
| | | 'jvb_align': function(value) { |
| | | this.quill.format('align', value === this.quill.getFormat().list ? false : value); |
| | | const format = this.quill.getFormat(); |
| | | this.quill.format('align', value === format.align ? false : value); |
| | | }, |
| | | 'jvb_list': function(value) { |
| | | this.quill.format('list', value === this.quill.getFormat().list ? false : value); |
| | | // value will be either "bullet" or "ordered" depending on which button was clicked |
| | | const format = this.quill.getFormat(); |
| | | this.quill.format('list', value === format.list ? false : value); |
| | | }, |
| | | 'jvb_link': function(value) { |
| | | if (value) { |
| | | const range = this.quill.getSelection(); |
| | | if (range == null || range.length === 0) return; |
| | | // Get the existing link if any |
| | | const preview = this.quill.getText(range.index, range.length); |
| | | const existingLink = this.quill.getFormat(range).link; |
| | | |
| | | // Create modal for link input |
| | |
| | | } |
| | | }, |
| | | 'jvb_image': function() { |
| | | const objectID = textarea.dataset.postId || textarea.closest('form')?.dataset.postId; |
| | | |
| | | const input = document.createElement('input'); |
| | | input.setAttribute('type', 'file'); |
| | | input.setAttribute('accept', 'image/jpeg,image/png,image/gif,image/webp'); |
| | |
| | | formData.append('post_id', objectID); |
| | | } |
| | | |
| | | // Show loading state |
| | | if (window.jvbLoading) { |
| | | window.jvbLoading.showLoading('Uploading image...', 'Processing Upload'); |
| | | } |
| | | |
| | | try { |
| | | const response = await fetch( |
| | | `${jvbSettings.api}uploads/`, |
| | | { |
| | | method: 'POST', |
| | | headers: { |
| | | 'X-WP-Nonce': jvbSettings.nonce |
| | | 'X-WP-Nonce': window.auth.getNonce() |
| | | }, |
| | | body: formData |
| | | } |
| | |
| | | this.quill.insertEmbed(range.index, 'image', result.url); |
| | | |
| | | } catch (error) { |
| | | this.handleError('Upload error:', error); |
| | | console.error('Upload error:', error); |
| | | this.quill.insertText(range.index, 'Failed to upload image. Please try again.', { |
| | | 'color': '#f00', |
| | | 'italic': true |
| | | }, true); |
| | | } finally { |
| | | if (window.jvbLoading) { |
| | | window.jvbLoading.hide(); |
| | | } |
| | | input.remove(); |
| | | } |
| | | }; |
| | |
| | | } |
| | | }); |
| | | |
| | | instances.push(quill); |
| | | |
| | | quill.on('selection-change', function(range) { |
| | | if (!range) return; |
| | | |
| | | const format = quill.getFormat(range); |
| | | |
| | | // Update button states |
| | | const formatButtons = { |
| | | 'ql-jvb_bold': 'bold', |
| | | 'ql-jvb_italic': 'italic', |
| | | 'ql-jvb_underline': 'underline', |
| | | 'ql-jvb_strike': 'strike' |
| | | }; |
| | | |
| | | Object.entries(formatButtons).forEach(([buttonClass, formatName]) => { |
| | | const button = toolbar.querySelector(`.${buttonClass}`); |
| | | if (button) { |
| | | button.classList.toggle('active', !!format[formatName]); |
| | | } |
| | | }); |
| | | |
| | | // Update list button states |
| | | toolbar.querySelectorAll('.ql-jvb_list').forEach(button => { |
| | | const value = button.getAttribute('value'); |
| | | button.classList.toggle('ql-active', format.list === value); |
| | | }); |
| | | |
| | | // Update alignment button states |
| | | toolbar.querySelectorAll('.ql-jvb_align').forEach(button => { |
| | | const value = button.getAttribute('value'); |
| | | button.classList.toggle('ql-active', format.align === value); |
| | | }); |
| | | |
| | | const alignmentTools = toolbar.querySelector('.ql-align'); |
| | | if (alignmentTools) { |
| | | if (range && range.length === 0) { |
| | | if (range.length === 0) { |
| | | // Get the focused element |
| | | const [leaf] = this.quill.getLeaf(range.index); |
| | | if (leaf && leaf.domNode && leaf.domNode.tagName === 'IMG') { |
| | |
| | | textarea.dispatchEvent(new Event('change', { bubbles: true })); |
| | | }); |
| | | }); |
| | | return instances; |
| | | }; |