Files
2026-03-08 18:22:59 +01:00

496 lines
22 KiB
Twig
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "@Contao/frontend_module/_base.html.twig" %}
{% block content %}
{% if error is defined and error %}
<p role="alert">{{ error }}</p>
<p><a href="{{ backUrl }}">Zurück</a></p>
{% elseif form is defined and form %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js/public/assets/styles/choices.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/themes/airbnb.css">
<link rel="stylesheet" href="https://unpkg.com/filepond/dist/filepond.min.css">
<link rel="stylesheet" href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.min.css">
<script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/l10n/de.js"></script>
<script src="https://unpkg.com/filepond/dist/filepond.min.js"></script>
<script src="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.min.js"></script>
<script src="https://unpkg.com/filepond-plugin-image-resize/dist/filepond-plugin-image-resize.min.js"></script>
<script src="https://unpkg.com/filepond-plugin-image-transform/dist/filepond-plugin-image-transform.min.js"></script>
<script type="module" src="{{ asset('bundles/eventmanager/editor.js') }}?v=1"></script>
<script src="{{ asset('bundles/eventmanager/editor-fallback.js') }}?v=1"></script>
{{ form_start(form, { action: app.request.uri, attr: { 'aria-live': 'polite' } }) }}
<input type="hidden" name="REQUEST_TOKEN" value="{{ requestToken }}">
<input type="hidden" id="remove_image" name="remove_image" value="0">
{{ form_row(form.title) }}
{{ form_row(form.startDate) }}
{{ form_row(form.endDate) }}
<div class="widget fullwidth">
<div class="ns-tag-switch-item">
{{ form_widget(form.addTime, { attr: { class: (form.addTime.vars.attr.class|default('') ~ ' ns-tag-switch-input')|trim, role: 'switch' } }) }}
{{ form_label(form.addTime) }}
</div>
{{ form_errors(form.addTime) }}
</div>
<div id="event-time-wrap" hidden aria-hidden="true">
{{ form_row(form.startTime) }}
{{ form_row(form.endTime) }}
</div>
{% if form.organization_ids is defined %}
{{ form_row(form.organization_ids) }}
{% endif %}
{{ form_row(form.location_id) }}
<fieldset class="widget" aria-describedby="event-tags-help event-tags-errors">
<legend>{{ form.tags.vars.label }}</legend>
<p id="event-tags-help" class="event-editor-shortcuts">Mehrfachauswahl möglich. Mit Leertaste ein- und ausschalten.</p>
<div class="ns-tag-switches">
{% for tagField in form.tags %}
{% set tagLabelId = tagField.vars.id ~ '-label' %}
<div class="ns-tag-switch-item">
{{ form_widget(tagField, { attr: { 'aria-describedby': tagField.vars.id ~ '-state', 'aria-labelledby': tagLabelId } }) }}
{{ form_label(tagField, null, { label_attr: { id: tagLabelId } }) }}
<span
id="{{ tagField.vars.id }}-state"
class="visually-hidden"
data-switch-state-for="{{ tagField.vars.id }}"
>
{{ tagField.vars.checked ? 'Ein' : 'Aus' }}
</span>
</div>
{% endfor %}
</div>
<div id="event-tags-errors">{{ form_errors(form.tags) }}</div>
</fieldset>
<div class="widget">
{{ form_label(form.teaser, null, { label_attr: { id: form.teaser.vars.id ~ '-label' } }) }}
<div
class="event-editor-toolbar"
data-mm-editor-toolbar="{{ form.teaser.vars.id }}"
role="toolbar"
aria-label="Formatierungswerkzeuge Beschreibung"
aria-orientation="horizontal"
aria-describedby="{{ form.teaser.vars.id }}-shortcuts"
aria-controls="{{ form.teaser.vars.id }}-editor"
>
<button type="button" data-action="paragraph" title="Absatz">
<img src="{{ asset('bundles/eventmanager/icons/paragraph.svg') }}" alt="" aria-hidden="true">
<span class="visually-hidden">Absatz</span>
</button>
<button type="button" data-action="h2" title="Überschrift H2">
<img src="{{ asset('bundles/eventmanager/icons/h2.svg') }}" alt="" aria-hidden="true">
<span class="visually-hidden">H2</span>
</button>
<button type="button" data-action="h3" title="Überschrift H3">
<img src="{{ asset('bundles/eventmanager/icons/h3.svg') }}" alt="" aria-hidden="true">
<span class="visually-hidden">H3</span>
</button>
<button type="button" data-action="bold" title="Fett (Strg/Cmd+B)" aria-keyshortcuts="Control+B Meta+B">
<img src="{{ asset('bundles/eventmanager/icons/bold.svg') }}" alt="" aria-hidden="true">
<span class="visually-hidden">Fett</span>
</button>
<button type="button" data-action="italic" title="Kursiv (Strg/Cmd+I)" aria-keyshortcuts="Control+I Meta+I">
<img src="{{ asset('bundles/eventmanager/icons/italic.svg') }}" alt="" aria-hidden="true">
<span class="visually-hidden">Kursiv</span>
</button>
<button type="button" data-action="underline" title="Unterstrichen (Strg/Cmd+U)" aria-keyshortcuts="Control+U Meta+U">
<img src="{{ asset('bundles/eventmanager/icons/underline.svg') }}" alt="" aria-hidden="true">
<span class="visually-hidden">Unterstrichen</span>
</button>
<button type="button" data-action="bulletList" title="Liste">
<img src="{{ asset('bundles/eventmanager/icons/ul.svg') }}" alt="" aria-hidden="true">
<span class="visually-hidden">Liste</span>
</button>
<button type="button" data-action="orderedList" title="Nummerierte Liste">
<img src="{{ asset('bundles/eventmanager/icons/ol.svg') }}" alt="" aria-hidden="true">
<span class="visually-hidden">Nummerierte Liste</span>
</button>
<button type="button" data-action="indent" title="Einzug vergrößern">
<img src="{{ asset('bundles/eventmanager/icons/indent.svg') }}" alt="" aria-hidden="true">
<span class="visually-hidden">Einzug vergrößern</span>
</button>
<button type="button" data-action="outdent" title="Einzug verkleinern">
<img src="{{ asset('bundles/eventmanager/icons/outdent.svg') }}" alt="" aria-hidden="true">
<span class="visually-hidden">Einzug verkleinern</span>
</button>
<button type="button" data-action="undo" title="Rückgängig (Strg/Cmd+Z)" aria-keyshortcuts="Control+Z Meta+Z">
<img src="{{ asset('bundles/eventmanager/icons/undo.svg') }}" alt="" aria-hidden="true">
<span class="visually-hidden">Rückgängig</span>
</button>
<button type="button" data-action="redo" title="Wiederholen (Strg/Cmd+Shift+Z)" aria-keyshortcuts="Control+Shift+Z Meta+Shift+Z">
<img src="{{ asset('bundles/eventmanager/icons/redo.svg') }}" alt="" aria-hidden="true">
<span class="visually-hidden">Wiederholen</span>
</button>
</div>
<div id="{{ form.teaser.vars.id }}-shortcuts" class="event-editor-shortcuts">
<strong>Tastaturkürzel:</strong>
<ul>
<li><span aria-hidden="true">Strg/Cmd+B</span> Fett</li>
<li><span aria-hidden="true">Strg/Cmd+I</span> Kursiv</li>
<li><span aria-hidden="true">Strg/Cmd+U</span> Unterstrichen</li>
<li><span aria-hidden="true">Strg/Cmd+Z</span> Rückgängig</li>
<li><span aria-hidden="true">Strg/Cmd+Shift+Z</span> Wiederholen</li>
<li>Pfeiltasten links/rechts in der Toolbar zwischen Buttons wechseln</li>
</ul>
</div>
<div
id="{{ form.teaser.vars.id }}-editor"
class="event-editor"
data-mm-editor="tiptap"
data-textarea-id="{{ form.teaser.vars.id }}"
data-editor-label="Beschreibung"
role="textbox"
aria-multiline="true"
aria-labelledby="{{ form.teaser.vars.id }}-label"
aria-describedby="{{ form.teaser.vars.id }}-shortcuts {{ form.teaser.vars.id }}-counter {{ form.teaser.vars.id }}-errors"
></div>
<div id="{{ form.teaser.vars.id }}-counter" class="event-editor-counter" data-mm-editor-counter-for="{{ form.teaser.vars.id }}" role="status" aria-live="polite"></div>
{{ form_widget(form.teaser, { attr: { class: 'js-event-teaser-source', rows: 8, 'aria-hidden': 'true', tabindex: '-1' } }) }}
<div id="{{ form.teaser.vars.id }}-errors">{{ form_errors(form.teaser) }}</div>
</div>
{{ form_row(form.url) }}
<div class="widget fullwidth">
<div class="ns-tag-switch-item">
{{ form_widget(form.addImage, { attr: { class: (form.addImage.vars.attr.class|default('') ~ ' ns-tag-switch-input')|trim, role: 'switch' } }) }}
{{ form_label(form.addImage) }}
</div>
{{ form_errors(form.addImage) }}
</div>
<div id="event-image-upload-wrap" hidden aria-hidden="true">
{{ form_row(form.eventUpload) }}
{{ form_row(form.photographer) }}
<p class="help-text">Die Angabe des Urhebers ist notwendig. Ihnen muss eine Genehmigung zur Verwendung des Bildes vorliegen.</p>
</div>
<div class="widget fullwidth">
<div class="ns-tag-switch-item">
{{ form_widget(form.termsAccepted, { attr: { class: (form.termsAccepted.vars.attr.class|default('') ~ ' ns-tag-switch-input')|trim, role: 'switch' } }) }}
{{ form_label(form.termsAccepted) }}
</div>
{{ form_errors(form.termsAccepted) }}
</div>
<div class="widget fullwidth">
<div class="ns-tag-switch-item">
{{ form_widget(form.isSoldOut, { attr: { class: (form.isSoldOut.vars.attr.class|default('') ~ ' ns-tag-switch-input')|trim, role: 'switch' } }) }}
{{ form_label(form.isSoldOut) }}
</div>
{{ form_errors(form.isSoldOut) }}
</div>
<div class="widget fullwidth">
<div class="ns-tag-switch-item">
{{ form_widget(form.isCanceled, { attr: { class: (form.isCanceled.vars.attr.class|default('') ~ ' ns-tag-switch-input')|trim, role: 'switch' } }) }}
{{ form_label(form.isCanceled) }}
</div>
{{ form_errors(form.isCanceled) }}
</div>
<div class="widget fullwidth">
<div class="ns-tag-switch-item">
{{ form_widget(form.published, { attr: { class: (form.published.vars.attr.class|default('') ~ ' ns-tag-switch-input')|trim, role: 'switch' } }) }}
{{ form_label(form.published) }}
</div>
{{ form_errors(form.published) }}
</div>
<div class="actions" aria-label="Formularaktionen">
<button type="submit" id="save-btn" disabled>Speichern</button>
<button type="submit" id="save-back-btn" name="save_back" value="1" disabled>Speichern und Zurück</button>
<a href="{{ backUrl }}" aria-label="Zurück zur vorherigen Seite">Zurück</a>
</div>
{{ form_end(form) }}
<script>
(function () {
const locationSelect = document.querySelector('select.js-location-choice');
const organizationSelect = document.querySelector('select.js-organization-choice');
const tagSwitches = document.querySelectorAll('input.ns-tag-switch-input[role="switch"]');
if (organizationSelect && typeof window.Choices === 'function') {
new window.Choices(organizationSelect, {
searchEnabled: true,
shouldSort: false,
removeItemButton: true,
itemSelectText: '',
placeholder: true,
placeholderValue: organizationSelect.dataset.placeholder || 'Veranstalter suchen …',
noResultsText: 'Keine Treffer',
noChoicesText: 'Keine Optionen verfügbar'
});
}
if (locationSelect && typeof window.Choices === 'function') {
new window.Choices(locationSelect, {
searchEnabled: true,
shouldSort: false,
itemSelectText: '',
placeholder: true,
placeholderValue: locationSelect.dataset.placeholder || 'Suchen …',
noResultsText: 'Keine Treffer',
noChoicesText: 'Keine Optionen verfügbar'
});
}
const syncSwitchState = (input) => {
const isChecked = !!input.checked;
input.setAttribute('aria-checked', isChecked ? 'true' : 'false');
const stateNode = document.querySelector(`[data-switch-state-for="${input.id}"]`);
if (stateNode) {
stateNode.textContent = isChecked ? 'Ein' : 'Aus';
}
};
tagSwitches.forEach((input) => {
syncSwitchState(input);
input.addEventListener('change', () => syncSwitchState(input));
});
if (typeof window.flatpickr === 'function') {
const dateInputs = document.querySelectorAll('input.js-flatpickr-date');
dateInputs.forEach(function (input) {
window.flatpickr(input, {
locale: 'de',
dateFormat: 'Y-m-d',
allowInput: true
});
});
const timeInputs = document.querySelectorAll('input.js-flatpickr-time');
timeInputs.forEach(function (input) {
window.flatpickr(input, {
locale: 'de',
enableTime: true,
noCalendar: true,
dateFormat: 'H:i',
time_24hr: true,
allowInput: true
});
});
}
const addImageToggle = document.querySelector('input.js-add-image-toggle');
const addTimeToggle = document.querySelector('input.js-add-time-toggle');
const timeWrap = document.getElementById('event-time-wrap');
const startTimeInput = document.getElementById('{{ form.startTime.vars.id }}');
const imageWrap = document.getElementById('event-image-upload-wrap');
const imageInput = document.querySelector('input.js-event-image-upload');
const photographerInput = document.querySelector('input.js-photographer');
const removeImageInput = document.getElementById('remove_image');
const currentImagePath = {{ (currentImagePath is defined and currentImagePath) ? ('/' ~ currentImagePath)|json_encode|raw : 'null' }};
const hadInitialImage = !!currentImagePath;
let pondDirty = false;
const form = document.querySelector('form');
const saveButton = document.getElementById('save-btn');
const saveBackButton = document.getElementById('save-back-btn');
if (!form || !saveButton || !saveBackButton) {
return;
}
const initial = new FormData(form);
const hasChanges = () => {
const current = new FormData(form);
for (const [key, value] of current.entries()) {
if (initial.getAll(key).join(',') !== current.getAll(key).join(',')) {
return true;
}
}
return false;
};
const updateButtons = () => {
const changed = hasChanges() || pondDirty;
saveButton.disabled = !changed;
saveBackButton.disabled = !changed;
};
const setImageVisibility = () => {
if (!addImageToggle || !imageWrap) {
return;
}
addImageToggle.setAttribute('aria-controls', 'event-image-upload-wrap');
addImageToggle.setAttribute('aria-expanded', addImageToggle.checked ? 'true' : 'false');
imageWrap.style.display = addImageToggle.checked ? '' : 'none';
imageWrap.hidden = !addImageToggle.checked;
imageWrap.setAttribute('aria-hidden', addImageToggle.checked ? 'false' : 'true');
if (photographerInput) {
photographerInput.required = !!addImageToggle.checked;
}
if (!addImageToggle.checked && removeImageInput) {
removeImageInput.value = hadInitialImage ? '1' : '0';
}
updateButtons();
};
const setTimeVisibility = () => {
if (!addTimeToggle || !timeWrap) {
return;
}
addTimeToggle.setAttribute('aria-controls', 'event-time-wrap');
addTimeToggle.setAttribute('aria-expanded', addTimeToggle.checked ? 'true' : 'false');
timeWrap.style.display = addTimeToggle.checked ? '' : 'none';
timeWrap.hidden = !addTimeToggle.checked;
timeWrap.setAttribute('aria-hidden', addTimeToggle.checked ? 'false' : 'true');
if (startTimeInput) {
startTimeInput.required = !!addTimeToggle.checked;
}
updateButtons();
};
if (addTimeToggle) {
addTimeToggle.addEventListener('change', setTimeVisibility);
setTimeVisibility();
}
if (addImageToggle) {
addImageToggle.addEventListener('change', setImageVisibility);
setImageVisibility();
}
if (imageInput && typeof window.FilePond !== 'undefined') {
if (typeof window.FilePondPluginImagePreview !== 'undefined') {
window.FilePond.registerPlugin(window.FilePondPluginImagePreview);
}
if (typeof window.FilePondPluginImageResize !== 'undefined') {
window.FilePond.registerPlugin(window.FilePondPluginImageResize);
}
if (typeof window.FilePondPluginImageTransform !== 'undefined') {
window.FilePond.registerPlugin(window.FilePondPluginImageTransform);
}
const filePondOptions = {
instantUpload: false,
storeAsFile: true,
allowMultiple: false,
allowReplace: true,
stylePanelAspectRatio: 0.6666667,
styleItemPanelAspectRatio: 0.6666667,
credits: false,
acceptedFileTypes: ['image/*'],
allowImageResize: true,
imageResizeUpscale: false,
allowImageTransform: true,
onaddfile: function (error, fileItem) {
if (error || !fileItem || typeof fileItem.setMetadata !== 'function') {
return;
}
const fileType = fileItem.fileType || (fileItem.file && fileItem.file.type) || '';
if (fileType === 'image/svg+xml') {
fileItem.setMetadata('resize', null);
return;
}
fileItem.setMetadata('resize', {
mode: 'contain',
upscale: false,
size: {
width: 2000,
height: 2000
}
});
},
labelIdle: 'Bild hierher ziehen oder <span class="filepond--label-action">durchsuchen</span>'
};
if (currentImagePath) {
filePondOptions.files = [
{
source: currentImagePath,
options: {
type: 'local'
}
}
];
filePondOptions.server = {
load: (source, load, error, progress, abort) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', source);
xhr.responseType = 'blob';
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300) {
load(xhr.response);
} else {
error('Laden fehlgeschlagen');
}
};
xhr.onerror = function () {
error('Laden fehlgeschlagen');
};
xhr.send();
return {
abort: () => {
xhr.abort();
abort();
}
};
}
};
}
const pond = window.FilePond.create(imageInput, filePondOptions);
const syncPondState = () => {
const files = pond.getFiles();
const hasLocalFile = files.some((file) => window.FilePond.FileOrigin && file.origin === window.FilePond.FileOrigin.LOCAL);
const hasNewFile = files.some((file) => window.FilePond.FileOrigin && file.origin !== window.FilePond.FileOrigin.LOCAL);
const imageEnabled = addImageToggle ? addImageToggle.checked : true;
pondDirty = imageEnabled && (hasNewFile || (hadInitialImage && !hasLocalFile));
if (removeImageInput) {
if (!imageEnabled) {
removeImageInput.value = hadInitialImage ? '1' : '0';
} else {
removeImageInput.value = hadInitialImage && !hasLocalFile && !hasNewFile ? '1' : '0';
}
}
updateButtons();
};
syncPondState();
pond.on('removefile', function () {
syncPondState();
});
pond.on('addfile', function () {
syncPondState();
});
pond.on('updatefiles', function () {
syncPondState();
});
}
form.addEventListener('input', function () {
updateButtons();
});
form.addEventListener('change', function () {
updateButtons();
});
})();
</script>
{% else %}
<p>Kein Formular verfügbar.</p>
{% endif %}
{% endblock %}