Fassung 1.6.1: flexible Editorflaeche und passende Blockeigenschaften
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 08:57:42 +02:00
1 parent 05ea703655
commit b783d9d1e5
13 files changed
+276 -11

No files matched your search

+66 -2
View File
@@ -23,6 +23,7 @@ let device = 'desktop', propertyPanel = 'content', designTokens, designSchema, d
// same factor, so it is kept here instead of being recomputed from the transform string.
let previewScale = 1, overlay = null, pendingInsert = null;
const collapsed = new Set();
let renderedProperties = null;
function notify(message = '', recover = false) {
$('#message').textContent = message;
@@ -72,6 +73,7 @@ function controls() {
document.querySelectorAll('[data-panel],[data-viewport],#tree-search,#library-search,#expand-all,#collapse-all').forEach(input => input.disabled = !ready || busy);
$('[data-panel="design"]').disabled ||= !current;
$('#select-page').disabled = !ready || busy;
$('#add-root-section').disabled = !ready || busy;
$('#open-media-library').disabled = !ready || busy;
}
@@ -131,6 +133,9 @@ function highlight(scroll = false) {
function renderProperties() {
const block = locate(history.document.blocks, selected)?.block;
if (!block) propertyPanel = 'content';
const context = `${selected ?? 'page'}:${propertyPanel}`;
if (context !== renderedProperties) $('.right-panel').scrollTop = 0;
renderedProperties = context;
$('#properties-title').textContent = block ? blockLabel(block) : 'Seiteneinstellungen';
const panel = $('#properties'); panel.replaceChildren();
document.querySelectorAll('[data-panel]').forEach(button => button.setAttribute('aria-pressed', String(button.dataset.panel === propertyPanel)));
@@ -325,7 +330,7 @@ function renderProperties() {
}
if (block.type === 'section') {
field('Farbwelt', 'tone', block.layout?.tone || 'paper', { layout: true, choices: { paper: 'Hell · Papier', mint: 'Hellgrün · Akzent', ink: 'Dunkel · Tinte' } });
field('Inhaltsbreite', 'width', block.layout?.width || 'normal', { layout: true, choices: { normal: 'Normal (1180 px)', narrow: 'Schmal (800 px)', wide: 'Breit (1600 px)', full: 'Randlos (volle Breite)' }, help: 'Randlos heißt ohne Rand und ohne Begrenzung – für ein Bild oder eine Farbfläche von Kante zu Kante.' });
field('Inhaltsbreite', 'width', block.layout?.width || 'normal', { layout: true, choices: { normal: 'Websitebreite (aus dem Theme)', narrow: 'Schmal (800 px)', wide: 'Breit (1600 px)', full: 'Randlos (volle Breite)' }, help: 'Randlos nutzt die gesamte Sektion. Text und Überschriften füllen ihren Container; eine eigene Begrenzung stellst du unter Design → Größe ein.' });
}
}
@@ -545,9 +550,9 @@ function stopInlineEdit() {
function resizePreview() {
const container = $('.canvas-scroll'), frame = $('#preview'), stage = $('#preview-stage');
const width = { desktop: 1240, tablet: 768, mobile: 390 }[device];
const padding = parseFloat(getComputedStyle(container).paddingLeft) * 2;
const available = Math.max(100, container.clientWidth - padding);
const width = { desktop: Math.max(1240, available), tablet: 768, mobile: 390 }[device];
const scale = Math.min(1, available / width);
const height = Math.max(550, (container.clientHeight - padding) / scale);
previewScale = scale;
@@ -830,6 +835,13 @@ $('#library').addEventListener('click', event => {
addElement(button.dataset.add);
});
$('#cancel-insert').addEventListener('click', () => setPendingInsert(null));
$('#add-root-section').addEventListener('click', () => {
if (busy || !history) return;
stopInlineEdit();
setPendingInsert(null);
selected = history.change(doc => insertBlock(doc, 'section', selected));
propertyPanel = 'content'; changed();
});
$('#presets').addEventListener('click', event => {
const name = event.target.closest('[data-preset]')?.dataset.preset;
if (!name || busy || !history) return;
@@ -1003,6 +1015,58 @@ function setViewport(name) {
}
document.querySelectorAll('[data-viewport]').forEach(button => button.addEventListener('click', () => setViewport(button.dataset.viewport)));
new ResizeObserver(resizePreview).observe($('.canvas-scroll'));
// Panel sizes are workspace preferences, independent of document history and publication.
const panelSizes = { left: 280, right: 320 };
try {
const saved = JSON.parse(localStorage.getItem('kantsites.panel-widths'));
for (const side of ['left', 'right']) if (Number.isFinite(saved?.[side])) panelSizes[side] = saved[side];
} catch { /* Storage can be unavailable; resizing still works for this session. */ }
const panelMinimum = { left: 240, right: 280 };
function layoutPanels() {
let left = Math.max(240, Math.min(560, panelSizes.left));
let right = Math.max(280, Math.min(560, panelSizes.right));
const budget = Math.max(520, $('.editor-layout').clientWidth - 292);
const excess = Math.max(0, left + right - budget);
const spare = left + right - 520;
if (excess && spare) { left -= excess * (left - 240) / spare; right -= excess * (right - 280) / spare; }
for (const [side, size] of Object.entries({ left, right })) {
root.style.setProperty(`--panel-${side}`, `${size}px`);
const handle = $(`[data-resize="${side}"]`);
handle.setAttribute('aria-valuenow', String(Math.round(size)));
handle.setAttribute('aria-valuemax', String(Math.floor(Math.min(560, budget - (side === 'left' ? right : left)))));
handle.setAttribute('aria-valuetext', `${Math.round(size)} Pixel`);
}
}
const persistPanels = () => { try { localStorage.setItem('kantsites.panel-widths', JSON.stringify(panelSizes)); } catch {} };
for (const handle of document.querySelectorAll('[data-resize]')) {
const side = handle.dataset.resize, direction = side === 'left' ? 1 : -1;
let drag = null;
const setSize = value => {
panelSizes[side] = Math.max(panelMinimum[side], Math.min(Number(handle.getAttribute('aria-valuemax')), value));
layoutPanels();
};
handle.addEventListener('pointerdown', event => {
if (event.button !== 0) return;
event.preventDefault(); handle.focus();
drag = { x: event.clientX, size: Number(handle.getAttribute('aria-valuenow')) };
handle.setPointerCapture(event.pointerId); root.classList.add('resizing-panels');
});
handle.addEventListener('pointermove', event => { if (drag) setSize(drag.size + direction * (event.clientX - drag.x)); });
const stop = () => { drag = null; root.classList.remove('resizing-panels'); persistPanels(); };
handle.addEventListener('pointerup', stop);
handle.addEventListener('pointercancel', stop);
handle.addEventListener('lostpointercapture', stop);
handle.addEventListener('dblclick', () => { panelSizes[side] = side === 'left' ? 280 : 320; layoutPanels(); persistPanels(); });
handle.addEventListener('keydown', event => {
if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return;
event.preventDefault();
const current = Number(handle.getAttribute('aria-valuenow'));
setSize(event.key === 'Home' ? panelMinimum[side] : event.key === 'End' ? 560 : current + (event.key === 'ArrowRight' ? 1 : -1) * direction * (event.shiftKey ? 40 : 10));
persistPanels();
});
}
new ResizeObserver(layoutPanels).observe($('.editor-layout'));
layoutPanels();
window.addEventListener('keydown', event => {
if (!history || busy || !(event.ctrlKey || event.metaKey)) return;
const key = event.key.toLowerCase();