';
return;
}
var html = blocks.map(function(block, index) {
return self.renderCanvasBlock(block, index, blocks.length);
}).join('');
html += '
'
+ ''
+ '
';
canvas.innerHTML = html;
this.bindCanvasEvents();
}
renderCanvasBlock(block, index, total) {
var preview = BlockRenderer.renderBlock(block);
var isFirst = index === 0;
var isLast = index === total - 1;
var typeLabel = this.blockTypeLabel(block.type);
return '
'
+ '
'
+ '' + typeLabel + ''
+ '
'
+ ''
+ ''
+ ''
+ ''
+ '
'
+ '
'
+ '
' + preview + '
'
+ '
'
+ ''
+ '
'
+ '
';
}
bindCanvasEvents() {
var self = this;
document.querySelectorAll('[data-action]').forEach(function(btn) {
btn.addEventListener('click', function() {
var action = btn.dataset.action;
var idx = parseInt(btn.dataset.index);
if (action === 'edit') self.openEditModal(idx);
if (action === 'delete') self.deleteBlock(idx);
if (action === 'up') self.moveBlock(idx, -1);
if (action === 'down') self.moveBlock(idx, 1);
});
});
document.querySelectorAll('.insert-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
self.pendingInsertIndex = parseInt(btn.dataset.index);
self.highlightPalette();
});
});
}
highlightPalette() {
var palette = document.querySelector('.palette-blocks');
palette.classList.add('palette-highlight');
setTimeout(function() { palette.classList.remove('palette-highlight'); }, 1200);
palette.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
// ββ Block CRUD βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
addBlock(type) {
var block = this.createDefaultBlock(type);
var blocks = this.sections[this.currentSection].blocks;
var atIdx = (this.pendingInsertIndex !== undefined) ? this.pendingInsertIndex : blocks.length;
this.pendingInsertIndex = undefined;
if (atIdx >= 0 && atIdx <= blocks.length) {
blocks.splice(atIdx, 0, block);
} else {
blocks.push(block);
}
this.renderCanvas();
var newIdx = blocks.indexOf(block);
this.openEditModal(newIdx);
}
createDefaultBlock(type) {
var id = 'b' + Date.now();
switch (type) {
case 'heading': return { id: id, type: type, level: 2, text: 'New Heading' };
case 'paragraph': return { id: id, type: type, text: 'Enter paragraph text here.' };
case 'image': return { id: id, type: type, src: '', alt: '', caption: '' };
case 'quote': return { id: id, type: type, text: 'Quote text here.', author: 'Source' };
case 'gallery': return { id: id, type: type, images: [{ src: '', alt: '', caption: '' }] };
case 'grid4': return { id: id, type: type, images: [
{ src: '', alt: '', caption: '' }, { src: '', alt: '', caption: '' },
{ src: '', alt: '', caption: '' }, { src: '', alt: '', caption: '' }
]};
case 'statistics': return { id: id, type: type, stats: [{ number: '0', label: 'Label' }, { number: '0', label: 'Label' }] };
case 'map-button': return { id: id, type: type, label: 'View on Map', lat: 29.3759, lng: 47.9774, zoom: 6 };
default: return { id: id, type: type };
}
}
deleteBlock(index) {
if (!confirm('Delete this block?')) return;
this.sections[this.currentSection].blocks.splice(index, 1);
this.renderCanvas();
}
moveBlock(index, direction) {
var blocks = this.sections[this.currentSection].blocks;
var newIndex = index + direction;
if (newIndex < 0 || newIndex >= blocks.length) return;
var tmp = blocks[index]; blocks[index] = blocks[newIndex]; blocks[newIndex] = tmp;
this.renderCanvas();
// Scroll moved block into view
var el = document.querySelector('[data-index="' + newIndex + '"]');
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
// ββ Modal Editor βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
openEditModal(index) {
this.editingIndex = index;
var block = this.sections[this.currentSection].blocks[index];
document.getElementById('modalTitle').textContent = 'Edit: ' + this.blockTypeLabel(block.type);
document.getElementById('modalBody').innerHTML = this.buildModalForm(block);
document.getElementById('builderModal').classList.remove('hidden');
this.bindDynamicFormEvents(document.getElementById('modalBody'));
var first = document.getElementById('modalBody').querySelector('input, textarea, select');
if (first) first.focus();
}
buildModalForm(block) {
switch (block.type) {
case 'heading':
return this.formGroup('Level',
'')
+ this.formGroup('Text', '');
case 'paragraph':
return this.formGroup('Text', '');
case 'image':
return this.formGroup('Image URL', '')
+ this.formGroup('Alt Text (accessibility)', '')
+ this.formGroup('Caption (optional)', '');
case 'quote':
return this.formGroup('Quote Text', '')
+ this.formGroup('Attribution (optional)', '');
case 'gallery':
return this.formGroup('Gallery Images',
'