Merge branch 'main' of https://github.com/jakevdwerf/jvb
| | |
| | | this.currentItemID = itemID; |
| | | |
| | | let item = this.store.get(parseInt(itemID)); |
| | | console.log('Item', item); |
| | | if (item) { |
| | | this.ui.modals.edit.dataset.itemId = itemID; |
| | | this.ui.modals.edit.dataset.content = this.content; |
| | | |
| | | let form = this.ui.modals.edit.querySelector('form'); |
| | | [ |
| | | this.ui.modals.edit.querySelector('h2').textContent |
| | | ] = [ |
| | | `Editing ${item.fields.post_title}` |
| | | ]; |
| | | this.ui.modals.edit.querySelector('h2').textContent = `Editing ${item.fields.post_title}`; |
| | | form.dataset.formId = `edit-${itemID}`; |
| | | console.log('Sending to jvbPopulate: ', item.fields); |
| | | console.log('and images: ', item.images); |
| | | new window.jvbPopulate(form, item.fields, item.images); |
| | | |
| | | new window.jvbPopulate(form, item); |
| | | |
| | | this.formController.registerForm(this.ui.forms.edit); |
| | | } |
| | | } |
| | |
| | | /********************************************** |
| | | PopulateForm extracts saved data and populates the form field accordingly |
| | | **********************************************/ |
| | | **********************************************/ |
| | | class PopulateForm { |
| | | constructor(form, fieldData = {}, imageData = {}, options = {}) { |
| | | for (let [fieldName, fieldValue] of Object.entries(fieldData)) { |
| | | constructor(form, itemDataOrFields = {}, legacyImages = {}, options = {}) { |
| | | // Support both old signature (fields, images) and new signature (item object) |
| | | this.item = this.normalizeItemData(itemDataOrFields, legacyImages); |
| | | this.form = form; |
| | | this.options = options; |
| | | |
| | | // Populate all fields |
| | | for (let [fieldName, fieldValue] of Object.entries(this.item.fields)) { |
| | | let wrapper = form.querySelector(`[data-field="${fieldName}"]`); |
| | | if (wrapper) { |
| | | this.populateField(wrapper, fieldName, fieldValue, imageData, options); |
| | | this.populateField(wrapper, fieldName, fieldValue); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Populate a single field with its value |
| | | * @param {HTMLElement} fieldWrapper - The field wrapper element |
| | | * @param {string} fieldName - Field name |
| | | * @param {*} fieldValue - Field value |
| | | * @param {Object} imagesData - Image metadata |
| | | * @param {Object} options - Additional options |
| | | * Normalize data to consistent structure |
| | | * Supports both new format (item object) and legacy format (fields, images) |
| | | */ |
| | | populateField(fieldWrapper, fieldName, fieldValue, imagesData = {}, options = {}) { |
| | | normalizeItemData(itemDataOrFields, legacyImages) { |
| | | // Check if this is the new format (has a fields property) or legacy format |
| | | if (itemDataOrFields && typeof itemDataOrFields === 'object' && 'fields' in itemDataOrFields) { |
| | | // New format - already structured |
| | | return { |
| | | fields: itemDataOrFields.fields || {}, |
| | | images: itemDataOrFields.images || {}, |
| | | taxonomies: itemDataOrFields.taxonomies || {} |
| | | }; |
| | | } else { |
| | | // Legacy format - fields and images passed separately |
| | | return { |
| | | fields: itemDataOrFields || {}, |
| | | images: legacyImages || {}, |
| | | taxonomies: {} |
| | | }; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Check if a field is a taxonomy field |
| | | */ |
| | | isTaxonomyField(fieldName) { |
| | | return Object.hasOwn(this.item.taxonomies, fieldName) && |
| | | Object.keys(this.item.taxonomies[fieldName]).length > 0; |
| | | } |
| | | |
| | | /** |
| | | * Check if a value references image data |
| | | */ |
| | | isImageField(value) { |
| | | if (!this.item.images || Object.keys(this.item.images).length === 0) { |
| | | return false; |
| | | } |
| | | |
| | | const ids = this.splitIDs(value); |
| | | return ids.some(id => Object.keys(this.item.images).includes(String(id))); |
| | | } |
| | | |
| | | /** |
| | | * Split comma-separated IDs into array of integers |
| | | */ |
| | | splitIDs(value) { |
| | | return String(value).split(',') |
| | | .map(v => parseInt(v.trim())) |
| | | .filter(v => !isNaN(v) && v > 0); |
| | | } |
| | | |
| | | /** |
| | | * Populate a single field with its value |
| | | */ |
| | | populateField(fieldWrapper, fieldName, fieldValue, options = {}) { |
| | | if (!fieldWrapper || fieldValue === undefined || fieldValue === null) { |
| | | return; |
| | | } |
| | |
| | | case 'upload': |
| | | case 'gallery': |
| | | case 'image': |
| | | this.populateUploadField(fieldWrapper, fieldName, fieldValue, imagesData); |
| | | this.populateUploadField(fieldWrapper, fieldName, fieldValue); |
| | | break; |
| | | |
| | | case 'repeater': |
| | | this.populateRepeaterField(fieldWrapper, fieldName, fieldValue, options); |
| | | this.populateRepeaterField(fieldWrapper, fieldName, fieldValue); |
| | | break; |
| | | |
| | | case 'taxonomy': |
| | |
| | | case 'number': |
| | | this.populateNumberField(fieldWrapper, fieldName, fieldValue); |
| | | break; |
| | | |
| | | case 'textarea': |
| | | if (fieldWrapper.querySelector('.editor-container')) { |
| | | this.populateEditorField(fieldWrapper, fieldName, fieldValue); |
| | |
| | | } |
| | | |
| | | /** |
| | | * Determine field type from wrapper element |
| | | * @param {HTMLElement} fieldWrapper - Field wrapper element |
| | | * @returns {string} Field type |
| | | */ |
| | | getFieldType(fieldWrapper) { |
| | | // Check dataset first (most reliable for our use case) |
| | | if (fieldWrapper.dataset.fieldType) { |
| | | return fieldWrapper.dataset.fieldType; |
| | | } |
| | | |
| | | if (fieldWrapper.dataset.type) { |
| | | return fieldWrapper.dataset.type; |
| | | } |
| | | |
| | | // Check for specific field classes |
| | | const typeClasses = [ |
| | | 'upload', 'repeater', 'taxonomy', 'user', 'location', |
| | | 'set', 'checkbox', 'select', 'radio', 'true_false', 'date', |
| | | 'time', 'datetime', 'editor', 'number', 'text', 'textarea', |
| | | 'email', 'url', 'tel', 'phone' |
| | | ]; |
| | | |
| | | for (const type of typeClasses) { |
| | | if (fieldWrapper.classList.contains(type)) { |
| | | return type; |
| | | } |
| | | } |
| | | |
| | | // Check input type |
| | | const input = fieldWrapper.querySelector('input, select, textarea'); |
| | | if (input) { |
| | | if (input.tagName === 'TEXTAREA') { |
| | | return input.dataset.editor === 'true' ? 'editor' : 'textarea'; |
| | | } |
| | | if (input.type) { |
| | | return input.type === 'checkbox' && !fieldWrapper.classList.contains('true_false') ? 'set' : input.type; |
| | | } |
| | | } |
| | | |
| | | return 'text'; |
| | | } |
| | | |
| | | /** |
| | | * Populate text-based fields |
| | | */ |
| | | populateTextField(fieldWrapper, fieldName, fieldValue) { |
| | | const input = fieldWrapper.querySelector(`[name="${fieldName}"], input, textarea`); |
| | | // Skip file inputs - browsers don't allow setting their values programmatically |
| | | if (input && input.type === 'file') { |
| | | return; |
| | | } |
| | | if (input) { |
| | | |
| | | input.value = String(fieldValue || ''); |
| | | |
| | | // Update character counter if present |
| | | if (input.dataset.limit) { |
| | | const counter = fieldWrapper.querySelector('.char-count .current'); |
| | | if (counter) { |
| | | counter.textContent = input.value.length; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Populate textarea fields |
| | | */ |
| | | populateTextareaField(fieldWrapper, fieldName, fieldValue) { |
| | | const textarea = fieldWrapper.querySelector(`textarea[name="${fieldName}"]`) || |
| | | fieldWrapper.querySelector('textarea:not([data-editor="true"])'); |
| | | |
| | | if (textarea) { |
| | | textarea.value = String(fieldValue || ''); |
| | | |
| | | // Trigger change event to update any dependencies |
| | | textarea.dispatchEvent(new Event('change', { bubbles: true })); |
| | | |
| | | // Update character counter if present |
| | | if (textarea.dataset.limit) { |
| | | const counter = fieldWrapper.querySelector('.char-count .current'); |
| | | if (counter) { |
| | | counter.textContent = textarea.value.length; |
| | | |
| | | // Check if limit is reached |
| | | const limit = parseInt(textarea.dataset.limit, 10); |
| | | if (textarea.value.length >= limit) { |
| | | fieldWrapper.classList.add('reached'); |
| | | } else { |
| | | fieldWrapper.classList.remove('reached'); |
| | | } |
| | | } |
| | | } |
| | | } else { |
| | | console.warn(`No textarea found for field ${fieldName} in wrapper:`, fieldWrapper); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Populate number fields |
| | | */ |
| | | populateNumberField(fieldWrapper, fieldName, fieldValue) { |
| | | const input = fieldWrapper.querySelector(`[name="${fieldName}"], input[type="number"]`); |
| | | if (input) { |
| | | input.value = Number(fieldValue) || 0; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Populate boolean/true_false fields |
| | | */ |
| | | populateBooleanField(fieldWrapper, fieldName, fieldValue) { |
| | | const input = fieldWrapper.querySelector(`[name="${fieldName}"], input[type="checkbox"]`); |
| | | if (input) { |
| | | input.checked = Boolean(fieldValue); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Populate select/radio fields |
| | | */ |
| | | populateSelectField(fieldWrapper, fieldName, fieldValue) { |
| | | const value = String(fieldValue || ''); |
| | | |
| | | // Try select first |
| | | const select = fieldWrapper.querySelector(`select[name="${fieldName}"]`); |
| | | if (select) { |
| | | select.value = value; |
| | | return; |
| | | } |
| | | |
| | | // Try radio buttons |
| | | const radio = fieldWrapper.querySelector(`input[type="radio"][name="${fieldName}"][value="${value}"]`); |
| | | if (radio) { |
| | | radio.checked = true; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Populate set/checkbox fields (multiple selections) |
| | | */ |
| | | populateSetField(fieldWrapper, fieldName, fieldValue) { |
| | | // Parse value if it's a string |
| | | let values = fieldValue; |
| | | if (typeof fieldValue === 'string') { |
| | | try { |
| | | values = JSON.parse(fieldValue); |
| | | } catch (e) { |
| | | values = fieldValue.split(',').map(v => v.trim()); |
| | | } |
| | | } |
| | | |
| | | if (!Array.isArray(values)) { |
| | | values = [String(values)]; |
| | | } |
| | | |
| | | // Update checkboxes |
| | | fieldWrapper.querySelectorAll(`input[type="checkbox"][name*="${fieldName}"]`).forEach(checkbox => { |
| | | checkbox.checked = values.includes(checkbox.value); |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * Populate date/time fields |
| | | */ |
| | | populateDateField(fieldWrapper, fieldName, fieldValue) { |
| | | const input = fieldWrapper.querySelector(`[name="${fieldName}"], input`); |
| | | if (input && fieldValue) { |
| | | // Handle different date formats |
| | | let dateValue = fieldValue; |
| | | if (typeof fieldValue === 'object' && fieldValue.date) { |
| | | dateValue = fieldValue.date; |
| | | } |
| | | |
| | | // Convert to appropriate format for input type |
| | | try { |
| | | const date = new Date(dateValue); |
| | | if (!isNaN(date.getTime())) { |
| | | switch (input.type) { |
| | | case 'date': |
| | | input.value = date.toISOString().split('T')[0]; |
| | | break; |
| | | case 'time': |
| | | input.value = date.toTimeString().slice(0, 5); |
| | | break; |
| | | case 'datetime-local': |
| | | input.value = date.toISOString().slice(0, 16); |
| | | break; |
| | | default: |
| | | input.value = dateValue; |
| | | } |
| | | } |
| | | } catch (e) { |
| | | input.value = dateValue; |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Populate editor fields (Quill) |
| | | */ |
| | | populateEditorField(fieldWrapper, fieldName, fieldValue) { |
| | | const textarea = fieldWrapper.querySelector(`textarea[name="${fieldName}"]`) || |
| | | fieldWrapper.querySelector('textarea[data-editor="true"]') || |
| | | fieldWrapper.querySelector('textarea'); |
| | | |
| | | if (!textarea) { |
| | | console.warn(`Editor field ${fieldName}: textarea not found`); |
| | | return; |
| | | } |
| | | |
| | | const content = String(fieldValue || ''); |
| | | |
| | | // Update the textarea value |
| | | textarea.value = content; |
| | | |
| | | // Try to find and update Quill editor |
| | | const editorContainer = fieldWrapper.querySelector('.editor'); |
| | | if (editorContainer) { |
| | | // Try different ways to access the Quill instance |
| | | let quillInstance = null; |
| | | |
| | | // Method 1: Check if Quill is stored on the editor element |
| | | if (editorContainer.__quill) { |
| | | quillInstance = editorContainer.__quill; |
| | | } |
| | | // Method 2: Check if Quill is stored as quill property |
| | | else if (editorContainer.quill) { |
| | | quillInstance = editorContainer.quill; |
| | | } |
| | | // Method 3: Try to find Quill in the global registry (if you have one) |
| | | else if (window.Quill && window.Quill.find) { |
| | | quillInstance = window.Quill.find(editorContainer); |
| | | } |
| | | // Method 4: Check all Quill instances if available |
| | | else if (window.Quill && window.Quill.instances) { |
| | | // Some setups store instances in a registry |
| | | for (let instance of window.Quill.instances) { |
| | | if (instance.container === editorContainer) { |
| | | quillInstance = instance; |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (quillInstance) { |
| | | // Set the content in Quill |
| | | quillInstance.root.innerHTML = content; |
| | | // Store the instance reference for future use |
| | | editorContainer.__quill = quillInstance; |
| | | } else { |
| | | console.warn(`Quill instance not found for ${fieldName}, setting HTML directly`); |
| | | // Fallback: set HTML directly |
| | | editorContainer.innerHTML = content; |
| | | } |
| | | } else { |
| | | console.warn(`Editor container not found for ${fieldName}`); |
| | | } |
| | | |
| | | // Trigger change event on textarea |
| | | textarea.dispatchEvent(new Event('change', { bubbles: true })); |
| | | } |
| | | |
| | | /** |
| | | * Populate location fields |
| | | */ |
| | | populateLocationField(fieldWrapper, fieldName, fieldValue) { |
| | | if (!fieldValue || typeof fieldValue !== 'object') { |
| | | return; |
| | | } |
| | | |
| | | // Location fields typically have sub-fields |
| | | const subFields = ['address', 'lat', 'lng', 'street', 'city', 'province', 'postal_code', 'country']; |
| | | |
| | | subFields.forEach(subField => { |
| | | if (fieldValue[subField] !== undefined) { |
| | | const input = fieldWrapper.querySelector(`[name="${fieldName}_${subField}"], [name="${subField}"]`); |
| | | if (input) { |
| | | input.value = String(fieldValue[subField] || ''); |
| | | } |
| | | } |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * Populate taxonomy fields |
| | | * Populate taxonomy fields with visual display |
| | | */ |
| | | populateTaxonomyField(fieldWrapper, fieldName, fieldValue) { |
| | | // Handle different value formats |
| | |
| | | } else if (typeof fieldValue === 'string') { |
| | | try { |
| | | const parsed = JSON.parse(fieldValue); |
| | | termIds = Array.isArray(parsed) ? |
| | | parsed.map(v => String(v)) : [String(parsed)]; |
| | | termIds = Array.isArray(parsed) ? parsed.map(v => String(v)) : [String(parsed)]; |
| | | } catch (e) { |
| | | termIds = fieldValue.split(',').map(v => v.trim()); |
| | | termIds = fieldValue.split(',').map(v => v.trim()).filter(v => v); |
| | | } |
| | | } else if (fieldValue) { |
| | | termIds = [String(fieldValue)]; |
| | |
| | | // Trigger TaxonomySelector to update visual display |
| | | const toggle = fieldWrapper.querySelector('.taxonomy-toggle'); |
| | | if (toggle && toggle.dataset.fieldId && window.jvbTaxonomy) { |
| | | window.jvbTaxonomy.updateFieldFromInput(toggle.dataset.fieldId); |
| | | // Use requestAnimationFrame to ensure DOM is ready |
| | | requestAnimationFrame(() => { |
| | | window.jvbTaxonomy.updateFieldFromInput(toggle.dataset.fieldId); |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Populate user fields (similar to taxonomy) |
| | | */ |
| | | populateUserField(fieldWrapper, fieldName, fieldValue) { |
| | | // Similar logic to taxonomy fields |
| | | this.populateTaxonomyField(fieldWrapper, fieldName, fieldValue); |
| | | } |
| | | |
| | | /** |
| | | * Populate upload fields (images, videos, files) |
| | | */ |
| | | populateUploadField(fieldWrapper, fieldName, fieldValue, imagesData = {}) { |
| | | populateUploadField(fieldWrapper, fieldName, fieldValue) { |
| | | // Check if this is a timeline gallery |
| | | const isTimeline = fieldWrapper.dataset.subtype === 'timeline' || fieldName === 'timeline'; |
| | | |
| | | if (isTimeline) { |
| | | this.populateTimelineGallery(fieldWrapper, fieldName, fieldValue, imagesData); |
| | | this.populateTimelineGallery(fieldWrapper, fieldName, fieldValue); |
| | | return; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | // Handle comma-separated IDs or single ID |
| | | const itemIds = String(fieldValue).split(',').filter(id => parseInt(id.trim())); |
| | | const itemIds = this.splitIDs(fieldValue); |
| | | if (itemIds.length === 0) { |
| | | return; |
| | | } |
| | |
| | | if (grid) { |
| | | itemIds.forEach(itemId => { |
| | | const template = window.getTemplate('uploadItem'); |
| | | |
| | | if (!template) { |
| | | console.warn('uploadItem template not found'); |
| | | return; |
| | | } |
| | | |
| | | let input = template.querySelector('input[name="select-item"]'); |
| | | let label = template.querySelector('label[for="select-item"]'); |
| | | |
| | | template.dataset.id = itemId; |
| | | input.name = input.name + `-${itemId}`; |
| | | input.id = input.name; |
| | | label.htmlFor = input.name; |
| | | |
| | | const img = template.querySelector('img'); |
| | | template.querySelector('video')?.remove(); |
| | | const details = template.querySelector('details'); |
| | | |
| | | // Populate with data |
| | | if (imagesData[itemId]) { |
| | | const data = imagesData[itemId]; |
| | | if (img) { |
| | | img.src = data.medium || data.small || data.large || ''; |
| | | img.alt = data['image-alt-text'] || data.alt || ''; |
| | | } |
| | | |
| | | // Populate metadata fields |
| | | const titleInput = template.querySelector('[name="image-title"]'); |
| | | const altInput = template.querySelector('[name="image-alt-text"]'); |
| | | const captionInput = template.querySelector('[name="image-caption"]'); |
| | | |
| | | if (titleInput) titleInput.value = data['image-title'] || data.title || ''; |
| | | if (altInput) altInput.value = data['image-alt-text'] || data.alt || ''; |
| | | if (captionInput) captionInput.value = data['image-caption'] || data.caption || ''; |
| | | } else { |
| | | console.warn('No image data found for ID:', itemId, 'Available data:', Object.keys(imagesData)); |
| | | } |
| | | |
| | | // Remove hint if present |
| | | details?.querySelector('.upload-meta > .hint')?.remove(); |
| | | |
| | | this.populateUploadItem(template, itemId); |
| | | grid.append(template); |
| | | }); |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | populateTimelineGallery(fieldWrapper, fieldName, fieldValue, imagesData) { |
| | | console.log('populating Timeline Gallery'); |
| | | if (!fieldValue || typeof fieldValue !== 'object') { |
| | | /** |
| | | * Populate a single upload item |
| | | */ |
| | | populateUploadItem(template, itemId) { |
| | | let input = template.querySelector('input[name="select-item"]'); |
| | | let label = template.querySelector('label[for="select-item"]'); |
| | | |
| | | template.dataset.id = itemId; |
| | | input.name = `select-item-${itemId}`; |
| | | input.id = input.name; |
| | | label.htmlFor = input.name; |
| | | |
| | | const img = template.querySelector('img'); |
| | | template.querySelector('video')?.remove(); |
| | | |
| | | // Populate with data from item.images |
| | | if (this.item.images[itemId]) { |
| | | const data = this.item.images[itemId]; |
| | | if (img) { |
| | | img.src = data.medium || data.small || data.large || ''; |
| | | img.alt = data['image-alt-text'] || data.alt || ''; |
| | | } |
| | | |
| | | // Populate metadata fields |
| | | const titleInput = template.querySelector('[name="image-title"]'); |
| | | const altInput = template.querySelector('[name="image-alt-text"]'); |
| | | const captionInput = template.querySelector('[name="image-caption"]'); |
| | | |
| | | if (titleInput) titleInput.value = data['image-title'] || data.title || ''; |
| | | if (altInput) altInput.value = data['image-alt-text'] || data.alt || ''; |
| | | if (captionInput) captionInput.value = data['image-caption'] || data.caption || ''; |
| | | } else { |
| | | console.warn(`No image data found for ID: ${itemId}`); |
| | | } |
| | | |
| | | // Remove hint if present |
| | | template.querySelector('details .upload-meta > .hint')?.remove(); |
| | | } |
| | | |
| | | /** |
| | | * Populate timeline gallery - FIXED iteration |
| | | */ |
| | | populateTimelineGallery(fieldWrapper, fieldName, fieldValue) { |
| | | console.log('Populating Timeline Gallery', fieldValue); |
| | | |
| | | if (!fieldValue || !Array.isArray(fieldValue)) { |
| | | console.warn('Timeline field value must be an array'); |
| | | return; |
| | | } |
| | | |
| | | const imageIds = Object.values(fieldValue); |
| | | if (imageIds.length === 0) { |
| | | if (fieldValue.length === 0) { |
| | | return; |
| | | } |
| | | |
| | | // Update display |
| | | const grid = fieldWrapper.querySelector('.item-grid'); |
| | | const uploadContainer = fieldWrapper.querySelector('.file-upload-container'); |
| | | fieldWrapper.querySelector('.progress')?.remove(); |
| | | |
| | | // Clear existing items |
| | | if (grid) { |
| | | window.removeChildren(grid); |
| | | console.log(imageIds); |
| | | for (let data of Object.entries(fieldValue)) { |
| | | let imageId = data['post_thumbnail']; |
| | | const template = window.getTemplate('timelineItem'); |
| | | if (!template) return; |
| | | } |
| | | |
| | | const img = template.querySelector('img'); |
| | | fieldWrapper.querySelector('.progress')?.remove(); |
| | | |
| | | // Remove unnecessary elements |
| | | template.querySelector('video')?.remove(); |
| | | template.querySelector('.select-item span')?.remove(); |
| | | if (!grid) return; |
| | | |
| | | let input = template.querySelector('input[name="select-item"]'); |
| | | let label = template.querySelector('label[for="select-item"]'); |
| | | template.dataset.id = imageId; |
| | | template.dataset.postId = data.id; |
| | | input.name = input.name + `-${imageId}`; |
| | | // FIX: Iterate directly over array, not Object.entries |
| | | for (let itemData of fieldValue) { |
| | | const template = window.getTemplate('timelineItem'); |
| | | if (!template) { |
| | | console.warn('timelineItem template not found'); |
| | | continue; |
| | | } |
| | | |
| | | const imageId = itemData.post_thumbnail; |
| | | const postId = itemData.id; |
| | | |
| | | // Set template data attributes |
| | | template.dataset.id = imageId; |
| | | template.dataset.postId = postId; |
| | | |
| | | // Update selection controls |
| | | let input = template.querySelector('input[name="select-item"]'); |
| | | let label = template.querySelector('label[for="select-item"]'); |
| | | if (input && label) { |
| | | input.name = `select-item-${imageId}`; |
| | | input.id = input.name; |
| | | label.htmlFor = input.name; |
| | | } |
| | | |
| | | const imgData = imagesData[imageId]; |
| | | // Remove unnecessary elements |
| | | template.querySelector('video')?.remove(); |
| | | template.querySelector('.select-item span')?.remove(); |
| | | |
| | | // Set image source |
| | | if (img && imgData) { |
| | | img.src = imgData.medium || imgData.small || imgData.large || ''; |
| | | img.title = imgData['image-title']; |
| | | // Populate main image |
| | | const img = template.querySelector('img'); |
| | | const imgData = this.item.images[imageId]; |
| | | if (img && imgData) { |
| | | img.src = imgData.medium || imgData.small || imgData.large || ''; |
| | | img.title = imgData['image-title'] || ''; |
| | | img.alt = imgData['image-alt-text'] || ''; |
| | | } |
| | | |
| | | // Populate all fields within the template |
| | | const fields = template.querySelectorAll('.field'); |
| | | fields.forEach(field => { |
| | | if (field.classList.contains('group')) { |
| | | return; |
| | | } |
| | | |
| | | // Merge data |
| | | const mergedData = { ...data, ...imgData }; |
| | | const input = field.querySelector('input:not([type="file"]), textarea'); |
| | | if (!input) return; |
| | | |
| | | // Populate fields |
| | | const fields = template.querySelectorAll('.field'); |
| | | fields.forEach(field => { |
| | | if (field.classList.contains('group')) { |
| | | return; |
| | | } |
| | | const label = field.querySelector('label'); |
| | | const fieldName = input.name.replace('upload_data::', '').replace(/^\[.*?\]/, ''); |
| | | |
| | | const input = field.querySelector('input:not([type="file"]), textarea'); |
| | | if (!input) return; |
| | | |
| | | const label = field.querySelector('label'); |
| | | const name = input.name.replace('upload_data::', ''); |
| | | const value = mergedData[name]; |
| | | |
| | | // Populate the field |
| | | this.populateField(field, name, value, imagesData); |
| | | |
| | | // Update field identifiers |
| | | const id = data.id; |
| | | const newName = `[${id}]${name}`; |
| | | input.name = newName; |
| | | input.id = newName; |
| | | if (label) label.htmlFor = newName; |
| | | }); |
| | | |
| | | grid.append(template); |
| | | } |
| | | |
| | | // Hide upload container if items exist |
| | | if (imageIds.length > 0 && uploadContainer) { |
| | | uploadContainer.hidden = true; |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Populate repeater fields |
| | | */ |
| | | populateRepeaterField(fieldWrapper, fieldName, fieldValue) { |
| | | if (!fieldValue || !Array.isArray(fieldValue)) { |
| | | return; |
| | | } |
| | | |
| | | const container = fieldWrapper.querySelector('.repeater-items'); |
| | | const template = fieldWrapper.querySelector('template'); |
| | | |
| | | if (!container || !template) { |
| | | console.warn(`Repeater field ${fieldName}: missing container or template`); |
| | | return; |
| | | } |
| | | |
| | | // Clear existing rows |
| | | window.removeChildren(container); |
| | | |
| | | // Create rows for each data item |
| | | fieldValue.forEach((rowData, index) => { |
| | | if (!rowData || typeof rowData !== 'object') { |
| | | return; |
| | | } |
| | | |
| | | const row = window.getTemplate(template.className); |
| | | if (!row) { |
| | | console.warn(`Repeater field ${fieldName}: template not found`); |
| | | return; |
| | | } |
| | | |
| | | // Set row ID and update row number |
| | | row.id = `${fieldWrapper.closest('form').id}-${fieldName}-row-${index}`; |
| | | row.dataset.index = index; |
| | | |
| | | const rowNumber = row.querySelector('.row-number'); |
| | | if (rowNumber) { |
| | | rowNumber.textContent = `#${index + 1}`; |
| | | } |
| | | |
| | | // Update field names and populate values |
| | | row.querySelectorAll('input, select, textarea').forEach(field => { |
| | | const originalName = field.name; |
| | | const newName = `${fieldName}:${index}:${originalName}`; |
| | | const newId = `${fieldName}-${index}-${originalName}-${field.value}`; |
| | | |
| | | // Update field identifiers |
| | | field.name = newName; |
| | | field.id = newId; |
| | | |
| | | // Update label |
| | | const label = field.nextElementSibling; |
| | | if (label && label.tagName === 'LABEL') { |
| | | label.htmlFor = newId; |
| | | // Get value from itemData or imgData |
| | | let value = itemData[fieldName]; |
| | | if (value === undefined && imgData) { |
| | | value = imgData[fieldName]; |
| | | } |
| | | |
| | | // Populate field value |
| | | if (rowData[originalName] !== undefined) { |
| | | this.populateRepeaterFieldValue(field, originalName, rowData[originalName]); |
| | | // Populate the field using our standard method |
| | | if (value !== undefined && value !== null) { |
| | | this.populateField(field, fieldName, value); |
| | | } |
| | | |
| | | // Update field identifiers to include post ID |
| | | const newName = `[${postId}]${fieldName}`; |
| | | const newId = newName; |
| | | input.name = newName; |
| | | input.id = newId; |
| | | if (label) label.htmlFor = newId; |
| | | }); |
| | | |
| | | container.appendChild(row); |
| | | }); |
| | | } |
| | | grid.append(template); |
| | | } |
| | | |
| | | /** |
| | | * Populate individual repeater field value |
| | | */ |
| | | populateRepeaterFieldValue(field, fieldName, fieldValue) { |
| | | switch (field.type) { |
| | | case 'checkbox': |
| | | field.checked = Boolean(fieldValue); |
| | | break; |
| | | case 'radio': |
| | | field.checked = field.value === String(fieldValue); |
| | | break; |
| | | case 'select-one': |
| | | case 'select-multiple': |
| | | field.value = String(fieldValue || ''); |
| | | break; |
| | | default: |
| | | field.value = String(fieldValue || ''); |
| | | // Hide upload container if items exist |
| | | if (fieldValue.length > 0 && uploadContainer) { |
| | | uploadContainer.hidden = true; |
| | | } |
| | | } |
| | | |
| | | // ... rest of the methods stay the same (populateTextField, populateTextareaField, etc.) |
| | | // I'll include the key ones that might need updating |
| | | |
| | | populateTextField(fieldWrapper, fieldName, fieldValue) { |
| | | const input = fieldWrapper.querySelector(`[name="${fieldName}"], input, textarea`); |
| | | if (input && input.type !== 'file') { |
| | | input.value = String(fieldValue || ''); |
| | | |
| | | if (input.dataset.limit) { |
| | | const counter = fieldWrapper.querySelector('.char-count .current'); |
| | | if (counter) { |
| | | counter.textContent = input.value.length; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | populateTextareaField(fieldWrapper, fieldName, fieldValue) { |
| | | const textarea = fieldWrapper.querySelector(`textarea[name="${fieldName}"]`) || |
| | | fieldWrapper.querySelector('textarea:not([data-editor="true"])'); |
| | | |
| | | if (textarea) { |
| | | textarea.value = String(fieldValue || ''); |
| | | textarea.dispatchEvent(new Event('change', { bubbles: true })); |
| | | |
| | | if (textarea.dataset.limit) { |
| | | const counter = fieldWrapper.querySelector('.char-count .current'); |
| | | if (counter) { |
| | | counter.textContent = textarea.value.length; |
| | | const limit = parseInt(textarea.dataset.limit, 10); |
| | | fieldWrapper.classList.toggle('reached', textarea.value.length >= limit); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | populateEditorField(fieldWrapper, fieldName, fieldValue) { |
| | | const textarea = fieldWrapper.querySelector(`textarea[name="${fieldName}"][data-editor="true"]`); |
| | | if (!textarea) return; |
| | | |
| | | textarea.value = String(fieldValue || ''); |
| | | const editorContainer = fieldWrapper.querySelector('.editor'); |
| | | const content = fieldValue || '<p><br></p>'; |
| | | |
| | | if (editorContainer) { |
| | | let quillInstance = editorContainer.__quill; |
| | | |
| | | if (!quillInstance && window.Quill) { |
| | | for (let instance of (window.Quill.instances || [])) { |
| | | if (instance.container === editorContainer) { |
| | | quillInstance = instance; |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (quillInstance) { |
| | | quillInstance.root.innerHTML = content; |
| | | editorContainer.__quill = quillInstance; |
| | | } else { |
| | | editorContainer.innerHTML = content; |
| | | } |
| | | } |
| | | |
| | | textarea.dispatchEvent(new Event('change', { bubbles: true })); |
| | | } |
| | | |
| | | getFieldType(fieldWrapper) { |
| | | if (fieldWrapper.dataset.fieldType) return fieldWrapper.dataset.fieldType; |
| | | if (fieldWrapper.dataset.type) return fieldWrapper.dataset.type; |
| | | |
| | | const typeClasses = [ |
| | | 'upload', 'repeater', 'taxonomy', 'user', 'location', |
| | | 'set', 'checkbox', 'select', 'radio', 'true_false', 'date', |
| | | 'time', 'datetime', 'editor', 'number', 'text', 'textarea', |
| | | 'email', 'url', 'tel', 'phone' |
| | | ]; |
| | | |
| | | for (const type of typeClasses) { |
| | | if (fieldWrapper.classList.contains(type)) { |
| | | return type; |
| | | } |
| | | } |
| | | |
| | | const input = fieldWrapper.querySelector('input, select, textarea'); |
| | | if (input) { |
| | | if (input.tagName === 'TEXTAREA') { |
| | | return input.dataset.editor === 'true' ? 'editor' : 'textarea'; |
| | | } |
| | | if (input.type) { |
| | | return input.type === 'checkbox' && !fieldWrapper.classList.contains('true_false') ? 'set' : input.type; |
| | | } |
| | | } |
| | | |
| | | return 'text'; |
| | | } |
| | | |
| | | // Include all other existing methods... |
| | | } |
| | | |
| | | // Make available globally |
| | | window.jvbPopulate = PopulateForm; |
| | |
| | | (()=>{class e{constructor(e){if(this.queue=window.jvbQueue,this.config=e,this.content=e.content||!1,this.settings=window.jvbUserSettings,this.a11y=window.jvbA11y,!this.content)return;this.isTimeline=!1,this.currentItemID=null,this.initElements(),this.updateBulkOptions();const t=window.jvbStore.register(this.content,{storeName:this.content,keyPath:"id",endpoint:"content",headers:{action_nonce:window.auth.getNonce("dash")},indexes:[{name:"id",keyPath:"id"},{name:"status",keyPath:"status"},{name:"date",keyPath:"date"},{name:"modified",keyPath:"modified"},{name:"title",keyPath:"title"}],filters:{content:this.content,user:window.auth.getUser(),page:1,status:"all",orderby:"modified",order:"desc"},TTL:18e5,showLoading:!0});this.store=t[this.content],this.status="all",this.filterTimeout=null,this.viewController=new window.jvbViews(this.ui.container,this.store),this.tableForm=null,this.tableChanges=new Map,this.formController=this.isTimeline?new window.jvbForm({collectFormData:()=>this.collectTimelineData.bind(this)}):new window.jvbForm,this.viewController.subscribe(((e,t)=>{if("table-view"!==e||this.tableForm){if("not-table-view"===e)this.tableForm;else if("order-changed"===e){let e=this.store.get(t);if(!e)return;let s={};s[t]=e,this.savePosts(s,"Updating progression order")}}else this.tableForm||(this.tableForm=this.formController.registerForm(t,{autosave:!1,formStatus:!1,isTable:!0}))})),this.formController.subscribe(((e,t)=>{switch(e){case"form-submit":case"form-autosave":this.handleFormChange(e,t)}})),this.queue.subscribe(((e,t)=>{Object.hasOwn(t,"endpoint")&&["content","uploads/groups"].includes(t.endpoint)&&("operation-completed"===e?this.handleQueueSuccess(e,t):"operation-failed-permanent"===e&&this.handleQueueFailure(e,t))})),this.initialized=!1,this.init()}handleFormChange(e,t){let s=t.fullData.post_title,i=Object.hasOwn(t,"changes")?t.changes:t.fullData,l={};if(this.isTimeline)return l[this.currentItemID]=i,void this.savePosts(l,s);let o=[];switch(!0){case t.config.element===this.ui.forms.edit:l[this.currentItemID]=i,s=`Saving ${s} Changes`,i.post_status&&this.shouldRemoveItem(i.post_status)&&o.push(this.currentItemID);break;case t.config.element===this.ui.forms.bulkEdit:let a=t.config.element.querySelectorAll(".selected input:checked");a.forEach((e=>{l[e.value]=i,i.post_status&&this.shouldRemoveItem(i.post_status)&&o.push(e.value)})),s=`Updating ${a.length} ${this.config.plural??"posts"} Changes`;break;case t.config.element===this.ui.forms.create:"form-submit"===e&&(l[t.config.data["form-id"]]=i,s=`Saving ${s} Changes`)}if(o.length>0){let e=0;o.forEach((t=>{setTimeout((()=>{const e=document.querySelector(`.item[data-id="${t}"]`);e&&window.fade(e,!1)}),e),e+=50})),t.config.element===this.ui.forms.bulkEdit&&setTimeout((()=>{this.viewController.clearSelection()}),e+100)}0!==Object.keys(l).length&&this.savePosts(l,s)}shouldRemoveItem(e){return"all"===this.status&&!["publish","draft"].includes(e)||e!==this.status}savePosts(e,t){if(0===Object.keys(e).length)return;for(let t in e)e[t].content||(e[t].content=this.content);let s={endpoint:"content",headers:{action_nonce:window.auth.getNonce("dash")},data:{posts:e},popup:"Saving changes",title:t};this.queue.addToQueue(s)}async handleQueueSuccess(e,t){this.store.clearCache(),this.store.fetch()}handleQueueFailure(e,t){console.error("Operation failed permanently:",t),this.a11y?.announce(`Operation failed: ${t.error_message||"Unknown error"}`)}initElements(){this.elements={modals:{create:"dialog.create",edit:"dialog.edit",bulkEdit:"dialog.bulkEdit"},container:".crud[data-content]",grid:".item-grid",bulkSelectActions:".bulk-action-select",forms:{create:"dialog.create form",edit:"dialog.edit form",bulkEdit:"dialog.bulkEdit form"},uploader:"details.uploader"},this.ui=window.uiFromSelectors(this.elements),this.ui.uploader&&(window.jvbUploads.scanFields(document.querySelector(this.elements.uploader)),window.jvbUploads.subscribe(((e,t)=>{"sent-to-queue"===e&&(console.log(t),t===this.ui.uploader.querySelector("[data-uploader]")?.dataset.uploader&&window.debouncer.schedule("crud-complete",(()=>{this.store.clearHttpHeaders()})))}))),this.isTimeline=!!document.querySelector("[data-timeline]")}init(){this.ui.uploader&&(this.settings.addSetting(this.ui.uploader,"open"),this.ui.uploader.addEventListener("toggle",(e=>{this.settings.saveSetting("open",this.ui.uploader.open?"on":"off")}))),this.filterHandler=this.handleFilterChange.bind(this),this.changeHandler=this.handleChange.bind(this),this.modals={};for(let[e,t]of Object.entries(this.ui.modals))this.modals[e]=new window.jvbModal(t),this.modals[e].subscribe(((t,s)=>{if("modal-close"===t)this.currentItemID=null,this.formController.cleanupForm(this.modals[e].modal.querySelector("form").dataset.formId)}));this.setupEventDelegation(),this.setupFilters(),this.initialized=!0}setupEventDelegation(){document.addEventListener("change",this.changeHandler),document.addEventListener("click",(e=>{const t=e.target.closest("[data-action]");if(t){e.preventDefault();const s=t.dataset.action,i=t.dataset.id;switch(s){case"edit":this.populateEditForm(i),this.modals.edit.handleOpen();break;case"delete":if(confirm("Delete this item?")){let e={};e[t.dataset.id]={post_status:"delete",content:this.content},window.fade(t.closest(".item"),!1),this.savePosts(e,`Sending ${this.singular} to trash...`),this.store.delete(i)}break;case"trash":let e={};e[t.dataset.id]={post_status:"trash",content:this.content},window.fade(t.closest(".item"),!1),this.savePosts(e,`Sending ${this.singular} to trash...`);break;case"create":this.modals.create.dataset.itemId="new",this.modals.create.dataset.content=this.content,this.modals.create.handleOpen();break;case"bulk-edit":Array.from(this.viewController.selectedItems).length>0&&this.modals.bulkEdit.handleOpen();break;case"bulk-delete":const s=Array.from(this.viewController.selectedItems);s.length>0&&confirm(`Delete ${s.length} items?`)&&(s.forEach((e=>this.store.delete(e))),this.viewController.clearSelection());break;case"sync":break;case"refresh":this.store.fetch()}}e.target.closest(".create-item")&&(this.formController.registerForm(this.ui.forms.create),this.modals.create.handleOpen()),e.target.closest(".cancel-bulk")&&this.viewController.selectAll(!1)})),document.addEventListener("keydown",(e=>{(e.ctrlKey||e.metaKey)&&"a"===e.key&&this.ui.container&&this.ui.container.contains(document.activeElement)&&(e.preventDefault(),this.viewController.selectAll()),"Escape"===e.key&&this.viewController?.selectedItems.size>0&&0===window.jvbModal.getAllModals().length&&this.viewController.clearSelection()}))}handleChange(e){if(e.target.closest("[data-id]"))this.isTimeline?this.handleTimelineTableChange(e):this.handleTableChange(e);else{if(e.target.classList.contains("bulk-action-select")){if(e.target.value.startsWith("tax-")){const t=e.target.value.replace("tax-","");return this.openTaxonomyModal(t),void(e.target.value="")}switch(e.target.value){case"edit":this.populateBulkEdit(),this.modals.bulkEdit.handleOpen();break;case"publish":this.setBulkStatus("publish");break;case"draft":case"restore":this.setBulkStatus("draft");break;case"trash":this.setBulkStatus("trash");break;case"delete":this.setBulkStatus("delete")}}window.targetCheck(e,"select[data-filter]")&&this.handleFilterChange(e)}}handleTableChange(e){const t=e.target.closest("tr[data-id]");if(!t)return;const s=e.target,i=parseInt(t.dataset.id),l=s.closest(["data-field"])?.dataset.field;if(!l)return;const o=this.store.get(i);if(!o)return;o.fields[l]=this.getInputValue(s),this.store.save(o);let a={};a[i]=o.fields,this.savePosts(a,`Saving changes to ${this.content}`)}handleTimelineTableChange(e){const t=e.target.closest("tbody[data-id]");if(!t)return;const s=e.target,i=s.closest("[data-field]")?.dataset.field;if(!i)return;const l=parseInt(t.dataset.id),o=s.closest("tr.timeline-point"),a=this.store.get(l);if(!a)return;const n=this.getInputValue(s);if(o){const e=o.dataset.imageId;a.fields.timeline||(a.fields.timeline={}),a.fields.timeline[e]||(a.fields.timeline[e]={}),a.fields.timeline[e][i]=n}else a.fields[i]=n;this.store.save(a);let r={};r[l]=a.fields,this.savePosts(r,"Updating progress post")}getInputValue(e){return"checkbox"===e.type?e.checked?e.value||"1":"":"radio"===e.type?e.checked?e.value:null:e.value}openTaxonomyModal(e){window.jvbSelector?window.jvbSelector.openForFilter(e,((e,t)=>this.handleBulkTaxonomy(e,t))):console.error("TaxonomySelector not initialized")}handleBulkTaxonomy(e,t){if(e.length>0){e=e.join(",");let s={},i=Array.from(this.viewController.selectedItems);i.forEach((i=>{s[i]={content:this.content},s[i][t]=e}));let l=`Adding ${i.length} ${this.config.plural??"posts"} to ${e.length} ${jvbSettings.labels[t].plural}`;this.viewController.clearSelection(),this.savePosts(s,l)}}setBulkStatus(e){if(!["publish","draft","trash","delete"].includes(e))return;let t,s={};for(let t of this.viewController.selectedItems)s[t]={post_status:e,content:this.content};if("delete"===e)t="Deleting";else t=window.uppercaseFirst(e)+"ing";if("all"===this.status&&!["publish","draft"].includes(e)||e!==this.status){let e=0;for(let t of this.viewController.selectedItems)setTimeout((()=>{const e=document.querySelector(`.item[data-id="${t}"]`);e&&window.fade(e,!1)}),e),e+=50}this.viewController.clearSelection(),0!==Object.keys(s).length&&this.savePosts(s,`${t} ${this.viewController.selectedItems.size} ${this.plural}...`)}handleFilterChange(e){let t=e.target;if("taxonomies"===t.dataset.filter){let e=t.dataset.taxonomy;this.store.setFilter(`tax_${e}`,t.value)}else this[t.dataset.filter]=t.value,this.store.setFilter(t.dataset.filter,t.value),"status"===t.dataset.filter&&this.updateBulkOptions(t.value)}updateBulkOptions(e="all"){if("trash"===e){if(this.ui.bulkSelectActions?.querySelector('[value="edit"]')){window.removeChildren(this.ui.bulkSelectActions),window.getTemplate("trashOptions").querySelectorAll("option").forEach(((e,t)=>{0===t&&(e.checked=!0),this.ui.bulkSelectActions.append(e)}))}}else if(this.ui.bulkSelectActions&&!this.ui.bulkSelectActions.querySelector('[value="edit"]')){window.removeChildren(this.ui.bulkSelectActions),window.getTemplate("notTrashOptions").querySelectorAll("option").forEach(((e,t)=>{this.ui.bulkSelectActions.append(e)}))}this.ui.bulkSelectActions&&(this.ui.bulkSelectActions.value="")}populateBulkEdit(){const e=this.modals.bulkEdit.modal.querySelector("form .selected");if(!e)return;window.removeChildren(e);for(let t of this.viewController.selectedItems){let s=this.store.get(t);const i=window.getTemplate("bulkItem");if(!i)return;const l=i.querySelector("input[type=checkbox]"),o=i.querySelector("img");l&&(l.id=`bulk_${s.id}`,l.value=s.id,l.checked=!0),o&&s.thumbnail&&(o.src=s.thumbnail,o.alt=s.alt||""),e.append(i)}let t=this.modals.bulkEdit.modal;[t.querySelector("h2 span").textContent]=[this.viewController.selectedItems.size],this.formController.registerForm(this.ui.forms.bulkEdit)}populateEditForm(e){this.currentItemID=e;let t=this.store.get(parseInt(e));if(console.log("Item",t),t){this.ui.modals.edit.dataset.itemId=e,this.ui.modals.edit.dataset.content=this.content;let s=this.ui.modals.edit.querySelector("form");[this.ui.modals.edit.querySelector("h2").textContent]=[`Editing ${t.fields.post_title}`],s.dataset.formId=`edit-${e}`,console.log("Sending to jvbPopulate: ",t.fields),console.log("and images: ",t.images),new window.jvbPopulate(s,t.fields,t.images),this.formController.registerForm(this.ui.forms.edit)}}setupFilters(){const e=document.querySelector('input[type="search"]');if(e){let t;e.addEventListener("input",(()=>{e.value.length>3?(clearTimeout(t),t=setTimeout((()=>{this.store.setFilter("search",e.value)}),300)):0===e.value.length&&this.store.removeFilter("search")}))}}destroy(){document.querySelectorAll("[data-filter]").forEach((e=>{e.removeEventListener("change",this.filterHandler)}))}}document.addEventListener("DOMContentLoaded",(async function(){window.auth.subscribe((t=>{if("auth-loaded"===t){let t=document.querySelector("[data-content]");t&&!Object.hasOwn(t.dataset,"ignore")&&(window.crudManager=new e({content:t.dataset.content}))}}))}))})(); |
| | | (()=>{class e{constructor(e){if(this.queue=window.jvbQueue,this.config=e,this.content=e.content||!1,this.settings=window.jvbUserSettings,this.a11y=window.jvbA11y,!this.content)return;this.isTimeline=!1,this.currentItemID=null,this.initElements(),this.updateBulkOptions();const t=window.jvbStore.register(this.content,{storeName:this.content,keyPath:"id",endpoint:"content",headers:{action_nonce:window.auth.getNonce("dash")},indexes:[{name:"id",keyPath:"id"},{name:"status",keyPath:"status"},{name:"date",keyPath:"date"},{name:"modified",keyPath:"modified"},{name:"title",keyPath:"title"}],filters:{content:this.content,user:window.auth.getUser(),page:1,status:"all",orderby:"modified",order:"desc"},TTL:18e5,showLoading:!0});this.store=t[this.content],this.status="all",this.filterTimeout=null,this.viewController=new window.jvbViews(this.ui.container,this.store),this.tableForm=null,this.tableChanges=new Map,this.formController=this.isTimeline?new window.jvbForm({collectFormData:()=>this.collectTimelineData.bind(this)}):new window.jvbForm,this.viewController.subscribe(((e,t)=>{if("table-view"!==e||this.tableForm){if("not-table-view"===e)this.tableForm;else if("order-changed"===e){let e=this.store.get(t);if(!e)return;let s={};s[t]=e,this.savePosts(s,"Updating progression order")}}else this.tableForm||(this.tableForm=this.formController.registerForm(t,{autosave:!1,formStatus:!1,isTable:!0}))})),this.formController.subscribe(((e,t)=>{switch(e){case"form-submit":case"form-autosave":this.handleFormChange(e,t)}})),this.queue.subscribe(((e,t)=>{Object.hasOwn(t,"endpoint")&&["content","uploads/groups"].includes(t.endpoint)&&("operation-completed"===e?this.handleQueueSuccess(e,t):"operation-failed-permanent"===e&&this.handleQueueFailure(e,t))})),this.initialized=!1,this.init()}handleFormChange(e,t){let s=t.fullData.post_title,i=Object.hasOwn(t,"changes")?t.changes:t.fullData,l={};if(this.isTimeline)return l[this.currentItemID]=i,void this.savePosts(l,s);let o=[];switch(!0){case t.config.element===this.ui.forms.edit:l[this.currentItemID]=i,s=`Saving ${s} Changes`,i.post_status&&this.shouldRemoveItem(i.post_status)&&o.push(this.currentItemID);break;case t.config.element===this.ui.forms.bulkEdit:let a=t.config.element.querySelectorAll(".selected input:checked");a.forEach((e=>{l[e.value]=i,i.post_status&&this.shouldRemoveItem(i.post_status)&&o.push(e.value)})),s=`Updating ${a.length} ${this.config.plural??"posts"} Changes`;break;case t.config.element===this.ui.forms.create:"form-submit"===e&&(l[t.config.data["form-id"]]=i,s=`Saving ${s} Changes`)}if(o.length>0){let e=0;o.forEach((t=>{setTimeout((()=>{const e=document.querySelector(`.item[data-id="${t}"]`);e&&window.fade(e,!1)}),e),e+=50})),t.config.element===this.ui.forms.bulkEdit&&setTimeout((()=>{this.viewController.clearSelection()}),e+100)}0!==Object.keys(l).length&&this.savePosts(l,s)}shouldRemoveItem(e){return"all"===this.status&&!["publish","draft"].includes(e)||e!==this.status}savePosts(e,t){if(0===Object.keys(e).length)return;for(let t in e)e[t].content||(e[t].content=this.content);let s={endpoint:"content",headers:{action_nonce:window.auth.getNonce("dash")},data:{posts:e},popup:"Saving changes",title:t};this.queue.addToQueue(s)}async handleQueueSuccess(e,t){this.store.clearCache(),this.store.fetch()}handleQueueFailure(e,t){console.error("Operation failed permanently:",t),this.a11y?.announce(`Operation failed: ${t.error_message||"Unknown error"}`)}initElements(){this.elements={modals:{create:"dialog.create",edit:"dialog.edit",bulkEdit:"dialog.bulkEdit"},container:".crud[data-content]",grid:".item-grid",bulkSelectActions:".bulk-action-select",forms:{create:"dialog.create form",edit:"dialog.edit form",bulkEdit:"dialog.bulkEdit form"},uploader:"details.uploader"},this.ui=window.uiFromSelectors(this.elements),this.ui.uploader&&(window.jvbUploads.scanFields(document.querySelector(this.elements.uploader)),window.jvbUploads.subscribe(((e,t)=>{"sent-to-queue"===e&&(console.log(t),t===this.ui.uploader.querySelector("[data-uploader]")?.dataset.uploader&&window.debouncer.schedule("crud-complete",(()=>{this.store.clearHttpHeaders()})))}))),this.isTimeline=!!document.querySelector("[data-timeline]")}init(){this.ui.uploader&&(this.settings.addSetting(this.ui.uploader,"open"),this.ui.uploader.addEventListener("toggle",(e=>{this.settings.saveSetting("open",this.ui.uploader.open?"on":"off")}))),this.filterHandler=this.handleFilterChange.bind(this),this.changeHandler=this.handleChange.bind(this),this.modals={};for(let[e,t]of Object.entries(this.ui.modals))this.modals[e]=new window.jvbModal(t),this.modals[e].subscribe(((t,s)=>{if("modal-close"===t)this.currentItemID=null,this.formController.cleanupForm(this.modals[e].modal.querySelector("form").dataset.formId)}));this.setupEventDelegation(),this.setupFilters(),this.initialized=!0}setupEventDelegation(){document.addEventListener("change",this.changeHandler),document.addEventListener("click",(e=>{const t=e.target.closest("[data-action]");if(t){e.preventDefault();const s=t.dataset.action,i=t.dataset.id;switch(s){case"edit":this.populateEditForm(i),this.modals.edit.handleOpen();break;case"delete":if(confirm("Delete this item?")){let e={};e[t.dataset.id]={post_status:"delete",content:this.content},window.fade(t.closest(".item"),!1),this.savePosts(e,`Sending ${this.singular} to trash...`),this.store.delete(i)}break;case"trash":let e={};e[t.dataset.id]={post_status:"trash",content:this.content},window.fade(t.closest(".item"),!1),this.savePosts(e,`Sending ${this.singular} to trash...`);break;case"create":this.modals.create.dataset.itemId="new",this.modals.create.dataset.content=this.content,this.modals.create.handleOpen();break;case"bulk-edit":Array.from(this.viewController.selectedItems).length>0&&this.modals.bulkEdit.handleOpen();break;case"bulk-delete":const s=Array.from(this.viewController.selectedItems);s.length>0&&confirm(`Delete ${s.length} items?`)&&(s.forEach((e=>this.store.delete(e))),this.viewController.clearSelection());break;case"sync":break;case"refresh":this.store.fetch()}}e.target.closest(".create-item")&&(this.formController.registerForm(this.ui.forms.create),this.modals.create.handleOpen()),e.target.closest(".cancel-bulk")&&this.viewController.selectAll(!1)})),document.addEventListener("keydown",(e=>{(e.ctrlKey||e.metaKey)&&"a"===e.key&&this.ui.container&&this.ui.container.contains(document.activeElement)&&(e.preventDefault(),this.viewController.selectAll()),"Escape"===e.key&&this.viewController?.selectedItems.size>0&&0===window.jvbModal.getAllModals().length&&this.viewController.clearSelection()}))}handleChange(e){if(e.target.closest("[data-id]"))this.isTimeline?this.handleTimelineTableChange(e):this.handleTableChange(e);else{if(e.target.classList.contains("bulk-action-select")){if(e.target.value.startsWith("tax-")){const t=e.target.value.replace("tax-","");return this.openTaxonomyModal(t),void(e.target.value="")}switch(e.target.value){case"edit":this.populateBulkEdit(),this.modals.bulkEdit.handleOpen();break;case"publish":this.setBulkStatus("publish");break;case"draft":case"restore":this.setBulkStatus("draft");break;case"trash":this.setBulkStatus("trash");break;case"delete":this.setBulkStatus("delete")}}window.targetCheck(e,"select[data-filter]")&&this.handleFilterChange(e)}}handleTableChange(e){const t=e.target.closest("tr[data-id]");if(!t)return;const s=e.target,i=parseInt(t.dataset.id),l=s.closest(["data-field"])?.dataset.field;if(!l)return;const o=this.store.get(i);if(!o)return;o.fields[l]=this.getInputValue(s),this.store.save(o);let a={};a[i]=o.fields,this.savePosts(a,`Saving changes to ${this.content}`)}handleTimelineTableChange(e){const t=e.target.closest("tbody[data-id]");if(!t)return;const s=e.target,i=s.closest("[data-field]")?.dataset.field;if(!i)return;const l=parseInt(t.dataset.id),o=s.closest("tr.timeline-point"),a=this.store.get(l);if(!a)return;const n=this.getInputValue(s);if(o){const e=o.dataset.imageId;a.fields.timeline||(a.fields.timeline={}),a.fields.timeline[e]||(a.fields.timeline[e]={}),a.fields.timeline[e][i]=n}else a.fields[i]=n;this.store.save(a);let r={};r[l]=a.fields,this.savePosts(r,"Updating progress post")}getInputValue(e){return"checkbox"===e.type?e.checked?e.value||"1":"":"radio"===e.type?e.checked?e.value:null:e.value}openTaxonomyModal(e){window.jvbSelector?window.jvbSelector.openForFilter(e,((e,t)=>this.handleBulkTaxonomy(e,t))):console.error("TaxonomySelector not initialized")}handleBulkTaxonomy(e,t){if(e.length>0){e=e.join(",");let s={},i=Array.from(this.viewController.selectedItems);i.forEach((i=>{s[i]={content:this.content},s[i][t]=e}));let l=`Adding ${i.length} ${this.config.plural??"posts"} to ${e.length} ${jvbSettings.labels[t].plural}`;this.viewController.clearSelection(),this.savePosts(s,l)}}setBulkStatus(e){if(!["publish","draft","trash","delete"].includes(e))return;let t,s={};for(let t of this.viewController.selectedItems)s[t]={post_status:e,content:this.content};if("delete"===e)t="Deleting";else t=window.uppercaseFirst(e)+"ing";if("all"===this.status&&!["publish","draft"].includes(e)||e!==this.status){let e=0;for(let t of this.viewController.selectedItems)setTimeout((()=>{const e=document.querySelector(`.item[data-id="${t}"]`);e&&window.fade(e,!1)}),e),e+=50}this.viewController.clearSelection(),0!==Object.keys(s).length&&this.savePosts(s,`${t} ${this.viewController.selectedItems.size} ${this.plural}...`)}handleFilterChange(e){let t=e.target;if("taxonomies"===t.dataset.filter){let e=t.dataset.taxonomy;this.store.setFilter(`tax_${e}`,t.value)}else this[t.dataset.filter]=t.value,this.store.setFilter(t.dataset.filter,t.value),"status"===t.dataset.filter&&this.updateBulkOptions(t.value)}updateBulkOptions(e="all"){if("trash"===e){if(this.ui.bulkSelectActions?.querySelector('[value="edit"]')){window.removeChildren(this.ui.bulkSelectActions),window.getTemplate("trashOptions").querySelectorAll("option").forEach(((e,t)=>{0===t&&(e.checked=!0),this.ui.bulkSelectActions.append(e)}))}}else if(this.ui.bulkSelectActions&&!this.ui.bulkSelectActions.querySelector('[value="edit"]')){window.removeChildren(this.ui.bulkSelectActions),window.getTemplate("notTrashOptions").querySelectorAll("option").forEach(((e,t)=>{this.ui.bulkSelectActions.append(e)}))}this.ui.bulkSelectActions&&(this.ui.bulkSelectActions.value="")}populateBulkEdit(){const e=this.modals.bulkEdit.modal.querySelector("form .selected");if(!e)return;window.removeChildren(e);for(let t of this.viewController.selectedItems){let s=this.store.get(t);const i=window.getTemplate("bulkItem");if(!i)return;const l=i.querySelector("input[type=checkbox]"),o=i.querySelector("img");l&&(l.id=`bulk_${s.id}`,l.value=s.id,l.checked=!0),o&&s.thumbnail&&(o.src=s.thumbnail,o.alt=s.alt||""),e.append(i)}let t=this.modals.bulkEdit.modal;[t.querySelector("h2 span").textContent]=[this.viewController.selectedItems.size],this.formController.registerForm(this.ui.forms.bulkEdit)}populateEditForm(e){this.currentItemID=e;let t=this.store.get(parseInt(e));if(t){this.ui.modals.edit.dataset.itemId=e,this.ui.modals.edit.dataset.content=this.content;let s=this.ui.modals.edit.querySelector("form");this.ui.modals.edit.querySelector("h2").textContent=`Editing ${t.fields.post_title}`,s.dataset.formId=`edit-${e}`,new window.jvbPopulate(s,t),this.formController.registerForm(this.ui.forms.edit)}}setupFilters(){const e=document.querySelector('input[type="search"]');if(e){let t;e.addEventListener("input",(()=>{e.value.length>3?(clearTimeout(t),t=setTimeout((()=>{this.store.setFilter("search",e.value)}),300)):0===e.value.length&&this.store.removeFilter("search")}))}}destroy(){document.querySelectorAll("[data-filter]").forEach((e=>{e.removeEventListener("change",this.filterHandler)}))}}document.addEventListener("DOMContentLoaded",(async function(){window.auth.subscribe((t=>{if("auth-loaded"===t){let t=document.querySelector("[data-content]");t&&!Object.hasOwn(t.dataset,"ignore")&&(window.crudManager=new e({content:t.dataset.content}))}}))}))})(); |
| | |
| | | window.jvbPopulate=class{constructor(e,t={},a={},l={}){for(let[r,o]of Object.entries(t)){let t=e.querySelector(`[data-field="${r}"]`);t&&this.populateField(t,r,o,a,l)}}populateField(e,t,a,l={},r={}){if(e&&null!=a)switch(this.getFieldType(e)){case"upload":case"gallery":case"image":this.populateUploadField(e,t,a,l);break;case"repeater":this.populateRepeaterField(e,t,a,r);break;case"taxonomy":this.populateTaxonomyField(e,t,a);break;case"user":this.populateUserField(e,t,a);break;case"location":this.populateLocationField(e,t,a);break;case"set":case"checkbox":this.populateSetField(e,t,a);break;case"select":case"radio":this.populateSelectField(e,t,a);break;case"true_false":this.populateBooleanField(e,t,a);break;case"date":case"time":case"datetime":this.populateDateField(e,t,a);break;case"number":this.populateNumberField(e,t,a);break;case"textarea":e.querySelector(".editor-container")?this.populateEditorField(e,t,a):this.populateTextareaField(e,t,a);break;default:this.populateTextField(e,t,a)}}getFieldType(e){if(e.dataset.fieldType)return e.dataset.fieldType;if(e.dataset.type)return e.dataset.type;const t=["upload","repeater","taxonomy","user","location","set","checkbox","select","radio","true_false","date","time","datetime","editor","number","text","textarea","email","url","tel","phone"];for(const a of t)if(e.classList.contains(a))return a;const a=e.querySelector("input, select, textarea");if(a){if("TEXTAREA"===a.tagName)return"true"===a.dataset.editor?"editor":"textarea";if(a.type)return"checkbox"!==a.type||e.classList.contains("true_false")?a.type:"set"}return"text"}populateTextField(e,t,a){const l=e.querySelector(`[name="${t}"], input, textarea`);if((!l||"file"!==l.type)&&l&&(l.value=String(a||""),l.dataset.limit)){const t=e.querySelector(".char-count .current");t&&(t.textContent=l.value.length)}}populateTextareaField(e,t,a){const l=e.querySelector(`textarea[name="${t}"]`)||e.querySelector('textarea:not([data-editor="true"])');if(l){if(l.value=String(a||""),l.dispatchEvent(new Event("change",{bubbles:!0})),l.dataset.limit){const t=e.querySelector(".char-count .current");if(t){t.textContent=l.value.length;const a=parseInt(l.dataset.limit,10);l.value.length>=a?e.classList.add("reached"):e.classList.remove("reached")}}}else console.warn(`No textarea found for field ${t} in wrapper:`,e)}populateNumberField(e,t,a){const l=e.querySelector(`[name="${t}"], input[type="number"]`);l&&(l.value=Number(a)||0)}populateBooleanField(e,t,a){const l=e.querySelector(`[name="${t}"], input[type="checkbox"]`);l&&(l.checked=Boolean(a))}populateSelectField(e,t,a){const l=String(a||""),r=e.querySelector(`select[name="${t}"]`);if(r)return void(r.value=l);const o=e.querySelector(`input[type="radio"][name="${t}"][value="${l}"]`);o&&(o.checked=!0)}populateSetField(e,t,a){let l=a;if("string"==typeof a)try{l=JSON.parse(a)}catch(e){l=a.split(",").map((e=>e.trim()))}Array.isArray(l)||(l=[String(l)]),e.querySelectorAll(`input[type="checkbox"][name*="${t}"]`).forEach((e=>{e.checked=l.includes(e.value)}))}populateDateField(e,t,a){const l=e.querySelector(`[name="${t}"], input`);if(l&&a){let e=a;"object"==typeof a&&a.date&&(e=a.date);try{const t=new Date(e);if(!isNaN(t.getTime()))switch(l.type){case"date":l.value=t.toISOString().split("T")[0];break;case"time":l.value=t.toTimeString().slice(0,5);break;case"datetime-local":l.value=t.toISOString().slice(0,16);break;default:l.value=e}}catch(t){l.value=e}}}populateEditorField(e,t,a){const l=e.querySelector(`textarea[name="${t}"]`)||e.querySelector('textarea[data-editor="true"]')||e.querySelector("textarea");if(!l)return void console.warn(`Editor field ${t}: textarea not found`);const r=String(a||"");l.value=r;const o=e.querySelector(".editor");if(o){let e=null;if(o.__quill)e=o.__quill;else if(o.quill)e=o.quill;else if(window.Quill&&window.Quill.find)e=window.Quill.find(o);else if(window.Quill&&window.Quill.instances)for(let t of window.Quill.instances)if(t.container===o){e=t;break}e?(e.root.innerHTML=r,o.__quill=e):(console.warn(`Quill instance not found for ${t}, setting HTML directly`),o.innerHTML=r)}else console.warn(`Editor container not found for ${t}`);l.dispatchEvent(new Event("change",{bubbles:!0}))}populateLocationField(e,t,a){a&&"object"==typeof a&&["address","lat","lng","street","city","province","postal_code","country"].forEach((l=>{if(void 0!==a[l]){const r=e.querySelector(`[name="${t}_${l}"], [name="${l}"]`);r&&(r.value=String(a[l]||""))}}))}populateTaxonomyField(e,t,a){let l=[];if(Array.isArray(a))l=a.map((e=>String(e)));else if("string"==typeof a)try{const e=JSON.parse(a);l=Array.isArray(e)?e.map((e=>String(e))):[String(e)]}catch(e){l=a.split(",").map((e=>e.trim()))}else a&&(l=[String(a)]);if(0===l.length)return;const r=e.querySelector(`input[type="hidden"][name="${t}"]`);if(r){r.value=l.join(",");const t=e.querySelector(".taxonomy-toggle");t&&t.dataset.fieldId&&window.jvbTaxonomy&&window.jvbTaxonomy.updateFieldFromInput(t.dataset.fieldId)}}populateUserField(e,t,a){this.populateTaxonomyField(e,t,a)}populateUploadField(e,t,a,l={}){if("timeline"===e.dataset.subtype||"timeline"===t)return void this.populateTimelineGallery(e,t,a,l);if(!a)return;const r=String(a).split(",").filter((e=>parseInt(e.trim())));if(0===r.length)return;const o=e.querySelector(`input[type="hidden"][name="${t}"]`);o&&(o.value=r.join(","));const i=e.querySelector(".item-grid"),n=e.querySelector(".file-upload-container");i&&window.removeChildren(i),e.querySelector(".progress")?.remove(),i&&(r.forEach((e=>{const t=window.getTemplate("uploadItem");if(!t)return void console.warn("uploadItem template not found");let a=t.querySelector('input[name="select-item"]'),r=t.querySelector('label[for="select-item"]');t.dataset.id=e,a.name=a.name+`-${e}`,a.id=a.name,r.htmlFor=a.name;const o=t.querySelector("img");t.querySelector("video")?.remove();const n=t.querySelector("details");if(l[e]){const a=l[e];o&&(o.src=a.medium||a.small||a.large||"",o.alt=a["image-alt-text"]||a.alt||"");const r=t.querySelector('[name="image-title"]'),i=t.querySelector('[name="image-alt-text"]'),n=t.querySelector('[name="image-caption"]');r&&(r.value=a["image-title"]||a.title||""),i&&(i.value=a["image-alt-text"]||a.alt||""),n&&(n.value=a["image-caption"]||a.caption||"")}else console.warn("No image data found for ID:",e,"Available data:",Object.keys(l));n?.querySelector(".upload-meta > .hint")?.remove(),i.append(t)})),r.length>0&&n&&(n.hidden=!0))}populateTimelineGallery(e,t,a,l){if(console.log("populating Timeline Gallery"),!a||"object"!=typeof a)return;const r=Object.values(a);if(0===r.length)return;const o=e.querySelector(".item-grid"),i=e.querySelector(".file-upload-container");if(e.querySelector(".progress")?.remove(),o){window.removeChildren(o),console.log(r);for(let e of Object.entries(a)){let t=e.post_thumbnail;const a=window.getTemplate("timelineItem");if(!a)return;const r=a.querySelector("img");a.querySelector("video")?.remove(),a.querySelector(".select-item span")?.remove();let i=a.querySelector('input[name="select-item"]'),n=a.querySelector('label[for="select-item"]');a.dataset.id=t,a.dataset.postId=e.id,i.name=i.name+`-${t}`,i.id=i.name,n.htmlFor=i.name;const c=l[t];r&&c&&(r.src=c.medium||c.small||c.large||"",r.title=c["image-title"]);const s={...e,...c};a.querySelectorAll(".field").forEach((t=>{if(t.classList.contains("group"))return;const a=t.querySelector('input:not([type="file"]), textarea');if(!a)return;const r=t.querySelector("label"),o=a.name.replace("upload_data::",""),i=s[o];this.populateField(t,o,i,l);const n=`[${e.id}]${o}`;a.name=n,a.id=n,r&&(r.htmlFor=n)})),o.append(a)}r.length>0&&i&&(i.hidden=!0)}}populateRepeaterField(e,t,a){if(!a||!Array.isArray(a))return;const l=e.querySelector(".repeater-items"),r=e.querySelector("template");l&&r?(window.removeChildren(l),a.forEach(((a,o)=>{if(!a||"object"!=typeof a)return;const i=window.getTemplate(r.className);if(!i)return void console.warn(`Repeater field ${t}: template not found`);i.id=`${e.closest("form").id}-${t}-row-${o}`,i.dataset.index=o;const n=i.querySelector(".row-number");n&&(n.textContent=`#${o+1}`),i.querySelectorAll("input, select, textarea").forEach((e=>{const l=e.name,r=`${t}:${o}:${l}`,i=`${t}-${o}-${l}-${e.value}`;e.name=r,e.id=i;const n=e.nextElementSibling;n&&"LABEL"===n.tagName&&(n.htmlFor=i),void 0!==a[l]&&this.populateRepeaterFieldValue(e,l,a[l])})),l.appendChild(i)}))):console.warn(`Repeater field ${t}: missing container or template`)}populateRepeaterFieldValue(e,t,a){switch(e.type){case"checkbox":e.checked=Boolean(a);break;case"radio":e.checked=e.value===String(a);break;default:e.value=String(a||"")}}}; |
| | | window.jvbPopulate=class{constructor(e,t={},a={},i={}){this.item=this.normalizeItemData(t,a),this.form=e,this.options=i;for(let[t,a]of Object.entries(this.item.fields)){let i=e.querySelector(`[data-field="${t}"]`);i&&this.populateField(i,t,a)}}normalizeItemData(e,t){return e&&"object"==typeof e&&"fields"in e?{fields:e.fields||{},images:e.images||{},taxonomies:e.taxonomies||{}}:{fields:e||{},images:t||{},taxonomies:{}}}isTaxonomyField(e){return Object.hasOwn(this.item.taxonomies,e)&&Object.keys(this.item.taxonomies[e]).length>0}isImageField(e){return!(!this.item.images||0===Object.keys(this.item.images).length)&&this.splitIDs(e).some((e=>Object.keys(this.item.images).includes(String(e))))}splitIDs(e){return String(e).split(",").map((e=>parseInt(e.trim()))).filter((e=>!isNaN(e)&&e>0))}populateField(e,t,a,i={}){if(e&&null!=a)switch(this.getFieldType(e)){case"upload":case"gallery":case"image":this.populateUploadField(e,t,a);break;case"repeater":this.populateRepeaterField(e,t,a);break;case"taxonomy":this.populateTaxonomyField(e,t,a);break;case"user":this.populateUserField(e,t,a);break;case"location":this.populateLocationField(e,t,a);break;case"set":case"checkbox":this.populateSetField(e,t,a);break;case"select":case"radio":this.populateSelectField(e,t,a);break;case"true_false":this.populateBooleanField(e,t,a);break;case"date":case"time":case"datetime":this.populateDateField(e,t,a);break;case"number":this.populateNumberField(e,t,a);break;case"textarea":e.querySelector(".editor-container")?this.populateEditorField(e,t,a):this.populateTextareaField(e,t,a);break;default:this.populateTextField(e,t,a)}}populateTaxonomyField(e,t,a){let i=[];if(Array.isArray(a))i=a.map((e=>String(e)));else if("string"==typeof a)try{const e=JSON.parse(a);i=Array.isArray(e)?e.map((e=>String(e))):[String(e)]}catch(e){i=a.split(",").map((e=>e.trim())).filter((e=>e))}else a&&(i=[String(a)]);if(0===i.length)return;const l=e.querySelector(`input[type="hidden"][name="${t}"]`);if(l){l.value=i.join(",");const t=e.querySelector(".taxonomy-toggle");t&&t.dataset.fieldId&&window.jvbTaxonomy&&requestAnimationFrame((()=>{window.jvbTaxonomy.updateFieldFromInput(t.dataset.fieldId)}))}}populateUploadField(e,t,a){if("timeline"===e.dataset.subtype||"timeline"===t)return void this.populateTimelineGallery(e,t,a);if(!a)return;const i=this.splitIDs(a);if(0===i.length)return;const l=e.querySelector(`input[type="hidden"][name="${t}"]`);l&&(l.value=i.join(","));const r=e.querySelector(".item-grid"),o=e.querySelector(".file-upload-container");r&&window.removeChildren(r),e.querySelector(".progress")?.remove(),r&&(i.forEach((e=>{const t=window.getTemplate("uploadItem");t?(this.populateUploadItem(t,e),r.append(t)):console.warn("uploadItem template not found")})),i.length>0&&o&&(o.hidden=!0))}populateUploadItem(e,t){let a=e.querySelector('input[name="select-item"]'),i=e.querySelector('label[for="select-item"]');e.dataset.id=t,a.name=`select-item-${t}`,a.id=a.name,i.htmlFor=a.name;const l=e.querySelector("img");if(e.querySelector("video")?.remove(),this.item.images[t]){const a=this.item.images[t];l&&(l.src=a.medium||a.small||a.large||"",l.alt=a["image-alt-text"]||a.alt||"");const i=e.querySelector('[name="image-title"]'),r=e.querySelector('[name="image-alt-text"]'),o=e.querySelector('[name="image-caption"]');i&&(i.value=a["image-title"]||a.title||""),r&&(r.value=a["image-alt-text"]||a.alt||""),o&&(o.value=a["image-caption"]||a.caption||"")}else console.warn(`No image data found for ID: ${t}`);e.querySelector("details .upload-meta > .hint")?.remove()}populateTimelineGallery(e,t,a){if(console.log("Populating Timeline Gallery",a),!a||!Array.isArray(a))return void console.warn("Timeline field value must be an array");if(0===a.length)return;const i=e.querySelector(".item-grid"),l=e.querySelector(".file-upload-container");if(i&&window.removeChildren(i),e.querySelector(".progress")?.remove(),i){for(let e of a){const t=window.getTemplate("timelineItem");if(!t){console.warn("timelineItem template not found");continue}const a=e.post_thumbnail,l=e.id;t.dataset.id=a,t.dataset.postId=l;let r=t.querySelector('input[name="select-item"]'),o=t.querySelector('label[for="select-item"]');r&&o&&(r.name=`select-item-${a}`,r.id=r.name,o.htmlFor=r.name),t.querySelector("video")?.remove(),t.querySelector(".select-item span")?.remove();const n=t.querySelector("img"),s=this.item.images[a];n&&s&&(n.src=s.medium||s.small||s.large||"",n.title=s["image-title"]||"",n.alt=s["image-alt-text"]||""),t.querySelectorAll(".field").forEach((t=>{if(t.classList.contains("group"))return;const a=t.querySelector('input:not([type="file"]), textarea');if(!a)return;const i=t.querySelector("label"),r=a.name.replace("upload_data::","").replace(/^\[.*?\]/,"");let o=e[r];void 0===o&&s&&(o=s[r]),null!=o&&this.populateField(t,r,o);const n=`[${l}]${r}`,c=n;a.name=n,a.id=c,i&&(i.htmlFor=c)})),i.append(t)}a.length>0&&l&&(l.hidden=!0)}}populateTextField(e,t,a){const i=e.querySelector(`[name="${t}"], input, textarea`);if(i&&"file"!==i.type&&(i.value=String(a||""),i.dataset.limit)){const t=e.querySelector(".char-count .current");t&&(t.textContent=i.value.length)}}populateTextareaField(e,t,a){const i=e.querySelector(`textarea[name="${t}"]`)||e.querySelector('textarea:not([data-editor="true"])');if(i&&(i.value=String(a||""),i.dispatchEvent(new Event("change",{bubbles:!0})),i.dataset.limit)){const t=e.querySelector(".char-count .current");if(t){t.textContent=i.value.length;const a=parseInt(i.dataset.limit,10);e.classList.toggle("reached",i.value.length>=a)}}}populateEditorField(e,t,a){const i=e.querySelector(`textarea[name="${t}"][data-editor="true"]`);if(!i)return;i.value=String(a||"");const l=e.querySelector(".editor"),r=a||"<p><br></p>";if(l){let e=l.__quill;if(!e&&window.Quill)for(let t of window.Quill.instances||[])if(t.container===l){e=t;break}e?(e.root.innerHTML=r,l.__quill=e):l.innerHTML=r}i.dispatchEvent(new Event("change",{bubbles:!0}))}getFieldType(e){if(e.dataset.fieldType)return e.dataset.fieldType;if(e.dataset.type)return e.dataset.type;const t=["upload","repeater","taxonomy","user","location","set","checkbox","select","radio","true_false","date","time","datetime","editor","number","text","textarea","email","url","tel","phone"];for(const a of t)if(e.classList.contains(a))return a;const a=e.querySelector("input, select, textarea");if(a){if("TEXTAREA"===a.tagName)return"true"===a.dataset.editor?"editor":"textarea";if(a.type)return"checkbox"!==a.type||e.classList.contains("true_false")?a.type:"set"}return"text"}}; |
| | |
| | | window.feedBlock = new FeedBlock(); |
| | | } |
| | | }); |
| | | |
| | | let item = { |
| | | content: "art", |
| | | date: "2025-12-24 03:37:26", |
| | | fields: { |
| | | gallery: "", |
| | | post_content: "", |
| | | post_thumbnail: 200, |
| | | post_title: "Great Gray Owl", |
| | | price: "", |
| | | }, |
| | | icon: "arrows-clockwise", |
| | | id: 195, |
| | | images: { |
| | | 200: { |
| | | 'image-alt-text': "", |
| | | 'image-caption': "", |
| | | 'image-title': "Great Gray Owl", |
| | | large: "http://jakevan.test/wp-content/uploads/2025/12/Great-Gray-Owl.jpg", |
| | | medium: "http://jakevan.test/wp-content/uploads/2025/12/Great-Gray-Owl-1024x1024.jpg", |
| | | small: "http://jakevan.test/wp-content/uploads/2025/12/Great-Gray-Owl-300x300.jpg", |
| | | tiny: "http://jakevan.test/wp-content/uploads/2025/12/Great-Gray-Owl-50x50.jpg" |
| | | } |
| | | }, |
| | | url: "http://jakevan.test/art/great-gray-owl/", |
| | | user_id: 3 |
| | | }; |
| | | }); |
| | | |
| | | |