window.jvbQuill = function(form) {
|
const textareas = form.querySelectorAll('textarea[data-editor=true]');
|
|
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 ? `<button type="button" class="ql-jvb_image">\n ${dashboardSettings.icons.image}\n </button>` : '';
|
toolbar.id = `toolbar-${textarea.id}`;
|
toolbar.innerHTML = `
|
<span class="ql-formats">
|
<button type="button" class="ql-p">
|
<i class="icon icon-paragraph"></i>
|
</button>
|
<button type="button" class="ql-h1">
|
<i class="icon icon-text-h-one"></i>
|
</button>
|
<button type="button" class="ql-h2">
|
<i class="icon icon-text-h-two"></i>
|
</button>
|
<button type="button" class="ql-h3">
|
<i class="icon icon-text-h-three"></i>
|
</button>
|
</span>
|
<span class="ql-formats">
|
<button type="button" class="ql-jvb_bold">
|
<i class="icon icon-text-b-fi"></i>
|
</button>
|
<button type="button" class="ql-jvb_italic">
|
<i class="icon icon-text-italic"></i>
|
</button>
|
<button type="button" class="ql-jvb_underline">
|
<i class="icon icon-text-underline"></i>
|
</button>
|
<button type="button" class="ql-jvb_strike">
|
<i class="icon icon-text-strikethrough"></i>
|
</button>
|
</span>
|
<span class="ql-formats">
|
<button type="button" class="ql-jvb_list" value="bullet">
|
<i class="icon icon-list-dashes"></i>
|
</button>
|
<button type="button" class="ql-jvb_list" value="ordered">
|
<i class="icon icon-list-numbers"></i>
|
</button>
|
</span>
|
<span class="ql-formats">
|
<button type="button" class="ql-jvb_align" value="left">
|
<i class="icon icon-text-align-left"></i>
|
</button>
|
<button type="button" class="ql-jvb_align" value="center">
|
<i class="icon icon-text-align-center"></i>
|
</button>
|
<button type="button" class="ql-jvb_align" value="right">
|
<i class="icon icon-text-align-right"></i>
|
</button>
|
</span>
|
<span class="ql-formats">
|
<button type="button" class="ql-jvb_link">
|
<i class="icon icon-link"></i>
|
</button>
|
${image}
|
</span>
|
`;
|
|
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;
|
// 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
|
const modal = document.createElement('dialog');
|
modal.className = 'quill-link-modal';
|
modal.innerHTML = `
|
<div class="quill-link-modal-content ">
|
<label for="link">Enter URL</label>
|
<input type="url" id="link" placeholder="Enter URL" value="${existingLink || ''}" />
|
<div class="buttons">
|
<button type="button" class="save">Save</button>
|
${existingLink ? '<button type="button" class="remove">Remove</button>' : ''}
|
<button type="button" class="cancel">Cancel</button>
|
</div>
|
</div>
|
`;
|
|
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);
|
}
|
|
// 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': 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 {
|
if (window.jvbLoading) {
|
window.jvbLoading.hide();
|
}
|
input.remove();
|
}
|
};
|
|
input.click();
|
}
|
}
|
},
|
history: {
|
delay: 2000,
|
maxStack: 500
|
},
|
clipboard: {
|
matchVisual: false
|
}
|
}
|
});
|
|
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 }));
|
});
|
});
|
};
|