adsad
This commit is contained in:
@@ -502,6 +502,12 @@
|
||||
// 🚨 KRITISCH: Server erwartet das Feld 'json'
|
||||
json: jsonProjectDataRaw,
|
||||
};
|
||||
const activateNext = B.NEXT_ACTIVATE_VERSION || window.NEXT_ACTIVATE_VERSION;
|
||||
if (activateNext) {
|
||||
dataToSend.activate_version = 1;
|
||||
B.NEXT_ACTIVATE_VERSION = 0;
|
||||
window.NEXT_ACTIVATE_VERSION = 0;
|
||||
}
|
||||
if (SECTION_ID) {
|
||||
dataToSend.section_id = SECTION_ID;
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@
|
||||
return [];
|
||||
}
|
||||
const promises = sections.map(section =>
|
||||
fetchData('content', 'list', { section_id: section.id })
|
||||
fetchData('content', 'list', { section_id: section.id, active_only: 1 })
|
||||
.then(items => (Array.isArray(items) ? items : []).map(i => ({
|
||||
...i,
|
||||
kind: String(section.slug || '').toLowerCase(),
|
||||
|
||||
@@ -33,6 +33,11 @@ export function initEditor() {
|
||||
const btnUnsavedCancel = document.getElementById('btn-unsaved-cancel');
|
||||
const btnUnsavedDiscard = document.getElementById('btn-unsaved-discard');
|
||||
const btnUnsavedSave = document.getElementById('btn-unsaved-save');
|
||||
const btnDeactivateVersion = document.getElementById('btn-deactivate-version');
|
||||
const activateDialog = document.getElementById('activateVersionDialog');
|
||||
const btnActivateCancel = document.getElementById('btn-activate-cancel');
|
||||
const btnActivateNo = document.getElementById('btn-activate-no');
|
||||
const btnActivateYes = document.getElementById('btn-activate-yes');
|
||||
|
||||
let current = null; // { resource, id, name, section }
|
||||
let bridgeListener = null;
|
||||
@@ -49,6 +54,9 @@ export function initEditor() {
|
||||
let suppressDirty = false;
|
||||
let suppressTimer = null;
|
||||
let baselineReady = false;
|
||||
let versionMap = new Map();
|
||||
let currentVersionId = 0;
|
||||
let currentVersionMeta = null;
|
||||
|
||||
const ok = (m) => toast(m, true);
|
||||
const err = (m) => toast(m, false);
|
||||
@@ -94,6 +102,7 @@ export function initEditor() {
|
||||
function setVersionUiVisible(show) {
|
||||
if (versionSelect) versionSelect.classList.toggle('hidden', !show);
|
||||
// restore button removed
|
||||
if (btnDeactivateVersion) btnDeactivateVersion.classList.toggle('hidden', !show);
|
||||
}
|
||||
|
||||
setVersionUiVisible(false);
|
||||
@@ -262,11 +271,49 @@ export function initEditor() {
|
||||
return choice;
|
||||
}
|
||||
|
||||
function showActivateDialog() {
|
||||
return new Promise((resolve) => {
|
||||
if (!activateDialog || typeof activateDialog.showModal !== 'function') {
|
||||
resolve('no');
|
||||
return;
|
||||
}
|
||||
const cleanup = () => {
|
||||
btnActivateCancel && btnActivateCancel.removeEventListener('click', onCancel);
|
||||
btnActivateNo && btnActivateNo.removeEventListener('click', onNo);
|
||||
btnActivateYes && btnActivateYes.removeEventListener('click', onYes);
|
||||
};
|
||||
const closeWith = (choice) => {
|
||||
cleanup();
|
||||
activateDialog.close();
|
||||
resolve(choice);
|
||||
};
|
||||
const onCancel = () => closeWith('cancel');
|
||||
const onNo = () => closeWith('no');
|
||||
const onYes = () => closeWith('yes');
|
||||
btnActivateCancel && btnActivateCancel.addEventListener('click', onCancel);
|
||||
btnActivateNo && btnActivateNo.addEventListener('click', onNo);
|
||||
btnActivateYes && btnActivateYes.addEventListener('click', onYes);
|
||||
activateDialog.showModal();
|
||||
});
|
||||
}
|
||||
|
||||
function updateVersionMeta(id) {
|
||||
const key = id ? String(id) : '';
|
||||
currentVersionId = id ? Number(id) : 0;
|
||||
currentVersionMeta = key && versionMap.has(key) ? versionMap.get(key) : null;
|
||||
if (btnDeactivateVersion) {
|
||||
const isActive = !!(currentVersionMeta && Number(currentVersionMeta.is_active) === 1);
|
||||
btnDeactivateVersion.classList.toggle('hidden', !isActive);
|
||||
}
|
||||
}
|
||||
|
||||
function renderVersionOptions(items) {
|
||||
versionItems = items || [];
|
||||
if (!versionSelect) return;
|
||||
versionMap = new Map();
|
||||
if (!versionSelect) return '';
|
||||
const rows = Array.isArray(versionItems) ? versionItems : [];
|
||||
versionSelect.innerHTML = '';
|
||||
lastVersionSelection = '';
|
||||
if (!rows.length) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = '';
|
||||
@@ -274,33 +321,41 @@ export function initEditor() {
|
||||
opt.disabled = true;
|
||||
versionSelect.appendChild(opt);
|
||||
versionSelect.disabled = true;
|
||||
return;
|
||||
updateVersionMeta(0);
|
||||
return '';
|
||||
}
|
||||
versionSelect.disabled = false;
|
||||
rows.forEach((item, idx) => {
|
||||
let activeId = '';
|
||||
rows.forEach((item) => {
|
||||
const opt = document.createElement('option');
|
||||
const label = `#${item.version_no} – ${formatVersionDate(item.created_at)}`;
|
||||
const label = `#${item.version_no} – ${formatVersionDate(item.created_at)}` + (Number(item.is_active) === 1 ? ' (aktiv)' : '');
|
||||
opt.value = String(item.id);
|
||||
opt.textContent = label;
|
||||
versionSelect.appendChild(opt);
|
||||
if (!lastVersionSelection && idx === 0) {
|
||||
lastVersionSelection = String(item.id);
|
||||
}
|
||||
versionMap.set(String(item.id), item);
|
||||
if (Number(item.is_active) === 1 && !activeId) activeId = String(item.id);
|
||||
});
|
||||
if (lastVersionSelection) versionSelect.value = lastVersionSelection;
|
||||
const fallbackId = activeId || (rows[0] ? String(rows[0].id) : '');
|
||||
if (fallbackId) {
|
||||
lastVersionSelection = fallbackId;
|
||||
versionSelect.value = fallbackId;
|
||||
updateVersionMeta(Number(fallbackId));
|
||||
}
|
||||
return lastVersionSelection;
|
||||
}
|
||||
|
||||
async function loadVersionsForCurrent() {
|
||||
if (!current?.id) {
|
||||
renderVersionOptions([]);
|
||||
return;
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
const res = await apiAction('content_versions.list', { method: 'GET', data: { content_id: current.id, id: current.id } });
|
||||
if (!res?.ok) throw new Error(res?.error || 'Versionen konnten nicht geladen werden');
|
||||
renderVersionOptions(Array.isArray(res?.items) ? res.items : []);
|
||||
return renderVersionOptions(Array.isArray(res?.items) ? res.items : []);
|
||||
} catch {
|
||||
renderVersionOptions([]);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,9 +649,11 @@ export function initEditor() {
|
||||
// Overlay zeigen
|
||||
showVeil();
|
||||
|
||||
// Daten parallel laden (fresh HTML + kontextgefilterte Snippets + Referenzen)
|
||||
let fresh = '';
|
||||
let snippets = [];
|
||||
const requestedVersionId = Number(item?.version_id || item?.versionId || 0);
|
||||
|
||||
// Daten parallel laden (fresh HTML + kontextgefilterte Snippets + Referenzen)
|
||||
let fresh = '';
|
||||
let snippets = [];
|
||||
let refLib = { sections: [], blocks: [] };
|
||||
let hasJson = false;
|
||||
let jsonState = '';
|
||||
@@ -604,6 +661,7 @@ export function initEditor() {
|
||||
let editorType = 'grapesjs';
|
||||
let craftJson = '';
|
||||
|
||||
let defaultVersionId = '';
|
||||
await Promise.all([
|
||||
(async() => {
|
||||
try {
|
||||
@@ -624,9 +682,25 @@ export function initEditor() {
|
||||
})(),
|
||||
(async() => { snippets = await buildSnippetsForContext(current); })(),
|
||||
(async() => { refLib = await buildRefLibForContext(current); })(),
|
||||
(async() => { await loadVersionsForCurrent(); })()
|
||||
(async() => { defaultVersionId = await loadVersionsForCurrent(); })()
|
||||
]);
|
||||
|
||||
const effectiveVersionId = requestedVersionId ? String(requestedVersionId) : defaultVersionId;
|
||||
if (effectiveVersionId) {
|
||||
try {
|
||||
const res = await apiAction('content_versions.get', { method: 'GET', data: { id: effectiveVersionId, content_id: current.id } });
|
||||
if (res?.ok && res?.item) {
|
||||
const fields = extractContentFields(res.item);
|
||||
fresh = fields.html || '';
|
||||
jsonState = fields.json || '';
|
||||
editorType = fields.editorType || editorType;
|
||||
craftJson = fields.craftJson || '';
|
||||
updateVersionMeta(Number(effectiveVersionId));
|
||||
lastVersionSelection = String(effectiveVersionId);
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
editorType = editorType === 'craftjs' ? 'craftjs' : 'grapesjs';
|
||||
setSavedSnapshotFromData({ html: fresh, content: jsonState, editor_type: editorType, craft_json: craftJson });
|
||||
setEditorType(editorType);
|
||||
@@ -744,12 +818,20 @@ export function initEditor() {
|
||||
async function save() {
|
||||
if (!current?.id) return err('Keine aktive ID');
|
||||
|
||||
let activateNext = false;
|
||||
if (currentVersionMeta && (Number(currentVersionMeta.is_active) === 1 || Number(currentVersionMeta.was_active) === 1)) {
|
||||
const decision = await showActivateDialog();
|
||||
if (decision === 'cancel') return false;
|
||||
activateNext = decision === 'yes';
|
||||
}
|
||||
|
||||
if (currentEditorType === 'craftjs') {
|
||||
const html = craftEditor ? craftEditor.getContent() : '';
|
||||
const craftJson = craftEditor && craftEditor.getCraftJson
|
||||
? craftEditor.getCraftJson()
|
||||
: JSON.stringify({ html });
|
||||
const payload = { html, craft_json: craftJson, editor_type: 'craftjs', section_id: current.section.id };
|
||||
if (activateNext) payload.activate_version = 1;
|
||||
const res = await apiUpdate('content', current.id, payload);
|
||||
if (res?.ok) ok('Gespeichert');
|
||||
else err(res?.error || 'Speichern fehlgeschlagen');
|
||||
@@ -757,6 +839,14 @@ export function initEditor() {
|
||||
return res?.ok;
|
||||
}
|
||||
|
||||
if (activateNext) {
|
||||
const win = iframe?.contentWindow;
|
||||
if (win && win.BridgeParts) {
|
||||
win.BridgeParts.NEXT_ACTIVATE_VERSION = 1;
|
||||
} else if (win) {
|
||||
win.NEXT_ACTIVATE_VERSION = 1;
|
||||
}
|
||||
}
|
||||
const okSave = await delegateCommand('save-data');
|
||||
if (okSave) {
|
||||
setTimeout(async () => {
|
||||
@@ -921,6 +1011,18 @@ export function initEditor() {
|
||||
btnCancelSend&& (btnCancelSend.onclick= closeSend);
|
||||
sendForm && (sendForm.onsubmit = doSend);
|
||||
editorSelect && (editorSelect.onchange = () => switchEditor(editorSelect.value));
|
||||
btnDeactivateVersion && (btnDeactivateVersion.onclick = async () => {
|
||||
if (!current?.id) return;
|
||||
if (!currentVersionMeta || Number(currentVersionMeta.is_active) !== 1) return;
|
||||
try {
|
||||
const res = await apiAction('content_versions.deactivate', { method: 'POST', data: { content_id: current.id } });
|
||||
if (!res?.ok) throw new Error(res?.error || 'Deaktivieren fehlgeschlagen');
|
||||
await loadVersionsForCurrent();
|
||||
toast('Aktive Version deaktiviert', true);
|
||||
} catch (e) {
|
||||
err(e.message || 'Deaktivieren fehlgeschlagen');
|
||||
}
|
||||
});
|
||||
versionSelect && (versionSelect.onchange = async () => {
|
||||
if (!current?.id) return;
|
||||
const previousSelection = lastVersionSelection;
|
||||
@@ -939,6 +1041,7 @@ export function initEditor() {
|
||||
if (!res?.ok) throw new Error(res?.error || 'Version konnte nicht geladen werden');
|
||||
await applyVersionPayload(res?.item || res);
|
||||
lastVersionSelection = String(versionId);
|
||||
updateVersionMeta(versionId);
|
||||
} catch (e) {
|
||||
err(e.message || 'Version konnte nicht geladen werden');
|
||||
versionSelect.value = previousSelection;
|
||||
|
||||
@@ -29,6 +29,7 @@ async function fetchContentItem(id, sectionId) {
|
||||
}
|
||||
|
||||
async function openContentEditor(item, section) {
|
||||
const versionId = Number(item?.version_id || 0);
|
||||
const id = Number(item?.id || 0);
|
||||
const name = item?.name || '';
|
||||
if (!id) return;
|
||||
@@ -39,9 +40,9 @@ async function openContentEditor(item, section) {
|
||||
window.__currentEditorCtx = { id, mode: section.slug, section };
|
||||
|
||||
if (window.EditorUI && typeof window.EditorUI.open === 'function') {
|
||||
window.EditorUI.open({ id, name, html, section }, 'content');
|
||||
window.EditorUI.open({ id, name, html, section, version_id: versionId }, 'content');
|
||||
} else if (window.__openEditor) {
|
||||
window.__openEditor({ resource: 'content', id, name, html, section });
|
||||
window.__openEditor({ resource: 'content', id, name, html, section, version_id: versionId });
|
||||
} else {
|
||||
toast('Editor ist nicht initialisiert.', false);
|
||||
}
|
||||
@@ -189,11 +190,56 @@ export async function loadList(section) {
|
||||
return name.includes(q) || (api && api.includes(q));
|
||||
}
|
||||
|
||||
const versionCache = new Map();
|
||||
|
||||
async function loadVersionOptions(selectEl, itemId) {
|
||||
if (!selectEl || !itemId) return;
|
||||
if (versionCache.has(itemId)) {
|
||||
const cached = versionCache.get(itemId);
|
||||
renderVersionSelect(selectEl, cached.items, cached.activeId);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await apiAction('content_versions.list', { method: 'GET', data: { content_id: itemId } });
|
||||
const items = Array.isArray(res?.items) ? res.items : [];
|
||||
const active = items.find(v => Number(v.is_active) === 1);
|
||||
const activeId = active ? String(active.id) : (items[0] ? String(items[0].id) : '');
|
||||
versionCache.set(itemId, { items, activeId });
|
||||
renderVersionSelect(selectEl, items, activeId);
|
||||
} catch {
|
||||
renderVersionSelect(selectEl, [], '');
|
||||
}
|
||||
}
|
||||
|
||||
function renderVersionSelect(selectEl, items, activeId) {
|
||||
selectEl.innerHTML = '';
|
||||
if (!items.length) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = '';
|
||||
opt.textContent = 'Keine Versionen';
|
||||
opt.disabled = true;
|
||||
selectEl.appendChild(opt);
|
||||
selectEl.disabled = true;
|
||||
return;
|
||||
}
|
||||
selectEl.disabled = false;
|
||||
items.forEach(item => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = String(item.id);
|
||||
opt.textContent = `#${item.version_no}` + (Number(item.is_active) === 1 ? ' (aktiv)' : '');
|
||||
selectEl.appendChild(opt);
|
||||
});
|
||||
if (activeId) selectEl.value = activeId;
|
||||
}
|
||||
|
||||
function render(items) {
|
||||
list.innerHTML = items.map(item => {
|
||||
const name = esc(item.name || '');
|
||||
const apiName = isTemplate ? esc(item.api_name || '') : '';
|
||||
const apiLine = (isTemplate && apiName) ? `<div class='text-xs text-slate-500'>API: ${apiName}</div>` : '';
|
||||
const versionSelect = `<select class="input h-8 py-0 text-xs min-w-[160px]" data-version-select="${item.id}" disabled>
|
||||
<option value="">Versionen laden…</option>
|
||||
</select>`;
|
||||
const nameCell = `<div class='min-w-48'>
|
||||
<div class='font-medium truncate' title="${name}">${name || '(ohne Name)'}</div>
|
||||
${apiLine}
|
||||
@@ -207,6 +253,7 @@ export async function loadList(section) {
|
||||
return `<div class='p-3 flex items-center gap-3'>
|
||||
${nameCell}
|
||||
<div class='text-xs text-gray-500'>#${item.id}</div>
|
||||
${versionSelect}
|
||||
<div class='ms-auto flex gap-2'>${[openBtn, editTplBtn, testBtn, prevBtn, delBtn].filter(Boolean).join('')}</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
@@ -250,6 +297,18 @@ export async function loadList(section) {
|
||||
applyFilter();
|
||||
|
||||
function bindListHandlers(scope) {
|
||||
scope.querySelectorAll('[data-version-select]').forEach(sel => {
|
||||
const id = Number(sel.getAttribute('data-version-select') || 0);
|
||||
sel.addEventListener('focus', () => loadVersionOptions(sel, id));
|
||||
sel.addEventListener('click', () => loadVersionOptions(sel, id));
|
||||
sel.addEventListener('change', () => {
|
||||
const versionId = Number(sel.value || 0);
|
||||
if (!versionId) return;
|
||||
const item = data.find(it => Number(it.id) === id);
|
||||
if (!item) return;
|
||||
openContentEditor({ ...item, version_id: versionId }, section);
|
||||
});
|
||||
});
|
||||
scope.querySelectorAll('[data-open]').forEach(btn => btn.addEventListener('click', () => {
|
||||
const id = Number(btn.dataset.open || 0);
|
||||
const item = data.find(it => Number(it.id) === id);
|
||||
|
||||
@@ -62,6 +62,7 @@ require __DIR__ . '/../partials/structure/layout_start.php';
|
||||
<select id="versionSelect" class="input h-8 py-0 text-sm min-w-[200px]">
|
||||
<option value="">Letzte Versionen</option>
|
||||
</select>
|
||||
<button id="btn-deactivate-version" type="button" class="btn">Aktiv deaktivieren</button>
|
||||
<button id="btn-clear-main" type="button" class="btn" title="Leeren">🧹</button>
|
||||
<button id="btn-preview" type="button" class="btn">Vorschau</button>
|
||||
<button id="btn-test" type="button" class="btn">Testversand</button>
|
||||
@@ -125,6 +126,18 @@ require __DIR__ . '/../partials/structure/layout_start.php';
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog id="activateVersionDialog" class="rounded-2xl p-0 w-[440px]">
|
||||
<div class="p-4 bg-white rounded-2xl space-y-4">
|
||||
<h3 class="text-lg font-semibold">Neue Version aktivieren</h3>
|
||||
<p class="text-sm text-slate-600">Soll die neu gespeicherte Version als aktiv gesetzt werden?</p>
|
||||
<div class="flex justify-end gap-2">
|
||||
<button type="button" id="btn-activate-cancel" class="btn">Abbrechen</button>
|
||||
<button type="button" id="btn-activate-no" class="btn">Nein</button>
|
||||
<button type="button" id="btn-activate-yes" class="btn">Ja</button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<!-- Edit Snippet Dialog -->
|
||||
<dialog id="editSnippetDialog" class="rounded-2xl p-0 w-[700px]">
|
||||
<form id="editSnippetForm" method="dialog" class="p-4 bg-white rounded-2xl">
|
||||
|
||||
Reference in New Issue
Block a user