fix: DockerSecretsResolver - don't normalize absolute paths like /var/www/html/...
Some checks failed
Deploy Application / deploy (push) Has been cancelled

This commit is contained in:
2025-11-24 21:28:25 +01:00
parent 4eb7134853
commit 77abc65cd7
1327 changed files with 91915 additions and 9909 deletions

313
resources/js/admin-forms.js Normal file
View File

@@ -0,0 +1,313 @@
/**
* Admin Forms JavaScript Enhancements
*
* Provides:
* - Drag & drop handlers for upload zones
* - File preview generation (images)
* - Auto-resize textareas (JSON fields)
* - Keyboard shortcuts (Ctrl+S for save)
* - Toast notification system
* - Form auto-save (debounced)
*/
(function() {
'use strict';
/**
* Initialize drag & drop for upload zones
*/
function initDragAndDrop() {
const uploadZones = document.querySelectorAll('[data-live-dropzone]');
uploadZones.forEach(zone => {
const fileInput = zone.querySelector('input[type="file"]');
if (!fileInput) return;
// Prevent default drag behaviors
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
zone.addEventListener(eventName, preventDefaults, false);
document.body.addEventListener(eventName, preventDefaults, false);
});
// Highlight drop zone when item is dragged over it
['dragenter', 'dragover'].forEach(eventName => {
zone.addEventListener(eventName, () => {
zone.classList.add('admin-upload-zone--dragover');
}, false);
});
['dragleave', 'drop'].forEach(eventName => {
zone.addEventListener(eventName, () => {
zone.classList.remove('admin-upload-zone--dragover');
}, false);
});
// Handle dropped files
zone.addEventListener('drop', (e) => {
const dt = e.dataTransfer;
const files = dt.files;
if (files.length > 0) {
fileInput.files = files;
// Trigger change event to notify LiveComponent
fileInput.dispatchEvent(new Event('change', { bubbles: true }));
}
}, false);
// Click on zone to trigger file input
zone.addEventListener('click', () => {
if (fileInput) {
fileInput.click();
}
}, false);
});
}
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
/**
* Generate file preview for images
*/
function initFilePreview() {
const fileInputs = document.querySelectorAll('input[type="file"][data-live-upload]');
fileInputs.forEach(input => {
input.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file || !file.type.startsWith('image/')) return;
const reader = new FileReader();
reader.onload = (event) => {
// Find preview container
const component = input.closest('[data-live-component]');
if (!component) return;
const previewContainer = component.querySelector('.admin-file-preview');
if (!previewContainer) return;
const previewImage = previewContainer.querySelector('.admin-file-preview__image');
if (previewImage) {
previewImage.src = event.target.result;
} else {
// Create preview image if it doesn't exist
const img = document.createElement('img');
img.src = event.target.result;
img.className = 'admin-file-preview__image';
img.alt = 'Preview';
previewContainer.insertBefore(img, previewContainer.firstChild);
}
};
reader.readAsDataURL(file);
});
});
}
/**
* Auto-resize textareas (especially for JSON fields)
*/
function initAutoResizeTextareas() {
const textareas = document.querySelectorAll('.admin-textarea--code');
textareas.forEach(textarea => {
// Set initial height
textarea.style.height = 'auto';
textarea.style.height = textarea.scrollHeight + 'px';
// Auto-resize on input
textarea.addEventListener('input', () => {
textarea.style.height = 'auto';
textarea.style.height = textarea.scrollHeight + 'px';
});
// Auto-resize on paste
textarea.addEventListener('paste', () => {
setTimeout(() => {
textarea.style.height = 'auto';
textarea.style.height = textarea.scrollHeight + 'px';
}, 10);
});
});
}
/**
* Keyboard shortcuts
*/
function initKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
// Ctrl+S or Cmd+S: Save draft
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault();
const saveButton = document.querySelector('[data-live-action="autoSave"]');
if (saveButton) {
saveButton.click();
}
}
// Escape: Cancel/Close
if (e.key === 'Escape') {
const cancelButton = document.querySelector('a[href*="/admin/"]');
if (cancelButton && cancelButton.textContent.trim().toLowerCase() === 'cancel') {
cancelButton.click();
}
}
});
}
/**
* Toast notification system
*/
const Toast = {
show(message, type = 'info', duration = 5000) {
const toast = document.createElement('div');
toast.className = `admin-toast admin-toast--${type}`;
const messageEl = document.createElement('p');
messageEl.className = 'admin-toast__message';
messageEl.textContent = message;
toast.appendChild(messageEl);
const closeBtn = document.createElement('button');
closeBtn.className = 'admin-toast__close';
closeBtn.innerHTML = '×';
closeBtn.setAttribute('aria-label', 'Close');
closeBtn.addEventListener('click', () => this.hide(toast));
toast.appendChild(closeBtn);
document.body.appendChild(toast);
// Auto-hide after duration
if (duration > 0) {
setTimeout(() => this.hide(toast), duration);
}
return toast;
},
hide(toast) {
if (toast && toast.parentNode) {
toast.style.animation = 'admin-toast-slide-out 0.3s ease-out';
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 300);
}
},
success(message, duration = 5000) {
return this.show(message, 'success', duration);
},
error(message, duration = 7000) {
return this.show(message, 'error', duration);
},
info(message, duration = 5000) {
return this.show(message, 'info', duration);
},
warning(message, duration = 6000) {
return this.show(message, 'warning', duration);
}
};
// Listen for LiveComponent events and show toasts
document.addEventListener('livecomponent:upload:success', (e) => {
const detail = e.detail || {};
Toast.success(detail.message || 'File uploaded successfully!');
// Optional redirect after upload
if (detail.redirect_url) {
setTimeout(() => {
window.location.href = detail.redirect_url;
}, 1500);
}
});
document.addEventListener('livecomponent:upload:error', (e) => {
const error = e.detail?.error || 'Upload failed';
Toast.error(error);
});
document.addEventListener('livecomponent:draft:saved', (e) => {
Toast.info('Draft saved');
});
// Handle form submission with redirect
document.addEventListener('livecomponent:form:submitted', (e) => {
const detail = e.detail || {};
if (detail.success && detail.redirect_url) {
Toast.success(detail.message || 'Content created successfully!');
// Redirect after a short delay to show the toast
setTimeout(() => {
window.location.href = detail.redirect_url;
}, 1000);
} else {
Toast.error(detail.message || 'Failed to submit form');
}
});
/**
* Form auto-save (debounced)
*/
function initAutoSave() {
const forms = document.querySelectorAll('[data-live-component]');
forms.forEach(form => {
const inputs = form.querySelectorAll('input, textarea, select');
let autoSaveTimer = null;
const AUTO_SAVE_DELAY = 30000; // 30 seconds
inputs.forEach(input => {
input.addEventListener('input', () => {
// Clear existing timer
if (autoSaveTimer) {
clearTimeout(autoSaveTimer);
}
// Set new timer
autoSaveTimer = setTimeout(() => {
const saveButton = form.querySelector('[data-live-action="autoSave"]');
if (saveButton) {
saveButton.click();
}
}, AUTO_SAVE_DELAY);
});
});
});
}
/**
* Initialize all enhancements when DOM is ready
*/
function init() {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
return;
}
initDragAndDrop();
initFilePreview();
initAutoResizeTextareas();
initKeyboardShortcuts();
initAutoSave();
// Expose Toast globally for use in other scripts
window.AdminToast = Toast;
}
// Initialize immediately if DOM is already loaded
init();
// Also initialize when LiveComponents are loaded dynamically
document.addEventListener('livecomponent:loaded', () => {
initDragAndDrop();
initFilePreview();
initAutoResizeTextareas();
});
})();