Fassung 1.6.2: sichtbare leere Layouts und direktes Befuellen im Editor
CMS quality / php (8.2) (push) Canceled after 0s
CMS quality / php (8.4) (push) Canceled after 0s
CMS quality / browser (push) Canceled after 0s
CMS quality / mysql (push) Canceled after 0s

This commit is contained in:
kantarci committed 2026-09-14 09:22:23 +02:00
1 parent 149e1fe520
commit 6a044e59e9
14 files changed
+314 -21

No files matched your search

+46 -10
View File
@@ -4,9 +4,9 @@ import { blockLabel, locate, dropList, isContainer } from './editor-model.js';
// The layer above the preview.
//
// The preview is a sandboxed iframe without scripts of its own, and every change replaces its
// whole document. Anything drawn inside it would therefore be wiped on the next keystroke and
// would also end up in the markup we are supposed to be showing faithfully. So the outline, the
// element toolbar, the insert points and the drop marker all live in the editor document, in an
// whole document. The selection tools therefore live outside it and get rebound after a refresh.
// Empty-container hit areas are rebuilt separately inside the frame by preview-containers.js.
// The outline, element toolbar, insert points and drop marker live in the editor document, in an
// absolutely positioned layer that sits exactly on top of the frame.
//
// Geometry: the layer is the same box as the frame and carries the same scale transform, so a
@@ -50,10 +50,11 @@ export function createCanvasOverlay({ stage, frame, getScale, getDocument, getSe
toolbar.setAttribute('role', 'toolbar');
toolbar.setAttribute('aria-label', 'Aktionen für das gewählte Element');
const drop = element('div', 'ov-drop');
const dropLabel = element('div', 'ov-drop-label');
const insertBefore = insertButton('before', 'Element davor einfügen');
const insertAfter = insertButton('after', 'Element dahinter einfügen');
toolbar.append(selectName);
layer.append(hover, select, toolbar, insertBefore, insertAfter, drop);
layer.append(hover, select, toolbar, insertBefore, insertAfter, drop, dropLabel);
stage.append(layer);
for (const tool of TOOLS) {
@@ -206,7 +207,10 @@ export function createCanvasOverlay({ stage, frame, getScale, getDocument, getSe
positionToolbar(selectedBox, Boolean(sibling));
const hoverBox = hovered && hovered !== selectedId ? rectOf(hovered) : null;
if (place(hover, hoverBox)) paintLabel(hoverLabel, hovered);
if (dragging) paintDrop();
if (dragging) {
dropPlan = dragPoint ? planDrop(dragPoint.x, dragPoint.y, dragging.block) : null;
paintDrop();
}
}
// -------------------------------------------------------------------------------------------
@@ -259,8 +263,9 @@ export function createCanvasOverlay({ stage, frame, getScale, getDocument, getSe
const parentId = item?.parent?.id ?? null;
if (id && !ownBranch && dropList(document_, parentId, block)) {
const box = rectOf(id);
const after = box ? y > box.top + box.height / 2 : false;
return { parentId, index: item.index + (after ? 1 : 0), line: box ? { box, edge: after ? 'bottom' : 'top' } : null };
const horizontal = item.parent?.type === 'row' && indexInside(parentId, x, y).sideBySide;
const after = box ? (horizontal ? x > box.left + box.width / 2 : y > box.top + box.height / 2) : false;
return { parentId, index: item.index + (after ? 1 : 0), line: box ? { box, edge: horizontal ? (after ? 'right' : 'left') : (after ? 'bottom' : 'top') } : null };
}
if (!node) break;
node = item?.parent ? doc()?.getElementById(item.parent.id) : null;
@@ -276,11 +281,19 @@ export function createCanvasOverlay({ stage, frame, getScale, getDocument, getSe
function paintDrop() {
const line = dropPlan?.line;
if (!line?.box) { drop.hidden = true; return; }
if (!line?.box) { drop.hidden = true; dropLabel.hidden = true; return; }
drop.hidden = false;
const scale = getScale();
const thickness = 3 / scale;
const { box, edge } = line;
const target = dropPlan.parentId ? doc()?.getElementById(dropPlan.parentId) : null;
const label = target?.dataset.editorLabel || (dropPlan.parentId ? blockLabel(locate(getDocument().blocks, dropPlan.parentId)?.block) : 'Seite');
dropLabel.textContent = `${edge === 'inside' ? 'Einfügen in' : 'Einfügen ·'} ${label}`;
dropLabel.hidden = false;
dropLabel.style.left = `${Math.max(0, box.left + 8)}px`;
dropLabel.style.top = `${Math.max(2, box.top + 8)}px`;
counterScale(dropLabel, 'left', 'top');
drop.style.marginLeft = drop.style.marginTop = '0';
drop.classList.toggle('ov-drop-inside', edge === 'inside');
if (edge === 'inside') {
drop.style.left = `${box.left}px`; drop.style.top = `${box.top}px`;
@@ -317,15 +330,20 @@ export function createCanvasOverlay({ stage, frame, getScale, getDocument, getSe
document_.addEventListener('dragover', event => {
if (!dragging) return;
event.preventDefault();
event.dataTransfer.dropEffect = dragging.kind === 'move' ? 'move' : 'copy';
dragPoint = { x: event.clientX, y: event.clientY };
dropPlan = planDrop(event.clientX, event.clientY, dragging.block);
event.dataTransfer.dropEffect = dropPlan ? (dragging.kind === 'move' ? 'move' : 'copy') : 'none';
paintDrop();
if (!scrollFrame) scrollFrame = requestAnimationFrame(scrollDrag);
});
document_.addEventListener('dragleave', event => {
if (!event.relatedTarget) { dragPoint = null; dropPlan = null; paintDrop(); }
});
document_.addEventListener('drop', event => {
if (!dragging) return;
event.preventDefault();
// Both are read before the clean-up clears them.
const payload = dragging, plan = dropPlan;
const payload = dragging, plan = planDrop(event.clientX, event.clientY, dragging.block);
endDrag();
if (plan) onDrop(payload, plan);
});
@@ -335,17 +353,35 @@ export function createCanvasOverlay({ stage, frame, getScale, getDocument, getSe
function startDrag(payload) {
dragging = payload;
layer.classList.add('is-dragging');
doc()?.documentElement.classList.add('editor-dragging');
hovered = null;
sync();
}
function endDrag() {
dragging = null;
dragPoint = null;
cancelAnimationFrame(scrollFrame); scrollFrame = null;
dropPlan = null;
drop.hidden = true;
dropLabel.hidden = true;
layer.classList.remove('is-dragging');
doc()?.documentElement.classList.remove('editor-dragging');
sync();
}
let dragPoint = null, scrollFrame = null;
function scrollDrag() {
scrollFrame = null;
if (!dragging || !dragPoint) return;
const edge = 60 / getScale();
const amount = dragPoint.y < edge ? -14 : dragPoint.y > frame.clientHeight - edge ? 14 : 0;
if (amount) {
doc()?.defaultView?.scrollBy(0, amount / getScale());
sync();
scrollFrame = requestAnimationFrame(scrollDrag);
}
}
return { sync, attach, startDrag, endDrag, get dragPayload() { return dragging; } };
}