496 lines
22 KiB
Twig
496 lines
22 KiB
Twig
{% 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 %}
|