Compare commits

...

6 Commits

Author SHA1 Message Date
Jürgen Mummert 76be3bcd09 Fix textarea HTML rendering and enforce light FilePond panels 2026-03-01 12:11:08 +01:00
Jürgen Mummert 84a6f118dc Adjust organization address fields and brighten FilePond preview 2026-03-01 12:00:46 +01:00
Jürgen Mummert 3176e6e414 Fix FilePond label interaction CSS conflicts 2026-03-01 11:52:43 +01:00
Jürgen Mummert 83c632f2dc Tune FilePond panel aspect ratios 2026-03-01 11:34:24 +01:00
Jürgen Mummert 15a93466b6 Adjust FilePond sizing and upload resize rules 2026-03-01 10:56:38 +01:00
Jürgen Mummert 3942569fb3 fix(member-events): confirm actions reliably and normalize duplicate titles 2026-03-01 10:22:53 +01:00
9 changed files with 212 additions and 13 deletions
@@ -10,11 +10,56 @@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/themes/airbnb.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/dist/filepond.min.css">
<link rel="stylesheet" href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.min.css"> <link rel="stylesheet" href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.min.css">
<style>
.module-event-edit .filepond--root {
width: 400px;
height: 600px;
}
.module-event-edit .filepond--root .filepond--drop-label {
height: 100%;
inset: 0;
}
.module-event-edit .filepond--root .filepond--drop-label label {
display: block;
width: auto;
float: none;
margin: 0;
padding: 0;
text-align: center;
font-weight: inherit;
}
.module-event-edit .filepond--root .filepond--drop-label,
.module-event-edit .filepond--root .filepond--drop-label label,
.module-event-edit .filepond--root .filepond--file-action-button {
pointer-events: auto;
}
.module-event-edit .filepond--root .filepond--image-preview-wrapper {
background: #f7f7f7;
}
.module-event-edit .filepond--root .filepond--panel-root,
.module-event-edit .filepond--root .filepond--item-panel,
.module-event-edit .filepond--root .filepond--image-preview {
background-color: #f7f7f7 !important;
}
.module-event-edit .filepond--root .filepond--image-preview-overlay-idle,
.module-event-edit .filepond--root .filepond--image-preview-overlay-success,
.module-event-edit .filepond--root .filepond--image-preview-overlay-failure {
background: rgba(255, 255, 255, 0.65);
}
</style>
<script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js"></script> <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"></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/l10n/de.js"></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/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-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>
{{ form_start(form, { action: app.request.uri, attr: { 'aria-live': 'polite' } }) }} {{ form_start(form, { action: app.request.uri, attr: { 'aria-live': 'polite' } }) }}
<input type="hidden" name="REQUEST_TOKEN" value="{{ requestToken }}"> <input type="hidden" name="REQUEST_TOKEN" value="{{ requestToken }}">
@@ -212,14 +257,46 @@
if (typeof window.FilePondPluginImagePreview !== 'undefined') { if (typeof window.FilePondPluginImagePreview !== 'undefined') {
window.FilePond.registerPlugin(window.FilePondPluginImagePreview); 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 = { const filePondOptions = {
instantUpload: false, instantUpload: false,
storeAsFile: true, storeAsFile: true,
allowMultiple: false, allowMultiple: false,
allowReplace: true, allowReplace: true,
stylePanelAspectRatio: 0.6666667,
styleItemPanelAspectRatio: 0.6666667,
credits: false, credits: false,
acceptedFileTypes: ['image/*'], 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>' labelIdle: 'Bild hierher ziehen oder <span class="filepond--label-action">durchsuchen</span>'
}; };
@@ -28,14 +28,14 @@
<a href="{{ item.editUrl }}" aria-label="{{ item.title }} bearbeiten">Bearbeiten</a> <a href="{{ item.editUrl }}" aria-label="{{ item.title }} bearbeiten">Bearbeiten</a>
{% endif %} {% endif %}
<form method="post" style="display:inline;" aria-label="{{ item.title }} duplizieren"> <form method="post" style="display:inline;" data-confirm-message="{{ ('Möchten Sie das Event \"' ~ item.title ~ '\" wirklich duplizieren?')|e('html_attr') }}" aria-label="{{ item.title }} duplizieren">
<input type="hidden" name="REQUEST_TOKEN" value="{{ requestToken }}"> <input type="hidden" name="REQUEST_TOKEN" value="{{ requestToken }}">
<input type="hidden" name="action" value="duplicate"> <input type="hidden" name="action" value="duplicate">
<input type="hidden" name="event_id" value="{{ item.id }}"> <input type="hidden" name="event_id" value="{{ item.id }}">
<button type="submit" aria-label="{{ item.title }} duplizieren">Duplizieren</button> <button type="submit" aria-label="{{ item.title }} duplizieren">Duplizieren</button>
</form> </form>
<form method="post" style="display:inline;" onsubmit="return confirm('wirklich löschen?');" aria-label="{{ item.title }} löschen"> <form method="post" style="display:inline;" data-confirm-message="{{ ('Möchten Sie das Event \"' ~ item.title ~ '\" wirklich löschen?')|e('html_attr') }}" aria-label="{{ item.title }} löschen">
<input type="hidden" name="REQUEST_TOKEN" value="{{ requestToken }}"> <input type="hidden" name="REQUEST_TOKEN" value="{{ requestToken }}">
<input type="hidden" name="action" value="delete"> <input type="hidden" name="action" value="delete">
<input type="hidden" name="event_id" value="{{ item.id }}"> <input type="hidden" name="event_id" value="{{ item.id }}">
@@ -56,6 +56,8 @@
</form> </form>
{% endif %} {% endif %}
<script src="/bundles/mummertmediaeventmanager/assets/member-events-confirm.js?v=20260301a"></script>
<h2>Vergangene Veranstaltungen</h2> <h2>Vergangene Veranstaltungen</h2>
{% if pastEvents is empty %} {% if pastEvents is empty %}
<p>Keine vergangenen Veranstaltungen gefunden.</p> <p>Keine vergangenen Veranstaltungen gefunden.</p>
@@ -83,14 +85,14 @@
<a href="{{ item.editUrl }}" aria-label="{{ item.title }} bearbeiten">Bearbeiten</a> <a href="{{ item.editUrl }}" aria-label="{{ item.title }} bearbeiten">Bearbeiten</a>
{% endif %} {% endif %}
<form method="post" style="display:inline;" aria-label="{{ item.title }} duplizieren"> <form method="post" style="display:inline;" data-confirm-message="{{ ('Möchten Sie das Event \"' ~ item.title ~ '\" wirklich duplizieren?')|e('html_attr') }}" aria-label="{{ item.title }} duplizieren">
<input type="hidden" name="REQUEST_TOKEN" value="{{ requestToken }}"> <input type="hidden" name="REQUEST_TOKEN" value="{{ requestToken }}">
<input type="hidden" name="action" value="duplicate"> <input type="hidden" name="action" value="duplicate">
<input type="hidden" name="event_id" value="{{ item.id }}"> <input type="hidden" name="event_id" value="{{ item.id }}">
<button type="submit" aria-label="{{ item.title }} duplizieren">Duplizieren</button> <button type="submit" aria-label="{{ item.title }} duplizieren">Duplizieren</button>
</form> </form>
<form method="post" style="display:inline;" onsubmit="return confirm('wirklich löschen?');" aria-label="{{ item.title }} löschen"> <form method="post" style="display:inline;" data-confirm-message="{{ ('Möchten Sie das Event \"' ~ item.title ~ '\" wirklich löschen?')|e('html_attr') }}" aria-label="{{ item.title }} löschen">
<input type="hidden" name="REQUEST_TOKEN" value="{{ requestToken }}"> <input type="hidden" name="REQUEST_TOKEN" value="{{ requestToken }}">
<input type="hidden" name="action" value="delete"> <input type="hidden" name="action" value="delete">
<input type="hidden" name="event_id" value="{{ item.id }}"> <input type="hidden" name="event_id" value="{{ item.id }}">
@@ -8,9 +8,54 @@
<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/choices.js/public/assets/styles/choices.min.css">
<link rel="stylesheet" href="https://unpkg.com/filepond/dist/filepond.min.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"> <link rel="stylesheet" href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.min.css">
<style>
.module-organization-edit .filepond--root {
width: 400px;
height: 400px;
}
.module-organization-edit .filepond--root .filepond--drop-label {
height: 100%;
inset: 0;
}
.module-organization-edit .filepond--root .filepond--drop-label label {
display: block;
width: auto;
float: none;
margin: 0;
padding: 0;
text-align: center;
font-weight: inherit;
}
.module-organization-edit .filepond--root .filepond--drop-label,
.module-organization-edit .filepond--root .filepond--drop-label label,
.module-organization-edit .filepond--root .filepond--file-action-button {
pointer-events: auto;
}
.module-organization-edit .filepond--root .filepond--image-preview-wrapper {
background: #f7f7f7;
}
.module-organization-edit .filepond--root .filepond--panel-root,
.module-organization-edit .filepond--root .filepond--item-panel,
.module-organization-edit .filepond--root .filepond--image-preview {
background-color: #f7f7f7 !important;
}
.module-organization-edit .filepond--root .filepond--image-preview-overlay-idle,
.module-organization-edit .filepond--root .filepond--image-preview-overlay-success,
.module-organization-edit .filepond--root .filepond--image-preview-overlay-failure {
background: rgba(255, 255, 255, 0.65);
}
</style>
<script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js"></script>
<script src="https://unpkg.com/filepond/dist/filepond.min.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-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>
{{ form_start(form, { attr: { 'aria-live': 'polite' } }) }} {{ form_start(form, { attr: { 'aria-live': 'polite' } }) }}
<input type="hidden" name="REQUEST_TOKEN" value="{{ requestToken }}"> <input type="hidden" name="REQUEST_TOKEN" value="{{ requestToken }}">
@@ -73,14 +118,46 @@
if (typeof window.FilePondPluginImagePreview !== 'undefined') { if (typeof window.FilePondPluginImagePreview !== 'undefined') {
window.FilePond.registerPlugin(window.FilePondPluginImagePreview); 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 = { const filePondOptions = {
instantUpload: false, instantUpload: false,
storeAsFile: true, storeAsFile: true,
allowMultiple: false, allowMultiple: false,
allowReplace: true, allowReplace: true,
stylePanelAspectRatio: 1,
styleItemPanelAspectRatio: 1,
credits: false, credits: false,
acceptedFileTypes: ['image/*'], 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: 800,
height: 800
}
});
},
labelIdle: 'Logo hierher ziehen oder <span class="filepond--label-action">durchsuchen</span>' labelIdle: 'Logo hierher ziehen oder <span class="filepond--label-action">durchsuchen</span>'
}; };
+28
View File
@@ -0,0 +1,28 @@
(function () {
var decodeHtmlEntities = function (value) {
var textarea = document.createElement('textarea');
textarea.innerHTML = String(value || '');
return textarea.value;
};
var handleSubmit = function (event) {
var target = event.target;
if (!(target instanceof HTMLFormElement)) {
return;
}
var message = decodeHtmlEntities(target.getAttribute('data-confirm-message') || '');
if (!message) {
return;
}
if (!window.confirm(message)) {
event.preventDefault();
}
};
document.addEventListener('submit', handleSubmit, true);
})();
@@ -65,7 +65,14 @@ class MemberEventsController extends AbstractFrontendModuleController
if ('toggle_published' === $action) { if ('toggle_published' === $action) {
$this->eventRepository->togglePublished($eventId); $this->eventRepository->togglePublished($eventId);
} elseif ('duplicate' === $action) { } elseif ('duplicate' === $action) {
$this->eventRepository->duplicate($eventId); $newEventId = $this->eventRepository->duplicate($eventId);
if ($newEventId > 0 && $editPage instanceof PageModel) {
return new RedirectResponse($this->generateContentUrl($editPage, [
'event' => (string) $newEventId,
'ref' => base64_encode($backUrl),
]));
}
} elseif ('delete' === $action) { } elseif ('delete' === $action) {
$this->eventRepository->delete($eventId); $this->eventRepository->delete($eventId);
} }
@@ -70,14 +70,13 @@ class OrganizationEditController extends AbstractFrontendModuleController
$formData = [ $formData = [
'title' => (string) ($organization['title'] ?? ''), 'title' => (string) ($organization['title'] ?? ''),
'street' => (string) ($organization['street'] ?? ''), 'street' => (string) ($organization['street'] ?? ''),
'street2' => (string) ($organization['street2'] ?? ''),
'postal' => (string) ($organization['postal'] ?? ''), 'postal' => (string) ($organization['postal'] ?? ''),
'city' => (string) ($organization['city'] ?? ''), 'city' => (string) ($organization['city'] ?? ''),
'state' => (string) ($organization['state'] ?? ''),
'country' => (string) ($organization['country'] ?? ''),
'phone' => (string) ($organization['phone'] ?? ''), 'phone' => (string) ($organization['phone'] ?? ''),
'email' => (string) ($organization['email'] ?? ''), 'email' => (string) ($organization['email'] ?? ''),
'website' => (string) ($organization['website'] ?? ''), 'website' => (string) ($organization['website'] ?? ''),
'description' => (string) ($organization['description'] ?? ''), 'description' => $this->normalizeTextareaDescription((string) ($organization['description'] ?? '')),
'tags' => $this->organizationRepository->getTagIdsForOrganization($organizationId), 'tags' => $this->organizationRepository->getTagIdsForOrganization($organizationId),
]; ];
@@ -219,4 +218,14 @@ class OrganizationEditController extends AbstractFrontendModuleController
return $page instanceof PageModel ? $this->generateContentUrl($page) : '/'; return $page instanceof PageModel ? $this->generateContentUrl($page) : '/';
} }
private function normalizeTextareaDescription(string $value): string
{
$decoded = html_entity_decode($value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$decoded = preg_replace('/<\s*br\s*\/?>/i', "\n", $decoded) ?? $decoded;
$decoded = preg_replace('/<\s*\/p\s*>\s*<\s*p[^>]*>/i', "\n\n", $decoded) ?? $decoded;
$decoded = strip_tags($decoded);
return trim($decoded);
}
} }
+1 -2
View File
@@ -20,10 +20,9 @@ class OrganizationType extends AbstractType
$builder $builder
->add('title', TextType::class, ['label' => 'Titel', 'required' => true]) ->add('title', TextType::class, ['label' => 'Titel', 'required' => true])
->add('street', TextType::class, ['label' => 'Straße', 'required' => false]) ->add('street', TextType::class, ['label' => 'Straße', 'required' => false])
->add('street2', TextType::class, ['label' => 'Weitere Adressangaben', 'required' => false])
->add('postal', TextType::class, ['label' => 'PLZ', 'required' => false]) ->add('postal', TextType::class, ['label' => 'PLZ', 'required' => false])
->add('city', TextType::class, ['label' => 'Ort', 'required' => false]) ->add('city', TextType::class, ['label' => 'Ort', 'required' => false])
->add('state', TextType::class, ['label' => 'Bundesland', 'required' => false])
->add('country', TextType::class, ['label' => 'Land', 'required' => false])
->add('phone', TextType::class, ['label' => 'Telefon', 'required' => false]) ->add('phone', TextType::class, ['label' => 'Telefon', 'required' => false])
->add('email', EmailType::class, ['label' => 'E-Mail', 'required' => false]) ->add('email', EmailType::class, ['label' => 'E-Mail', 'required' => false])
->add('website', TextType::class, ['label' => 'Webseite', 'required' => false]) ->add('website', TextType::class, ['label' => 'Webseite', 'required' => false])
+2 -1
View File
@@ -84,7 +84,8 @@ class EventRepository
unset($row['id']); unset($row['id']);
if (array_key_exists('title', $row)) { if (array_key_exists('title', $row)) {
$row['title'] = trim((string) $row['title'].' (Kopie)'); $normalizedTitle = html_entity_decode((string) $row['title'], ENT_QUOTES | ENT_HTML5, 'UTF-8');
$row['title'] = trim($normalizedTitle.' (Kopie)');
} }
if (array_key_exists('alias', $row)) { if (array_key_exists('alias', $row)) {
+1 -2
View File
@@ -71,10 +71,9 @@ class OrganizationRepository
[ [
'title' => $data['title'] ?? '', 'title' => $data['title'] ?? '',
'street' => $data['street'] ?? '', 'street' => $data['street'] ?? '',
'street2' => $data['street2'] ?? '',
'postal' => $data['postal'] ?? '', 'postal' => $data['postal'] ?? '',
'city' => $data['city'] ?? '', 'city' => $data['city'] ?? '',
'state' => $data['state'] ?? '',
'country' => $data['country'] ?? '',
'phone' => $data['phone'] ?? '', 'phone' => $data['phone'] ?? '',
'email' => $data['email'] ?? '', 'email' => $data['email'] ?? '',
'website' => $data['website'] ?? '', 'website' => $data['website'] ?? '',