Fassung 1.6.2: sichtbare leere Layouts und direktes Befuellen im Editor
This commit is contained in:
1 parent
149e1fe520
commit
6a044e59e9
14 files changed
+314
-21
No files matched your search
+56
-8
@@ -4,6 +4,7 @@ import { createDesignPanel } from './design-panel.js';
|
||||
import { createRichText } from './rich-text.js';
|
||||
import { createCanvasOverlay } from './canvas-overlay.js';
|
||||
import { preview, rowPreview } from './library-previews.js';
|
||||
import { decorateContainers } from './preview-containers.js';
|
||||
|
||||
// Mirrors DocumentValidator::ICONS; the server rejects anything outside this list.
|
||||
const ICONS = ['app', 'web', 'design', 'network', 'hardware', 'shop', 'support', 'shield', 'clock', 'star', 'phone', 'mail', 'pin', 'check'];
|
||||
@@ -24,6 +25,7 @@ let device = 'desktop', propertyPanel = 'content', designTokens, designSchema, d
|
||||
let previewScale = 1, overlay = null, pendingInsert = null;
|
||||
const collapsed = new Set();
|
||||
let renderedProperties = null;
|
||||
let revealAfterPreview = null;
|
||||
|
||||
function notify(message = '', recover = false) {
|
||||
$('#message').textContent = message;
|
||||
@@ -114,6 +116,7 @@ function renderTree() {
|
||||
|
||||
function choose(id, scroll = true) {
|
||||
if (busy) return;
|
||||
if (id !== selected && pendingInsert) setPendingInsert(null);
|
||||
selected = locate(history.document.blocks, id) ? id : null;
|
||||
blockPath(history.document.blocks, selected).slice(0, -1).forEach(block => collapsed.delete(block.id));
|
||||
if (!selected) propertyPanel = 'content';
|
||||
@@ -595,7 +598,13 @@ async function updatePreview() {
|
||||
const doc = frame.contentDocument;
|
||||
if (!doc || doc === altesDokument || !doc.getElementById('kant-styles')) return;
|
||||
angewandt = true;
|
||||
const style = doc.createElement('link'); style.rel = 'stylesheet'; style.href = '/assets/preview.css'; doc.head.append(style);
|
||||
const style = doc.createElement('link'); style.rel = 'stylesheet'; style.href = `/assets/preview.css?v=${new URL(import.meta.url).searchParams.get('v') ?? 'dev'}`;
|
||||
// Register before insertion: cached loads (or errors) can precede DOMContentLoaded.
|
||||
const styleReady = new Promise(resolve => {
|
||||
style.addEventListener('load', () => resolve(true), { once: true });
|
||||
style.addEventListener('error', () => resolve(false), { once: true });
|
||||
});
|
||||
doc.head.append(style);
|
||||
// The preview delivers the generated stylesheet as inert template text (see page.html.twig).
|
||||
// Applying it as a constructed stylesheet is not a source in the sense of the policy, so the
|
||||
// preview shows exactly what the published page shows, without a single blocked style.
|
||||
@@ -607,6 +616,22 @@ async function updatePreview() {
|
||||
}
|
||||
doc.addEventListener('click', event => {
|
||||
event.preventDefault();
|
||||
const insert = event.target.closest('[data-editor-target]');
|
||||
if (insert && !busy && history) {
|
||||
stopInlineEdit();
|
||||
const id = insert.dataset.editorTarget;
|
||||
if (!locate(history.document.blocks, id)) return;
|
||||
choose(id, false);
|
||||
if (insert.hasAttribute('data-editor-row')) {
|
||||
setPendingInsert(null);
|
||||
selected = history.change(content => insertRowAt(content, Number(insert.dataset.editorRow), id, 0));
|
||||
propertyPanel = 'content'; changed();
|
||||
} else if (locate(history.document.blocks, id).block.type === 'row') {
|
||||
selected = history.change(content => insertBlockAt(content, 'column', id, 0));
|
||||
changed();
|
||||
} else setPendingInsert({ parentId: id, index: 0 });
|
||||
return;
|
||||
}
|
||||
if (inlineEdit) return;
|
||||
const element = event.target.closest('[id]');
|
||||
if (element && locate(history.document.blocks, element.id)) choose(element.id, false);
|
||||
@@ -618,12 +643,23 @@ async function updatePreview() {
|
||||
if (block && INLINE_TEXT[block.type]) { event.preventDefault(); choose(element.id, false); startInlineEdit(element.id); }
|
||||
});
|
||||
doc.addEventListener('submit', event => event.preventDefault());
|
||||
// Every refresh is a new document, so the layer above it has to bind to it again.
|
||||
overlay?.attach();
|
||||
frame.contentWindow.scrollTo(0, top); highlight();
|
||||
// Erst jetzt ansehen lassen: bis hierher zeigte das Fenster eine Seite ohne Gestaltung.
|
||||
frame.style.visibility = '';
|
||||
$('#preview-status').textContent = 'Entwurf · Klicken wählt aus, Doppelklick bearbeitet Text, Elemente lassen sich hierher ziehen.';
|
||||
// The head may arrive before the blocks. Decorate only once the complete DOM exists.
|
||||
const finish = () => {
|
||||
if (sequence !== previewSequence || doc !== frame.contentDocument) return;
|
||||
decorateContainers(doc, history.document);
|
||||
overlay?.attach();
|
||||
styleReady.then(loaded => {
|
||||
if (sequence !== previewSequence || doc !== frame.contentDocument) return;
|
||||
frame.contentWindow.scrollTo(0, top);
|
||||
highlight(revealAfterPreview === selected); revealAfterPreview = null;
|
||||
markInsertTarget();
|
||||
frame.style.visibility = '';
|
||||
$('#preview-status').textContent = loaded
|
||||
? 'Entwurf · Klicken wählt aus, Doppelklick bearbeitet Text, Elemente lassen sich hierher ziehen.'
|
||||
: 'Editor-Hilfsflächen konnten nicht geladen werden. Bitte den Editor neu laden.';
|
||||
});
|
||||
};
|
||||
if (doc.readyState === 'loading') doc.addEventListener('DOMContentLoaded', finish, { once: true }); else finish();
|
||||
};
|
||||
// Solange die Gestaltung fehlt, bleibt die Fläche leer statt falsch.
|
||||
frame.style.visibility = 'hidden';
|
||||
@@ -654,6 +690,7 @@ async function updatePreview() {
|
||||
|
||||
function changed() {
|
||||
if (!locate(history.document.blocks, selected)) selected = null;
|
||||
if (selected && !$('#preview').contentDocument?.getElementById(selected)) revealAfterPreview = selected;
|
||||
renderTree(); renderProperties(); controls(); schedulePreview();
|
||||
}
|
||||
|
||||
@@ -727,12 +764,23 @@ function setPendingInsert(target) {
|
||||
updateLibrary();
|
||||
$('#toolbox').classList.toggle('awaiting-insert', Boolean(target));
|
||||
$('#insert-hint').hidden = !target;
|
||||
markInsertTarget();
|
||||
if (target) {
|
||||
if ($('#toolbox').hidden) $('#toggle-library').click();
|
||||
$('#toolbox').scrollIntoView({ block: 'nearest' });
|
||||
$('#library-search').value = ''; filterLibrary();
|
||||
const name = target.parentId ? $('#preview').contentDocument?.getElementById(target.parentId)?.dataset.editorLabel || blockLabel(locate(history.document.blocks, target.parentId)?.block) : 'Seite';
|
||||
$('#insert-hint').firstChild.textContent = `Einfügen in ${name}. Wähle jetzt ein Element. `;
|
||||
$('#library-search').focus({ preventScroll: true });
|
||||
$('#library-search').scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
}
|
||||
|
||||
function markInsertTarget() {
|
||||
const doc = $('#preview').contentDocument;
|
||||
doc?.querySelectorAll('.editor-insert-target').forEach(node => node.classList.remove('editor-insert-target'));
|
||||
if (pendingInsert?.parentId) doc?.getElementById(pendingInsert.parentId)?.classList.add('editor-insert-target');
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// The toolbox.
|
||||
//
|
||||
|
||||
Reference in new issue
Block a user