Jake Vanderwerf
2026-01-04 c2a5476997befeaafce461693f4a363c13766df4
Merge branch 'main' of https://github.com/jakevdwerf/jvb
5 files modified
826 ■■■■■ changed files
assets/js/concise/CRUD.js 13 ●●●●● patch | view | raw | blame | history
assets/js/concise/PopulateForm.js 782 ●●●●● patch | view | raw | blame | history
assets/js/min/crud.min.js 2 ●●● patch | view | raw | blame | history
assets/js/min/populate.min.js 2 ●●● patch | view | raw | blame | history
src/feed/view.js 27 ●●●●● patch | view | raw | blame | history
assets/js/concise/CRUD.js
@@ -658,21 +658,16 @@
        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);
        }
    }
assets/js/concise/PopulateForm.js
@@ -1,25 +1,78 @@
/**********************************************
 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;
        }
@@ -31,11 +84,11 @@
            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':
@@ -73,6 +126,7 @@
            case 'number':
                this.populateNumberField(fieldWrapper, fieldName, fieldValue);
                break;
            case 'textarea':
                if (fieldWrapper.querySelector('.editor-container')) {
                    this.populateEditorField(fieldWrapper, fieldName, fieldValue);
@@ -93,294 +147,7 @@
    }
    /**
     * 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
@@ -391,10 +158,9 @@
        } 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)];
@@ -412,28 +178,23 @@
            // 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;
        }
@@ -442,7 +203,7 @@
        }
        // 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;
        }
@@ -467,47 +228,12 @@
        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);
            });
@@ -518,175 +244,249 @@
        }
    }
    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;
assets/js/min/crud.min.js
@@ -1 +1 @@
(()=>{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}))}}))}))})();
assets/js/min/populate.min.js
@@ -1 +1 @@
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"}};
src/feed/view.js
@@ -767,33 +767,6 @@
            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
    };
});