fix: DockerSecretsResolver - don't normalize absolute paths like /var/www/html/...
Some checks failed
Deploy Application / deploy (push) Has been cancelled
Some checks failed
Deploy Application / deploy (push) Has been cancelled
This commit is contained in:
317
resources/js/modules/admin/block-editor-dnd.js
Normal file
317
resources/js/modules/admin/block-editor-dnd.js
Normal file
@@ -0,0 +1,317 @@
|
||||
/**
|
||||
* Block Editor Drag & Drop
|
||||
*
|
||||
* Native HTML5 Drag & Drop API implementation for reordering CMS blocks
|
||||
*/
|
||||
|
||||
export class BlockEditorDnD {
|
||||
constructor(container) {
|
||||
this.container = container;
|
||||
this.blocksContainer = container.querySelector('[data-sortable="blocks"]');
|
||||
this.draggedElement = null;
|
||||
this.dragOverIndex = null;
|
||||
this.componentId = null;
|
||||
|
||||
// Find component ID from parent LiveComponent
|
||||
const liveComponent = container.closest('[data-live-component]');
|
||||
if (liveComponent) {
|
||||
this.componentId = liveComponent.dataset.liveComponent;
|
||||
}
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
if (!this.blocksContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make all block cards draggable
|
||||
this.updateDraggableElements();
|
||||
|
||||
// Listen for dynamically added blocks (LiveComponent updates)
|
||||
this.observeBlockChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make all block cards draggable and add event listeners
|
||||
*/
|
||||
updateDraggableElements() {
|
||||
const blockCards = this.blocksContainer.querySelectorAll('.admin-block-card');
|
||||
|
||||
blockCards.forEach((card, index) => {
|
||||
// Set draggable attribute
|
||||
card.draggable = true;
|
||||
card.dataset.blockIndex = index;
|
||||
|
||||
// Remove existing listeners to avoid duplicates
|
||||
const newCard = card.cloneNode(true);
|
||||
card.parentNode.replaceChild(newCard, card);
|
||||
|
||||
// Add drag event listeners to card
|
||||
newCard.addEventListener('dragstart', (e) => this.handleDragStart(e, newCard));
|
||||
newCard.addEventListener('dragend', (e) => this.handleDragEnd(e, newCard));
|
||||
newCard.addEventListener('dragover', (e) => this.handleDragOver(e, newCard));
|
||||
newCard.addEventListener('dragenter', (e) => this.handleDragEnter(e, newCard));
|
||||
newCard.addEventListener('dragleave', (e) => this.handleDragLeave(e, newCard));
|
||||
newCard.addEventListener('drop', (e) => this.handleDrop(e, newCard));
|
||||
|
||||
// Prevent drag start on interactive elements, but allow it on drag handle
|
||||
const interactiveElements = newCard.querySelectorAll('button, input, textarea, select, a');
|
||||
interactiveElements.forEach(el => {
|
||||
// Allow drag handle to start drag
|
||||
if (el.closest('[data-drag-handle="true"]')) {
|
||||
return;
|
||||
}
|
||||
|
||||
el.addEventListener('mousedown', (e) => {
|
||||
// Prevent drag when clicking on interactive elements
|
||||
e.stopPropagation();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Observe DOM changes to update draggable elements when blocks are added/removed
|
||||
*/
|
||||
observeBlockChanges() {
|
||||
const observer = new MutationObserver(() => {
|
||||
this.updateDraggableElements();
|
||||
});
|
||||
|
||||
observer.observe(this.blocksContainer, {
|
||||
childList: true,
|
||||
subtree: false
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle drag start
|
||||
*/
|
||||
handleDragStart(e, card) {
|
||||
// Don't start drag if clicking on buttons or inputs
|
||||
if (e.target.closest('button, input, textarea, select')) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
this.draggedElement = card;
|
||||
card.classList.add('admin-block-card--dragging');
|
||||
|
||||
// Set drag data
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
e.dataTransfer.setData('text/html', card.outerHTML);
|
||||
e.dataTransfer.setData('text/plain', card.dataset.blockId);
|
||||
|
||||
// Add visual feedback
|
||||
e.dataTransfer.setDragImage(card, 0, 0);
|
||||
|
||||
// Add placeholder styling
|
||||
this.blocksContainer.classList.add('admin-block-editor__blocks--dragging');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle drag end
|
||||
*/
|
||||
handleDragEnd(e, card) {
|
||||
card.classList.remove('admin-block-card--dragging');
|
||||
this.blocksContainer.classList.remove('admin-block-editor__blocks--dragging');
|
||||
|
||||
// Remove all drag-over classes
|
||||
const allCards = this.blocksContainer.querySelectorAll('.admin-block-card');
|
||||
allCards.forEach(c => {
|
||||
c.classList.remove('admin-block-card--drag-over');
|
||||
c.classList.remove('admin-block-card--drag-over-before');
|
||||
c.classList.remove('admin-block-card--drag-over-after');
|
||||
});
|
||||
|
||||
this.draggedElement = null;
|
||||
this.dragOverIndex = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle drag over
|
||||
*/
|
||||
handleDragOver(e, card) {
|
||||
if (this.draggedElement === card) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
|
||||
// Calculate drop position
|
||||
const cardRect = card.getBoundingClientRect();
|
||||
const mouseY = e.clientY;
|
||||
const cardMiddleY = cardRect.top + cardRect.height / 2;
|
||||
|
||||
// Determine if we're dropping before or after this card
|
||||
const isBefore = mouseY < cardMiddleY;
|
||||
|
||||
// Remove all drag-over classes
|
||||
const allCards = this.blocksContainer.querySelectorAll('.admin-block-card');
|
||||
allCards.forEach(c => {
|
||||
c.classList.remove('admin-block-card--drag-over');
|
||||
c.classList.remove('admin-block-card--drag-over-before');
|
||||
c.classList.remove('admin-block-card--drag-over-after');
|
||||
});
|
||||
|
||||
// Add appropriate class
|
||||
if (isBefore) {
|
||||
card.classList.add('admin-block-card--drag-over-before');
|
||||
} else {
|
||||
card.classList.add('admin-block-card--drag-over-after');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle drag enter
|
||||
*/
|
||||
handleDragEnter(e, card) {
|
||||
if (this.draggedElement === card) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle drag leave
|
||||
*/
|
||||
handleDragLeave(e, card) {
|
||||
// Only remove classes if we're actually leaving the card
|
||||
if (!card.contains(e.relatedTarget)) {
|
||||
card.classList.remove('admin-block-card--drag-over');
|
||||
card.classList.remove('admin-block-card--drag-over-before');
|
||||
card.classList.remove('admin-block-card--drag-over-after');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle drop
|
||||
*/
|
||||
handleDrop(e, targetCard) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (this.draggedElement === targetCard || !this.draggedElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all block cards in current order
|
||||
const allCards = Array.from(this.blocksContainer.querySelectorAll('.admin-block-card'));
|
||||
const draggedIndex = allCards.indexOf(this.draggedElement);
|
||||
const targetIndex = allCards.indexOf(targetCard);
|
||||
|
||||
// Calculate final position based on drop zone
|
||||
const cardRect = targetCard.getBoundingClientRect();
|
||||
const mouseY = e.clientY;
|
||||
const cardMiddleY = cardRect.top + cardRect.height / 2;
|
||||
const isBefore = mouseY < cardMiddleY;
|
||||
|
||||
let finalIndex = targetIndex;
|
||||
if (isBefore && draggedIndex > targetIndex) {
|
||||
finalIndex = targetIndex;
|
||||
} else if (!isBefore && draggedIndex < targetIndex) {
|
||||
finalIndex = targetIndex + 1;
|
||||
} else if (isBefore && draggedIndex < targetIndex) {
|
||||
finalIndex = targetIndex;
|
||||
} else {
|
||||
finalIndex = targetIndex + 1;
|
||||
}
|
||||
|
||||
// Extract block IDs in new order
|
||||
const blockIds = allCards.map(card => card.dataset.blockId);
|
||||
|
||||
// Remove dragged element from array
|
||||
blockIds.splice(draggedIndex, 1);
|
||||
|
||||
// Insert at new position
|
||||
blockIds.splice(finalIndex, 0, this.draggedElement.dataset.blockId);
|
||||
|
||||
// Call LiveComponent action to reorder blocks
|
||||
this.reorderBlocks(blockIds);
|
||||
|
||||
// Clean up
|
||||
this.handleDragEnd(e, targetCard);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call LiveComponent action to reorder blocks
|
||||
*/
|
||||
reorderBlocks(blockIds) {
|
||||
if (!this.componentId) {
|
||||
console.error('BlockEditorDnD: No component ID found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the LiveComponent instance
|
||||
const liveComponentElement = document.querySelector(`[data-live-component="${this.componentId}"]`);
|
||||
if (!liveComponentElement) {
|
||||
console.error('BlockEditorDnD: LiveComponent element not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the LiveComponent API if available
|
||||
if (window.liveComponentManager) {
|
||||
const manager = window.liveComponentManager;
|
||||
const component = manager.getComponent(this.componentId);
|
||||
|
||||
if (component) {
|
||||
// Call action directly
|
||||
component.executeAction('reorderBlocks', { blockIds: blockIds });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: Create a temporary button to trigger the action
|
||||
const actionButton = document.createElement('button');
|
||||
actionButton.type = 'button';
|
||||
actionButton.dataset.liveAction = 'reorderBlocks';
|
||||
actionButton.dataset.liveComponent = this.componentId;
|
||||
actionButton.style.display = 'none';
|
||||
|
||||
// Set block IDs as JSON string in data attribute
|
||||
// The parameter binder will convert data-param-block-ids to blockIds
|
||||
actionButton.dataset.paramBlockIds = JSON.stringify(blockIds);
|
||||
|
||||
liveComponentElement.appendChild(actionButton);
|
||||
|
||||
// Trigger click event (LiveComponent handler will pick it up)
|
||||
actionButton.click();
|
||||
|
||||
// Remove temporary button after a short delay
|
||||
setTimeout(() => {
|
||||
actionButton.remove();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all block editors on the page
|
||||
*/
|
||||
static initializeAll() {
|
||||
const containers = document.querySelectorAll('.admin-block-editor');
|
||||
const instances = [];
|
||||
|
||||
containers.forEach(container => {
|
||||
instances.push(new BlockEditorDnD(container));
|
||||
});
|
||||
|
||||
return instances;
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-initialize when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
BlockEditorDnD.initializeAll();
|
||||
});
|
||||
} else {
|
||||
BlockEditorDnD.initializeAll();
|
||||
}
|
||||
|
||||
// Re-initialize when LiveComponents are loaded dynamically
|
||||
document.addEventListener('livecomponent:loaded', () => {
|
||||
BlockEditorDnD.initializeAll();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user