Redirect the other www spelling to SITE_ORIGIN, hold roles to the role help and fix the audit findings

SITE_ORIGIN alone decides whether the site is www or not: CanonicalHostSubscriber
sends the other spelling there with 301, cms:site:origin writes the setting, the
system screen shows it. The roles now match the role help (editor: drafts,
publisher: live content, admin: design, settings, users, system). The installer no
longer reopens on an unreachable database, the session cookie follows ADMIN_PATH,
percent-encoded credentials no longer break the container, consent only accepts
same-site decisions, JSON-LD is HTML-safe, own CSS stays in its block, plus the
editor, updater, download and public-output findings from the full review
(ADR 099, ADR 100).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Fable 5.1 committed 2026-09-15 20:15:20 +02:00
1 parent 45040b2ca6
commit 7eb462ac7b
53 files changed
+1023 -122

No files matched your search

+20 -9
View File
@@ -123,12 +123,20 @@ function controls() {
$('#move-up').disabled ||= current.index === 0;
$('#move-down').disabled ||= current.index === current.siblings.length - 1;
$('#nest').disabled ||= current.index === 0 || !canContain(current.siblings[current.index - 1], current.block);
$('#outdent').disabled ||= !current.parent;
// Ausruecken heisst: neben den Elternblock. Was dort nicht stehen darf (eine Spalte, ein
// Akkordeoneintrag), bleibt, wo es ist.
$('#outdent').disabled ||= !current.parent || !dropList(history.document, locate(history.document.blocks, current.parent.id)?.parent?.id ?? null, current.block);
$('#go-parent').disabled ||= !current.parent;
}
updateLibrary();
$('#presets').querySelectorAll('button').forEach(button => button.disabled = !ready || busy);
$('#properties').querySelectorAll('input,textarea,select,button').forEach(input => input.disabled = busy);
// Waehrend des Speicherns ist alles gesperrt - und danach nur das wieder frei, was diese
// Sperre geschlossen hat. Das Design-Bedienfeld schaltet Knoepfe gezielt ab ("Gestaltung
// einfuegen" ohne Zwischenablage), und die muessen abgeschaltet bleiben.
$('#properties').querySelectorAll('input,textarea,select,button').forEach(input => {
if (busy) { if (!input.disabled) { input.disabled = true; input.dataset.busyLock = '1'; } }
else if (input.dataset.busyLock) { input.disabled = false; delete input.dataset.busyLock; }
});
document.querySelectorAll('[data-panel],[data-viewport],.sidebar-nav [role=tab],#tree-search,#library-search,#expand-all,#collapse-all').forEach(input => input.disabled = !ready || busy);
$('[data-panel="design"]').disabled ||= !current;
$('#open-media-library').disabled = !ready || busy;
@@ -281,7 +289,7 @@ function renderContent(block, panel) {
for (const [val, text] of Object.entries(options.choices)) { const option = document.createElement('option'); option.value = val; option.textContent = text; input.append(option); }
} else input = document.createElement(options.multiline ? 'textarea' : 'input');
input.id = id; input.value = value ?? '';
if (options.number && !options.choices) { input.type = 'number'; input.min = options.min || '1'; input.max = options.max || '8000'; }
if (options.number && !options.choices) { input.type = 'number'; input.min = options.min ?? '1'; input.max = options.max ?? '8000'; }
if (options.checkbox) { input.type = 'checkbox'; input.checked = Boolean(value); labelEl.className = 'check-label'; labelEl.prepend(input); }
if (options.maxLength) input.maxLength = options.maxLength;
let previousChoice = input.value;
@@ -339,7 +347,7 @@ function renderContent(block, panel) {
if (current) current.props.text = markup;
}, `rich-${block.id}`);
controls(); schedulePreview();
}, { label: 'Text' });
}, { label: 'Text', plain: history.document.schemaVersion < 12 });
const label = document.createElement('label'); label.textContent = 'Text';
const help = document.createElement('p'); help.className = 'field-help';
help.textContent = 'Fett, kursiv, Listen und Links. Über HTML lässt sich die Auszeichnung direkt bearbeiten. Der Server lässt nur eine feste Liste an Auszeichnungen durch; alles andere wird beim Speichern entfernt, der Text bleibt.';
@@ -431,11 +439,11 @@ function renderContent(block, panel) {
}
if (block.type === 'progress') {
field('Beschriftung', 'label', block.props.label, { maxLength: 120 });
field('Wert (%)', 'value', block.props.value, { number: true, help: 'Zwischen 0 und 100. Der Wert steht auch im Markup, nicht nur in der Balkenbreite.' });
field('Wert (%)', 'value', block.props.value, { number: true, min: 0, max: 100, help: 'Zwischen 0 und 100. Der Wert steht auch im Markup, nicht nur in der Balkenbreite.' });
field('Wert anzeigen', 'showValue', block.props.showValue, { checkbox: true });
}
if (block.type === 'slider') {
field('Automatisch weiterblättern (Sekunden, 0 = aus)', 'autoplay', block.props.autoplay || 0, { number: true, help: 'Ab 3 Sekunden. Lädt ein kleines Skript, nur auf Seiten mit einem solchen Slider. Bei „weniger Bewegung“ im Betriebssystem bleibt er stehen, und die erste Berührung stoppt ihn endgültig.' });
field('Automatisch weiterblättern (Sekunden, 0 = aus)', 'autoplay', block.props.autoplay || 0, { number: true, min: 0, help: 'Ab 3 Sekunden. Lädt ein kleines Skript, nur auf Seiten mit einem solchen Slider. Bei „weniger Bewegung“ im Betriebssystem bleibt er stehen, und die erste Berührung stoppt ihn endgültig.' });
field('Punkte und Pfeile', 'dots', block.props.dots === true, { checkbox: true, help: 'Zeichnet der Browser selbst, ohne Skript. Wo er es noch nicht kann, bleibt ein Schiebefeld.' });
for (const [key, label] of [['desktopVisible', 'Sichtbar auf Desktop'], ['tabletVisible', 'Sichtbar auf Tablet'], ['mobileVisible', 'Sichtbar auf Mobil']]) {
field(label, key, block.props[key], { number: true, help: key === 'desktopVisible' ? 'Wie viele Folien gleichzeitig zu sehen sind, 1 bis 6.' : undefined });
@@ -582,7 +590,6 @@ function renderAdvanced(block, panel) {
: 'Eigenes Skript kann nur ein Administrator ändern, weil es Code im Browser jedes Besuchers ausführt. Der Inhalt ist hier nur zu sehen.', 7);
if (!mayScript) {
scriptInput.readOnly = true;
scriptInput.setAttribute('aria-describedby', 'own-script-locked');
}
}
@@ -711,7 +718,9 @@ function chooseMedia(multiple) {
const target = selected;
openMedia({ multiple, onChoose: items => {
if (!items.length || busy) return;
selected = history.change(doc => insertMedia(doc, target, items)); changed();
const id = history.change(doc => insertMedia(doc, target, items));
if (id === null) { notify('Hier lassen sich keine Bilder einfügen – wähle eine Sektion, Spalte oder Galerie.', true); return; }
selected = id; changed();
} });
}
@@ -1325,7 +1334,9 @@ $('#delete').addEventListener('click', () => actions.delete(selected));
for (const [id, direction] of [['move-up', -1], ['move-down', 1]]) $(`#${id}`).addEventListener('click', () => actions.move(selected, direction));
$('#nest').addEventListener('click', () => { history.change(doc => { const item = locate(doc.blocks, selected); const previous = item.siblings[item.index - 1]; if (previous) moveBlock(doc, selected, previous.id, true); }); changed(); });
$('#outdent').addEventListener('click', () => {
history.change(doc => { const item = locate(doc.blocks, selected); if (!item?.parent) return; const parent = locate(doc.blocks, item.parent.id); item.siblings.splice(item.index, 1); parent.siblings.splice(parent.index + 1, 0, item.block); }); changed();
// Derselbe Weg wie ein Zug im Baum, mit denselben Regeln: neben den Elternblock, sofern der
// Grosselternblock den Baustein nimmt.
history.change(doc => { const item = locate(doc.blocks, selected); if (!item?.parent) return; const parent = locate(doc.blocks, item.parent.id); moveBlockTo(doc, selected, parent.parent?.id ?? null, parent.index + 1); }); changed();
});
// ---------------------------------------------------------------------------------------------
// The preview as a working surface: outline, element toolbar, insert points, drag and drop.