| | |
| | | } |
| | | } |
| | | |
| | | async queueUploads(endpoint, fieldId) { |
| | | async queueUploads(endpoint, fieldId, dependsOn = null) { |
| | | let data = new FormData(); |
| | | const field = this.fields.get(fieldId); |
| | | if (!field) return; |
| | |
| | | |
| | | if (isUpload) { |
| | | data.append('mode', field.config.mode); |
| | | data.append('field_name', field.config.name); |
| | | |
| | | data.append('field_name', field.config.repeaterPath || field.config.name); |
| | | data.append('fieldId', field.id); |
| | | data.append('field_type', field.config.type); |
| | | data.append('subtype', field.config.subtype); |
| | | data.append('item_id', field.config.itemID); |
| | | data.append('destination', field.config.destination); |
| | | if (dependsOn) { |
| | | data.append('depends_on', dependsOn); |
| | | } |
| | | } |
| | | |
| | | let posts, uploadMap, files; |
| | |
| | | } |
| | | |
| | | extractFieldConfig(fieldElement, autoUpload, imageMeta) { |
| | | return { |
| | | const config = { |
| | | autoUpload: autoUpload, |
| | | showMeta: imageMeta, |
| | | destination: fieldElement.dataset.destination || 'meta', //TODO: why do we need this? |
| | | destination: fieldElement.dataset.destination || 'meta', |
| | | content: this.extractFieldContent(fieldElement), |
| | | mode: fieldElement.dataset.mode || 'direct', |
| | | type: fieldElement.dataset.type || 'single', |
| | | name: fieldElement.dataset.field, |
| | | itemID: this.extractFieldItemId(fieldElement)??0, |
| | | maxFiles: parseInt(fieldElement.dataset.maxFiles)??25, |
| | | subType: fieldElement.dataset.subtype?? 'image' |
| | | itemID: this.extractFieldItemId(fieldElement) ?? 0, |
| | | maxFiles: parseInt(fieldElement.dataset.maxFiles) ?? 25, |
| | | subType: fieldElement.dataset.subtype ?? 'image', |
| | | repeaterPath: null |
| | | }; |
| | | |
| | | const repeaterRow = fieldElement.closest('[data-index]'); |
| | | const repeater = repeaterRow?.closest('[data-field][data-repeater-id]'); |
| | | if (repeater && repeaterRow) { |
| | | config.repeaterPath = `${repeater.dataset.field}:${repeaterRow.dataset.index}:${config.name}`; |
| | | } |
| | | |
| | | return config; |
| | | } |
| | | |
| | | extractFieldContent(fieldElement) { |
| | |
| | | determineFieldId(fieldElement) { |
| | | let content = this.extractFieldContent(fieldElement); |
| | | content = (content === null) ? '' : content+'_'; |
| | | |
| | | let itemID = this.extractFieldItemId(fieldElement); |
| | | itemID = (itemID === null) ? '' : itemID+'_'; |
| | | |
| | | const field = fieldElement.dataset.field || ''; |
| | | |
| | | // If inside a repeater row, include repeater name + index for uniqueness |
| | | const repeaterRow = fieldElement.closest('[data-index]'); |
| | | const repeater = repeaterRow?.closest('[data-field][data-repeater-id]'); |
| | | if (repeater && repeaterRow) { |
| | | return `${content}${itemID}${repeater.dataset.field}_${repeaterRow.dataset.index}_${field}`; |
| | | } |
| | | |
| | | return `${content}${itemID}${field}`; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | getSubtypeFromURL(url) { |
| | | if (!url || url === '') { |
| | | return ''; |
| | | } |
| | | const imgs = ['.webp', '.jpg', '.jpeg', '.png', '.gif', '.svg']; |
| | | const videos = ['.mp4', '.ogg', '.mov', '.webm', '.avi']; |
| | | |
| | |
| | | if (!confirm('Remove this item?')) return; |
| | | |
| | | if (uploadId) { |
| | | // New upload managed by stores |
| | | await this.removeUpload(uploadId); |
| | | } else { |
| | | // Existing server-rendered attachment |
| | | const fieldId = this.getFieldIdFromElement(button); |
| | | item.remove(); |
| | | |
| | | if (fieldId) { |
| | | this.updateHiddenInput(fieldId); |
| | | this.maybeLockUploads(fieldId); |
| | | this.notify('attachment-removed', { attachmentId, fieldId }); |
| | | } |
| | | } |
| | | |
| | | this.a11y.announce('Item removed'); |
| | | } |
| | | |
| | | updateHiddenInput(fieldId) { |
| | | const field = this.fields.get(fieldId); |
| | | if (!field?.ui.hidden) return; |
| | | |
| | | const remaining = Array.from(field.ui.grid?.querySelectorAll(this.selectors.items.item) || []) |
| | | .map(el => el.dataset.id || el.dataset.uploadId) |
| | | .filter(Boolean); |
| | | |
| | | const newValue = remaining.join(','); |
| | | if (field.ui.hidden.value === newValue) return; |
| | | |
| | | field.ui.hidden.value = newValue; |
| | | field.ui.hidden.dispatchEvent(new Event('change', { bubbles: true })); |
| | | } |
| | | async setBulkUpload(uploads, key, value) { |
| | | const promises = Array.from(uploads).map(async (upload) => { |
| | | if (typeof upload === 'string') upload = await this.stores.uploads.get(upload); |
| | |
| | | async removeUpload(uploadId) { |
| | | let upload = this.stores.uploads.get(uploadId); |
| | | if (!upload) return; |
| | | const fieldId = upload.field; // grab before clearing |
| | | |
| | | if (upload.group) { |
| | | let group = this.stores.groups.get(upload.group); |
| | | group.uploads = group.uploads.filter(id => id !== uploadId); |
| | |
| | | } |
| | | |
| | | await this.clearUpload(uploadId); |
| | | this.maybeLockUploads(upload.field); |
| | | this.updateHiddenInput(fieldId); |
| | | this.maybeLockUploads(fieldId); |
| | | |
| | | let handler = this.selectionHandlers.get(upload.field); |
| | | if (handler){ |
| | | let handler = this.selectionHandlers.get(fieldId); |
| | | if (handler) { |
| | | handler.deselect(uploadId); |
| | | } |
| | | |
| | |
| | | return; |
| | | } |
| | | |
| | | // Get current order from DOM |
| | | let items = Array.from(target.children) |
| | | .filter(el => el.matches(this.selectors.items.item) && !el.classList.contains('ghost')) |
| | | .map(upload => upload.dataset.uploadId) |
| | | .filter(id => id); |
| | | |
| | | if (!groupId) { |
| | | let hiddenInput = this.fields.get(fieldId)?.ui.hidden; |
| | | if (hiddenInput) { |
| | | hiddenInput.value = items.join(','); |
| | | } |
| | | this.updateHiddenInput(fieldId); |
| | | } else { |
| | | let items = Array.from(target.children) |
| | | .filter(el => el.matches(this.selectors.items.item) && !el.classList.contains('ghost')) |
| | | .map(upload => upload.dataset.uploadId) |
| | | .filter(id => id); |
| | | |
| | | let group = this.stores.groups.get(groupId); |
| | | if (group) { |
| | | group.uploads = items; |